web-dev-qa-db-fra.com

Quels sont les meilleurs moyens d'éviter le do-while (0); bidouille en C ++?

Lorsque le flux de code est comme ceci:

if(check())
{
  ...
  ...
  if(check())
  {
    ...
    ...
    if(check())
    {
      ...
      ...
    }
  }
}

J'ai généralement vu ce travail pour éviter le flux de code désordonné ci-dessus:

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

Quels sont les meilleurs moyens d'éviter cette solution de contournement/piratage afin qu'il devienne un code de niveau supérieur (au niveau de l'industrie)?

Toutes les suggestions hors de la boîte sont les bienvenues!

232
Sankalp

Il est considéré comme une pratique acceptable d’isoler ces décisions dans une fonction et d’utiliser returns au lieu de breaks. Bien que toutes ces vérifications correspondent au même niveau d'abstraction que pour la fonction, c'est une approche assez logique.

Par exemple:

void foo(...)
{
   if (!condition)
   {
      return;
   }
   ...
   if (!other condition)
   {
      return;
   }
   ...
   if (!another condition)
   {
      return;
   }
   ... 
   if (!yet another condition)
   {
      return;
   }
   ...
   // Some unconditional stuff       
}
307
Mikhail

Parfois, utiliser goto est en réalité la bonne réponse - du moins à ceux qui ne sont pas éduqués dans la conviction religieuse que "goto ne peut jamais être la réponse, peu importe la question. "- et c'est l'un de ces cas.

Ce code utilise le hack de do { ... } while(0); uniquement pour habiller un goto en tant que break. Si vous utilisez goto, alors soyez ouvert à ce sujet. Il ne sert à rien de rendre le code plus difficile à lire.

Une situation particulière est juste lorsque vous avez beaucoup de code avec des conditions assez complexes:

void func()
{
   setup of lots of stuff
   ...
   if (condition)
   {
      ... 
      ...
      if (!other condition)
      {
          ...
          if (another condition)
          {
              ... 
              if (yet another condition)
              {
                  ...
                  if (...)
                     ... 
              }
          }
      }
  .... 

  }
  finish up. 
}

En réalité, il peut être plus clair que le code est correct sans une logique aussi complexe.

void func()
{
   setup of lots of stuff
   ...
   if (!condition)
   {
      goto finish;
   }
   ... 
   ...
   if (other condition)
   {
      goto finish;
   }
   ...
   if (!another condition)
   {
      goto finish;
   }
   ... 
   if (!yet another condition)
   {
      goto finish;
   }
   ... 
   .... 
   if (...)
         ...    // No need to use goto here. 
 finish:
   finish up. 
}

Edit: Pour clarifier, je ne propose nullement l’utilisation de goto comme solution générale. Mais il existe des cas où goto est une meilleure solution que d’autres solutions.

Imaginons, par exemple, que nous collections des données et que les différentes conditions testées soient une sorte de "c'est la fin des données collectées" - ce qui dépend d'une sorte de marqueur "continuer/terminer" qui varie en fonction du lieu. vous êtes dans le flux de données.

Maintenant, lorsque nous avons terminé, nous devons enregistrer les données dans un fichier.

Et oui, il y a souvent d'autres solutions qui peuvent fournir une solution raisonnable, mais pas toujours.

253
Mats Petersson

Vous pouvez utiliser un motif de continuation simple avec une variable bool:

bool goOn;
if ((goOn = check0())) {
    ...
}
if (goOn && (goOn = check1())) {
    ...
}
if (goOn && (goOn = check2())) {
    ...
}
if (goOn && (goOn = check3())) {
    ...
}

Cette chaîne d'exécution s'arrête dès que checkN renvoie un false. Aucun autre appel check...() ne serait effectué en raison d'un court-circuit de l'opérateur &&. En outre, l'optimisation des compilateurs est suffisamment intelligente pour reconnaître que définir goOn sur false est une rue à sens unique et insérer le goto end Manquant pour vous. De ce fait, les performances du code ci-dessus seraient identiques à celles d'un do/while(0), mais sans porter un coup douloureux à sa lisibilité.

82
dasblinkenlight
  1. Essayez d'extraire le code dans une fonction distincte (ou peut-être plus d'une). Revenez ensuite de la fonction si la vérification échoue.

  2. Si cela est trop étroitement lié au code environnant pour le faire, et que vous ne pouvez pas trouver un moyen de réduire le couplage, regardez le code après ce bloc. Vraisemblablement, il nettoie certaines ressources utilisées par la fonction. Essayez de gérer ces ressources en utilisant un objet RAII ; remplacez ensuite chaque break douteux par return (ou throw, si cela convient mieux) et laissez le destructeur de l'objet nettoyer pour vous.

  3. Si le déroulement du programme est (nécessairement) tellement sinueux que vous avez vraiment besoin d'un goto, utilisez-le plutôt que de lui donner un déguisement bizarre.

  4. Si vous avez des règles de codage qui interdisent aveuglément goto et que vous ne pouvez vraiment pas simplifier le flux du programme, vous devrez probablement le dissimuler avec votre hack do.

