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!
Il est considéré comme une pratique acceptable d’isoler ces décisions dans une fonction et d’utiliser return
s au lieu de break
s. 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
}
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.
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é.
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.
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.
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.
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
.
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).
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
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.
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;
...
}
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
.
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.
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()
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()...
.
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;
}
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.
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;
}
}
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.
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.
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?
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.
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;
}
C'est comme ça que je le fais.
void func() {
if (!check()) return;
...
...
if (!check()) return;
...
...
if (!check()) return;
...
...
}
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.)
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
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.)
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
}
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é.
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.