37
Mike Seymour

TLDR : RAII , code transactionnel (définit uniquement les résultats ou renvoie des éléments lorsqu'il est déjà calculé) et les exceptions.

Longue réponse:

Dans [~ # ~] c [~ # ~] , la meilleure pratique pour ce type de code est d'ajouter un EXIT/CLEANUP/other étiquette dans le code, où le nettoyage des ressources locales a lieu et un code d'erreur (le cas échéant) est renvoyé. Cette pratique est recommandée car elle divise naturellement le code en initialisation, calcul, validation et retour:

error_code_type c_to_refactor(result_type *r)
{
    error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere
    some_resource r1, r2; // , ...;
    if(error_ok != (result = computation1(&r1))) // Allocates local resources
        goto cleanup;
    if(error_ok != (result = computation2(&r2))) // Allocates local resources
        goto cleanup;
    // ...

    // Commit code: all operations succeeded
    *r = computed_value_n;
cleanup:
    free_resource1(r1);
    free_resource2(r2);
    return result;
}

En C, dans la plupart des bases de code, les codes if(error_ok != ... Et goto sont généralement masqués derrière des macros pratiques (RET(computation_result), ENSURE_SUCCESS(computation_result, return_code), etc.).

C++ offre des outils supplémentaires sur [~ # ~] c [~ # ~] :

  • La fonctionnalité de bloc de nettoyage peut être implémentée en tant que RAII, ce qui signifie que vous n’avez plus besoin de tout le bloc cleanup et que le code client permet d’ajouter des instructions de retour anticipé.

  • Vous lancez chaque fois que vous ne pouvez pas continuer, transformant tous les if(error_ok != ... En appels directs.

Code C++ équivalent:

result_type cpp_code()
{
    raii_resource1 r1 = computation1();
    raii_resource2 r2 = computation2();
    // ...
    return computed_value_n;
}

C'est la meilleure pratique pour les raisons suivantes:

  • Il est explicite (c'est-à-dire que, même si le traitement des erreurs n'est pas explicite, le flux principal de l'algorithme l'est)

  • Il est facile d'écrire du code client

  • C'est minime

  • C'est simple

  • Il n'a pas de constructions de code répétitives

  • Il n'utilise pas de macros

  • Il n'utilise pas les constructions bizarres do { ... } while(0)

  • Il est réutilisable avec un minimum d’effort (c’est-à-dire que si je veux copier l’appel vers computation2(); vers une fonction différente, je n’ai pas à vérifier si j’ajoute un do { ... } while(0) dans le nouveau code, ni #define une macro d’emballage et une étiquette de nettoyage, ni rien d’autre).

36
utnapistim

J'ajoute une réponse par souci d'exhaustivité. Un certain nombre d'autres réponses ont indiqué que le grand bloc de conditions pouvait être divisé en une fonction distincte. Mais comme cela a également été souligné à plusieurs reprises, cette approche sépare le code conditionnel du contexte d'origine. C'est l'une des raisons pour lesquelles les lambdas ont été ajoutés au langage en C++ 11. L'utilisation de lambdas a été suggérée par d'autres mais aucun échantillon explicite n'a été fourni. J'ai mis un dans cette réponse. Ce qui me frappe, c’est que cela ressemble beaucoup à l’approche do { } while(0) à bien des égards - et peut-être que cela signifie que c’est toujours un goto déguisé ....

earlier operations
...
[&]()->void {

    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
}();
later operations
20
Peter R

Certainement pas la réponse, mais une réponse (par souci d'exhaustivité )

Au lieu de :

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

Vous pouvez écrire:

switch (0) {
case 0:
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

C’est toujours un goto déguisé, mais au moins ce n’est plus une boucle. Ce qui signifie que vous n'aurez pas à vérifier très attentivement qu'il n'y en a pas continue caché quelque part dans le bloc.

La construction est également assez simple pour que vous puissiez espérer que le compilateur l’optimisera.

Comme suggéré par @jamesdlin, vous pouvez même cacher cela derrière une macro comme

#define BLOC switch(0) case 0:

Et l'utiliser comme

BLOC {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

Cela est possible car la syntaxe du langage C attend une instruction après un commutateur, pas un bloc entre crochets et vous pouvez placer une étiquette de casse avant cette instruction. Jusqu'à présent, je ne voyais pas l'intérêt de permettre cela, mais dans ce cas particulier, il est pratique de cacher le commutateur derrière une macro Nice.

18
kriss

Je recommanderais une approche semblable à Mats answer moins l'inutile goto. Ne mettez que la logique conditionnelle dans la fonction. Tout code qui s'exécute toujours doit être placé avant ou après l'appel de la fonction dans l'appelant:

void main()
{
    //do stuff always
    func();
    //do other stuff always
}

void func()
{
    if (!condition)
        return;
    ...
    if (!other condition)
        return;
    ...
    if (!another condition)
        return;
    ... 
    if (!yet another condition)
        return;
    ...
}
15
Dan Bechard

Pour moi, do{...}while(0) va bien. Si vous ne voulez pas voir la do{...}while(0), vous pouvez définir des mots-clés alternatifs.

Exemple:

//--------SomeUtilities.hpp---------
#define BEGIN_TEST do{
#define END_TEST }while(0);

//--------SomeSourceFile.cpp--------
BEGIN_TEST
   if(!condition1) break;
   if(!condition2) break;
   if(!condition3) break;
   if(!condition4) break;
   if(!condition5) break;

   //processing code here

END_TEST

Je pense que le compilateur supprimera la condition non nécessaire while(0) dans do{...}while(0) dans la version binaire et convertira les sauts en saut inconditionnel. Vous pouvez vérifier que c'est la version linguistique de l'Assemblée pour en être sûr.

L'utilisation de goto produit également un code plus propre qui est simple avec la logique condition-then-jump. Vous pouvez faire ce qui suit:

{
   if(!condition1) goto end_blahblah;
   if(!condition2) goto end_blahblah;
   if(!condition3) goto end_blahblah;
   if(!condition4) goto end_blahblah;
   if(!condition5) goto end_blahblah;

   //processing code here

 }end_blah_blah:;  //use appropriate label here to describe...
                   //  ...the whole code inside the block.

Notez que l'étiquette est placée après la fermeture }. C’est l’un des problèmes possibles dans goto qui consiste à placer accidentellement un code entre les deux parce que vous n’avez pas vu l’étiquette. C'est maintenant comme do{...}while(0) sans code de condition.

Pour rendre ce code plus propre et plus compréhensible, vous pouvez le faire:

//--------SomeUtilities.hpp---------
#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

//--------SomeSourceFile.cpp--------
BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);
   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);
   if(!condition5) FAILED(NormalizeData);

END_TEST(NormalizeData)

Avec cela, vous pouvez faire des blocs imbriqués et spécifier où vous voulez sortir/sortir.

//--------SomeUtilities.hpp---------
#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

//--------SomeSourceFile.cpp--------
BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);

   BEGIN_TEST
      if(!conditionAA) FAILED(DecryptBlah);
      if(!conditionBB) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionCC) FAILED(DecryptBlah);

      // --We can now decrypt and do other stuffs.

   END_TEST(DecryptBlah)

   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);

   // --other code here

   BEGIN_TEST
      if(!conditionA) FAILED(TrimSpaces);
      if(!conditionB) FAILED(TrimSpaces);
      if(!conditionC) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionD) FAILED(TrimSpaces);

      // --We can now trim completely or do other stuffs.

   END_TEST(TrimSpaces)

   // --Other code here...

   if(!condition5) FAILED(NormalizeData);

   //Ok, we got here. We can now process what we need to process.

END_TEST(NormalizeData)

Le code spaghetti n'est pas la faute de goto, c'est la faute du programmeur. Vous pouvez toujours produire du code spaghetti sans utiliser goto.

12
acegs

Le flux de code lui-même est déjà une odeur de code qui affecte beaucoup la fonction. S'il n'y a pas de solution directe à cela (la fonction est une fonction de vérification générale), utilisez RAII pour pouvoir revenir au lieu de sauter à la fin. -section de la fonction pourrait être mieux.

12
stefaanv

Si vous n'avez pas besoin d'introduire des variables locales lors de l'exécution, vous pouvez souvent aplatir ceci:

if (check()) {
  doStuff();
}  
if (stillOk()) {
  doMoreStuff();
}
if (amIStillReallyOk()) {
  doEvenMore();
}

// edit 
doThingsAtEndAndReportErrorStatus()
11
the_mandrill

Semblable à la réponse de dasblinkenlight, mais évite l’affectation dans le if qui pourrait être "réparée" par un relecteur de code:

bool goOn = check0();
if (goOn) {
    ...
    goOn = check1();
}
if (goOn) {
    ...
    goOn = check2();
}
if (goOn) {
    ...
}

...

J'utilise ce modèle lorsque les résultats d'une étape doivent être vérifiés avant l'étape suivante, ce qui diffère de la situation dans laquelle toutes les vérifications pourraient être effectuées avec un grand modèle de type if( check1() && check2()....

10
Denise Skidmore

Utilisez des exceptions. Votre code aura l'air beaucoup plus propre (et des exceptions ont été créées exactement pour gérer les erreurs dans le flux d'exécution d'un programme). Pour nettoyer les ressources (descripteurs de fichier, connexions à la base de données, etc.), lisez l'article Pourquoi C++ ne fournit-il pas une construction "finally"?.

#include <iostream>
#include <stdexcept>   // For exception, runtime_error, out_of_range

int main () {
    try {
        if (!condition)
            throw std::runtime_error("nope.");
        ...
        if (!other condition)
            throw std::runtime_error("nope again.");
        ...
        if (!another condition)
            throw std::runtime_error("told you.");
        ...
        if (!yet another condition)
            throw std::runtime_error("OK, just forget it...");
    }
    catch (std::runtime_error &e) {
        std::cout << e.what() << std::endl;
    }
    catch (...) {
        std::cout << "Caught an unknown exception\n";
    }
    return 0;
}
9
Cartucho

C'est un problème bien connu et bien résolu du point de vue de la programmation fonctionnelle - peut-être la monade.

En réponse au commentaire que j'ai reçu ci-dessous, j'ai modifié mon introduction ici: Vous pouvez trouver tous les détails sur la mise en œuvre de C++ monades dans divers endroits qui vous permettront de réaliser ce que suggère Rotsor. . Il faut un peu de temps pour parler de monades, alors je vais plutôt suggérer ici un mécanisme rapide semblable à une monade pour les "pauvres-hommes", pour lequel vous ne devez rien savoir de plus que boost: facultatif.

Configurez vos étapes de calcul comme suit:

boost::optional<EnabledContext> enabled(boost::optional<Context> context);
boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);

Chaque étape de calcul peut évidemment faire quelque chose comme retour boost::none si l’option qui lui a été donnée est vide. Donc par exemple:

struct Context { std::string coordinates_filename; /* ... */ };

struct EnabledContext { int x; int y; int z; /* ... */ };

boost::optional<EnabledContext> enabled(boost::optional<Context> c) {
   if (!c) return boost::none; // this line becomes implicit if going the whole hog with monads
   if (!exists((*c).coordinates_filename)) return boost::none; // return none when any error is encountered.
   EnabledContext ec;
   std::ifstream file_in((*c).coordinates_filename.c_str());
   file_in >> ec.x >> ec.y >> ec.z;
   return boost::optional<EnabledContext>(ec); // All ok. Return non-empty value.
}

Puis enchaînez-les ensemble:

Context context("planet_surface.txt", ...); // Close over all needed bits and pieces

boost::optional<EnergisedContext> result(energised(enabled(context)));
if (result) { // A single level "if" statement
    // do work on *result
} else {
    // error
}

La bonne chose à ce sujet est que vous pouvez écrire des tests unitaires clairement définis pour chaque étape de calcul. De plus, l'invocation se lit comme un anglais ordinaire (comme c'est généralement le cas avec un style fonctionnel).

Si vous ne vous souciez pas de l'immuabilité et qu'il est plus pratique de renvoyer le même objet à chaque fois, vous pouvez créer une variation en utilisant shared_ptr ou similaire.

8
Benedict

Que diriez-vous de déplacer les instructions if dans une fonction supplémentaire donnant un résultat numérique ou enum?

int ConditionCode (void) {
   if (condition1)
      return 1;
   if (condition2)
      return 2;
   ...
   return 0;
}


void MyFunc (void) {
   switch (ConditionCode ()) {
      case 1:
         ...
         break;

      case 2:
         ...
         break;

      ...

      default:
         ...
         break;
   }
}
7
karx11erx

Quelque chose comme ça peut-être

#define EVER ;;

for(EVER)
{
    if(!check()) break;
}

ou utiliser des exceptions

try
{
    for(;;)
        if(!check()) throw 1;
}
catch()
{
}

En utilisant des exceptions, vous pouvez également transmettre des données.

5
liftarn

Je ne suis pas particulièrement habitué à utiliser break ou return dans un tel cas. Étant donné que normalement, dans une telle situation, il s’agit généralement d’une méthode relativement longue.

Si nous avons plusieurs points de sortie, cela peut poser des problèmes lorsque nous voulons savoir ce qui provoquera l’exécution de certaines logiques: Normalement, nous continuons simplement à monter des blocs qui renferment cette logique, et les critères de ceux-ci nous indiquent la situation:

Par exemple,

if (conditionA) {
    ....
    if (conditionB) {
        ....
        if (conditionC) {
            myLogic();
        }
    }
}

En regardant les blocs englobants, il est facile de savoir que myLogic() ne se produit que lorsque conditionA and conditionB and conditionC Est vrai.

Cela devient beaucoup moins visible lorsqu'il y a des retours anticipés:

if (conditionA) {
    ....
    if (!conditionB) {
        return;
    }
    if (!conditionD) {
        return;
    }
    if (conditionC) {
        myLogic();
    }
}

Nous ne pouvons plus naviguer à partir de myLogic(), en regardant le bloc englobant pour déterminer la condition.

Il existe différentes solutions de contournement que j'ai utilisées. Voici l'un d'entre eux:

if (conditionA) {
    isA = true;
    ....
}

if (isA && conditionB) {
    isB = true;
    ...
}

if (isB && conditionC) {
    isC = true;
    myLogic();
}

(Bien sûr, il est recommandé d’utiliser la même variable pour remplacer tout isA isB isC.)

Une telle approche donnera au moins au lecteur de code que myLogic() est exécuté lorsque isB && conditionC. Le lecteur est informé qu'il a besoin de rechercher plus avant ce qui causera isB d'être vrai.

5
Adrian Shum
typedef bool (*Checker)();

Checker * checkers[]={
 &checker0,&checker1,.....,&checkerN,NULL
};

bool checker1(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

bool checker2(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

......

void doCheck(){
  Checker ** checker = checkers;
  while( *checker && (*checker)())
    checker++;
}

Comment sur cela?

3
sniperbat

Je ne suis pas un programmeur C++ , je ne vais donc pas écrire de code ici, mais personne n'a encore mentionné de solution orientée objet. Donc, voici ce que je suppose:

Avoir une interface générique qui fournit une méthode pour évaluer une seule condition. Vous pouvez maintenant utiliser une liste d'implémentations de ces conditions dans votre objet contenant la méthode en question. Vous parcourez la liste et évaluez chaque condition, éventuellement en cas de défaillance précoce.

La bonne chose est qu’une telle conception colle très bien au principe ouvert/fermé , car vous pouvez facilement ajouter de nouvelles conditions lors de l’initialisation de l’objet contenant la méthode en question. Vous pouvez même ajouter une deuxième méthode à l'interface avec la méthode d'évaluation de condition renvoyant une description de la condition. Ceci peut être utilisé pour des systèmes auto-documentés.

L'inconvénient, cependant, est qu'il y a un peu plus de temps système impliqué à cause de l'utilisation d'un plus grand nombre d'objets et de l'itération sur la liste.

2
SpaceTrucker

Un autre modèle utile si vous avez besoin d'étapes de nettoyage différentes en fonction de l'emplacement de l'échec:

    private ResultCode DoEverything()
    {
        ResultCode processResult = ResultCode.FAILURE;
        if (DoStep1() != ResultCode.SUCCESSFUL)
        {
            Step1FailureCleanup();
        }
        else if (DoStep2() != ResultCode.SUCCESSFUL)
        {
            Step2FailureCleanup();
            processResult = ResultCode.SPECIFIC_FAILURE;
        }
        else if (DoStep3() != ResultCode.SUCCESSFUL)
        {
            Step3FailureCleanup();
        }
        ...
        else
        {
            processResult = ResultCode.SUCCESSFUL;
        }
        return processResult;
    }
2
Denise Skidmore

C'est comme ça que je le fais.

void func() {
  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...
}
1
Vadoff

Tout d'abord, un court exemple pour montrer pourquoi goto n'est pas une bonne solution pour C++:

struct Bar {
    Bar();
};

extern bool check();

void foo()
{
    if (!check())
       goto out;

    Bar x;

    out:
}

Essayez de compiler ceci dans un fichier objet et voyez ce qui se passe. Ensuite, essayez l'équivalent do + break + while(0).

C'était un aparté. Le point principal suit.

Ces petits morceaux de code nécessitent souvent une sorte de nettoyage en cas d'échec de la fonction. Ces nettoyages veulent généralement se dérouler dans l’ordre opposé à partir des morceaux eux-mêmes, au fur et à mesure que vous "déroulez" le calcul partiellement terminé.

Une option pour obtenir cette sémantique est RAII ; voir la réponse de @ utnapistim. C++ garantit que les destructeurs automatiques fonctionnent dans l'ordre inverse des constructeurs, ce qui fournit naturellement un "déroulement".

Mais cela nécessite beaucoup de classes RAII. Parfois, une option plus simple consiste simplement à utiliser la pile:

bool calc1()
{
    if (!check())
        return false;

    // ... Do stuff1 here ...

    if (!calc2()) {
        // ... Undo stuff1 here ...
        return false;
    }

    return true;
}

bool calc2()
{
    if (!check())
        return false;

    // ... Do stuff2 here ...

    if (!calc3()) {
        // ... Undo stuff2 here ...
        return false;
    }

    return true;
}

...etc. Ceci est facile à auditer, car il place le code "undo" à côté du code "do". L'audit facile est bon. Cela rend également le flux de contrôle très clair. C'est un motif utile pour C aussi.

Cela peut nécessiter que les fonctions calc prennent beaucoup d'arguments, mais ce n'est généralement pas un problème si vos classes/structures ont une bonne cohésion. (C’est-à-dire que les éléments qui appartiennent à l’ensemble résident dans un seul objet. Ces fonctions peuvent donc prendre des pointeurs ou des références sur un petit nombre d’objets tout en effectuant de nombreuses tâches utiles.)

1
Nemo

Si votre code a un long bloc d'instructions if..else if..else, vous pouvez essayer de réécrire le bloc entier à l'aide de Functors ou function pointers. Ce n'est peut-être pas toujours la bonne solution, mais c'est souvent le cas.

http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html

0
HAL

Si vous utilisez le même gestionnaire d'erreurs pour toutes les erreurs et que chaque étape renvoie une valeur booléenne indiquant le succès:

if(
    DoSomething() &&
    DoSomethingElse() &&
    DoAThirdThing() )
{
    // do good condition action
}
else
{
    // handle error
}

(Semblable à la réponse de tyzoid, mais les conditions sont les actions et le && empêche que des actions supplémentaires ne se produisent après le premier échec.)

0
Denise Skidmore

Pourquoi la méthode de repérage n'a-t-elle pas été répondue est-elle utilisée depuis des lustres.

//you can use something like this (pseudocode)
long var = 0;
if(condition)  flag a bit in var
if(condition)  flag another bit in var
if(condition)  flag another bit in var
............
if(var == certain number) {
Do the required task
}
0
Fennekin

Consolidez-le dans une instruction if:

if(
    condition
    && other_condition
    && another_condition
    && yet_another_condition
    && ...
) {
        if (final_cond){
            //Do stuff
        } else {
            //Do other stuff
        }
}

C'est le modèle utilisé dans des langages tels que Java où le mot-clé goto a été supprimé.

0
Tyzoid

Je suis surpris par le nombre de réponses différentes présentées ici. Mais finalement, dans le code que je dois changer (c’est-à-dire supprimer ce hack do-while(0) ou autre), j’ai fait quelque chose de différent de l’une des réponses mentionnées ici et je ne comprends pas pourquoi personne ne le pensait. Voici ce que j'ai fait:

Code initial:

do {

    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

finishingUpStuff.

À présent:

finish(params)
{
  ...
  ...
}

if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...

Donc, ce qui a été fait ici, c'est que les éléments de finition ont été isolés dans une fonction et que les choses sont soudainement devenues si simples et propres!

Je pensais que cette solution méritait d'être mentionnée, donc fournie ici.

0
Sankalp