Bonjour
En terme de bonnes pratiques, est ce que c'est mal d'utiliser le mot clé continue dans un code c++ ? J'ai un cas ou ça me permettrait de bien réduire la complexité cyclomatique, qui rend vite le code illisible dans certains cas.
Version imprimable
Bonjour
En terme de bonnes pratiques, est ce que c'est mal d'utiliser le mot clé continue dans un code c++ ? J'ai un cas ou ça me permettrait de bien réduire la complexité cyclomatique, qui rend vite le code illisible dans certains cas.
Pas de soucis pour utiliser "continue" ou "break".
Par contre "goto"...
Avant de l'utiliser, j'aurais tendance à regarder à deux fois si un refactoring ne me permettrait pas de l'éviter (par exemple, en mettant le corps de la boucle dans une fonction) de manière agréable, sinon, je n'aurais pas plus d'états d'âme que ça à le faire.
Salut,
Ce n'est qu'un ressenti personnel, et donc fatalement sujet à débat, mais, pour ma part, je suis tout relativement tout aussi allergique à l'utilisation de continue qu'à celle de break lorsque je ne suis pas dans une structure logique switch ... case.
Maintenant, je vais m'empresser d'ajouter une précision, avant que certains ne se mettent à crier "au scandale" :D
Je n'aime pas l'instruction continue, pas plus que je n'aime break en dehors des switch...case ou l'instruction goto.
Cela ne veut absolument pas dire que je veuille en arriver à en interdire l'utilisation :P. J'ai beau être (actuellement :aie:) responsable de rubrique, qui serais-je pour essayer d'imposer cette vision des choses :D
Généralement, ma politique tient principalement sur deux aspects:
S'il est possible (et "seulement si") d'assurer la facilité de relecture du code en s'en passant sans pour autant nuire à la facilité de mise en oeuvre et de programmation, alors, le choix est fait: j'évite les continue :D
- la facilité de relecture de code
- la simplicité de programmation et de mise en oeuvre
S'il devient trop difficile d'assurer la simplicité de mise en oeuvre et de programmation en s'en passant, et que le code reste, malgré tout "suffisamment" (c'est un curseur à placer... où bon te semble :D) facile à relire, il semble opportun de faire "contre mauvaise fortune, bon coeur" et d'accepter de déroger à la règle "générale" qui conseille de les éviter.
Ceci dit, il est *peut-être* également intéressant de se poser la question de savoir si ta fonction n'en fait pas un peu trop, et d'envisager de la factoriser de manière plus efficace, ce qui *pourrait peut-être* te permettre d'éviter un recours trop intense à continue, en plus de te faciliter la tâche en terme de recherche d'erreurs.
Je suis désolé de donner une réponse aussi "générique" à ta question, mais il me semble impossible d'être plus précis, chaque situation devant être évaluée individuellement ;)
hope it helps ;)
[EDIT]Grillé... ca m'apprendra à vouloir faire des discours :aie:
J'ai la même réaction que Loic.
Car, continue et break, c'est comme return, c'est une vieille histoire de SESE (Single Entry, Single Exit) que l'on se traine dans l'inconscient collectif depuis le C.
Le SESE, cela sert d'abord à assurer une libération déterministe de ressources. Hors, pour cela, le C++ dispose du RAII (à contrario du C, du Pascal et autres langages pré-exceptions).
Certains estiment que le SESE est nécessaire à rendre une fonction compréhensible. Foutaises! Si la fonction n'est pas compréhensible à cause d'une malheureuse interruption, c'est qu'elle est trop complexe. Qu'on la refactorise d'abord pour en extraire autant de parties que nécessaire (cf ce qu'à dit Loic). Pour une bonne compréhension et une bonne maintenabilité, c'est ça qui est important.
Et bien merci pour ces réponses détaillées !
Ho ce n'est pas tant qu'elle n'est pas compréhensible, mais ça rajoute des tas de ifs qui s'imbriquent les uns les autres et ça fera une mauvaise note de mon code au check cyclomatique ^^
Non ce n'est pas indispensable, et d'ailleurs je vais m'en passer, mais la question m'intéressait au delà même de mon bout de code.
il y a quand même quelques très mauvais cas d'utilisations
qui est un vilain goto ecrit de manière un peu pompeuse. (break peut aussi etre remplacé par continue)Code:
1
2
3
4
5
6
7 do { if(blablabla) break; /* plein de trucs */ if(dobidouwa) break; /* plein de trucs */ } while(0);
Ce qui est vilain, c'est /* plein de trucs */
Et contrairement à goto, on ne peut aller qu'à un seul endroit. Nous sommes loin de l'article "goto considered harmful" qui date d'une époque où il y pouvait y avoir des entrecroisements de spaghettis.
avoir un "do while" qui ne boucle pas c'est méchamment difficile a relire.
OK. J'avais manqué le 0. Des fois des gens ont des idées bizarres.
Roh mais arreter ce délit de facies avec le goto c'est bien !
C'est meme tellement bien que certains ont inventé le goto++ !
http://www.gotopp.org/presentation.html.fr
Au passage on peut admirer ses caractéristiques :
Que ce language permet de faire des troupeaux de pinguouin anonymes.Citation:
* C'est le meilleur langage de programmation au monde.
* Et même mieux, c'est le meilleur langage de programmation de l'univers.
* Il a une syntaxe claire et accessible.
* La possibilité de faire des GOTO.
* Manipulation des références (équivalent des pointeurs) pour faire plus de bugs.
* Multitâche très simple : on utilise un GOTOUNIVERSPARALLELEouizzz à la place d'un GOTO normal et le flux d'exécution du programme se sépare en deux.
* Objet : héritage, propriétés et méthodes partagées, accès aux membres par indice...
* Modulaire : grâce à une interface très simple, il est possible de programmer des modules en C++. Le GOTO++ propose entre autres un module réseau très facile à utiliser (en combinaison avec les caractéristiques multitâches du langage).
* Expressions régulières.
* Tableaux, tables de hachage...
* Des GOTO ! Plein de GOTO !
* Et bien d'autres choses !
De faire la merveilleuse commande gotochezpasou (oui oui un goto a atterrissage random si c'est pas merveilleux !)
Non bref le goto c'est bien mangez en !
Allez j'en remet une couche :
Citation:
Le GOTO++ (ce fabuleux langage) est le fruit d'un travail considérable de réflexions profondes sur le sens de la vie, la nature du monde qui nous entoure, l'utilité des coléoptères à pattes articulées dans notre écosystème et moultes choses non moins importantes. Cependant, son élaboration a été avant tout motivée par la nécessité de compter les suicides de pingouins dans les regions méridionales de l'Antarctique sauvage. Pour parler plus concrètement, le GOTO++ est basé sur des règles non conventionelles. Il n'y a pas d'opérations logiques en GOTO++, bien qu'on puisse s'y ramener par des artifices complexes. L'instruction de base est le GOTO, qui ne fait absolument rien. Ensuite vient le GOTOGOTO qui permet de se rendre en un autre point du programme (un goto classique me direz-vous). Toutefois, le GOTOGOTO requiert, en plus du label, un pourcentage de réussite qui va permettre de calculer la probabilité pour que le GOTOGOTO vous envoie vraiment à cette étiquette. Là, normalement, votre esprit vient de s'illuminer, vous avez atteint la libération du cycle du samsara et vous réalisez avec enthousiasme les innombrables possibilités qui sont désormais à votre disposition. Alors n'hésitez plus, rejoignez les millions de programmeurs qui ont déjà choisi le GOTO++ comme langage de prédilection !
Voici, en guise d'exemple, le fameux « Hello World! » écrit en GOTO++. Notez l'élégance du code et le fait que notre boucle n'est pas infinie mais se répète un nombre aléatoire de fois.
Ce message etait sponsorisé par la société des informaticiens trollsCode:
1
2
3
4 §1 GOTOPRINTDUTEXTE() ; «Hello World !» GOTO qui sert a rien GOTOGOTO() *(1)
ou pas ??? (j'ai toujours pas compris l'intérêt mais bon)
http://www.developpez.net/forums/d35...pp/define-gcc/
Le "try" est un magnifique goto. En fait c'est un goto qui ne se dit pas, mais il peut avoir lieu n'importe où, n'importe quand. Il suffit d'épousseter quelques labels (pardon, des catch) ça et là pour avoir, peut-être, une chance de constater qu'un goto a fait son oeuvre quelque part.
:zoubi:
Il y a certe interruption du flux normal mais soit tu attrapes l' exception ici et tu la traites soit tu remontes la pile d'appel pour tenter de trouver un bloc qui peut la traiter..
On ne peut pas parler de goto (qui lui envoie potentiellement n'importe où dans la portée courante).
personnellement je ne suis pas fan de la programmation par exception mais je pense qu'il s'agit d'une autre débat. les goto et continue j'arrive aussi a m'en passer.
Je n'utilise les try/catch que lorsque cela s'avère nécessaire c'est à dire pour gérer des cas exceptionnel comme par exemple l'appel de fonctions externe bas niveau non maitrisées (dans le sens boite noire) et qui peuvent en lancer par exemple.
dans l'absolu j'arrive toujours a me passer des exception des l'instant ou je n'ai pas a m'interfacer avec des fonctions pouvant en lever.
Tu es en train de nous dire que tu n'utilises (en C++) jamais des choses comme new, ou les conteneurs standards.
oO... Sa fait pas gras :')Citation:
ou je n'ai pas a m'interfacer avec des fonctions pouvant en lever.
ça tue pas les exceptions :)
je ne dit pas que cela tue, je dit simplement qu'il ne faut pas en abuser.
Et c'est justement le but de la programmation par exception (qui était à la mode il n'y a pas si longtemps).....
après j'ai peu être mal choisi mes mots, il y'a aussi des endroits ou tu n'as pas le choix (pour les exception).
Je crois quand même qu'il ne faut pas tout mélanger...
D'un coté il y a le goto, qui *peut exceptionnellement* présenter certains intérêts, mais dans des situations tout à fait particulières.
En toute honnêteté, je n'ai jamais du y avoir recours, et je ne m'en porte pas plus mal: si on ne reste pas bloqué sur le principe (qui est devenu totalement obsolète) du SESE déjà évoqué.
Si on prend cinq minutes de réflexion au lieu de se jeter directement sur son clavier et de se mettre à "vomir" du code, on arrive parfaitement à s'en passer, et c'est tout bénef pour la lisibilité du code.
A l'extrême opposé, on a le système d'exception. Mais on est donc tenté de s'intéresser à ce qu'est une exception.
Ta vision qui essaye de dire qu'une exception doit être... exceptionnelle est finalement fort restrictive: quand on regarde les différentes exceptions lancées par la STL, on remarque en effet que s'il y a deux points communs entre toutes les exceptions, c'est:
- effectivement des événements qui ne se produisent (par chance) que rarement ou dont on peut dire que "ils peuvent arriver"
- des problèmes dont la source et, par la même occasion, les opportunités d'y apporter éventuellement une solution, se trouve généralement (largement) en amont:
- Si new échoue, ce sera le plus souvent parce que... trop de mémoire est déjà allouée dynamiquement
- Si l'ouverture (enfin, si l'accès à un fichier réputé ouvert) d'un fichier échoue, c'est sans doute parce qu'il a été "verrouillé" par le système par ailleurs (souvent en dehors de l'application, d'ailleurs :P), ou parce que l'on a commis une erreur lorsqu'il a été question d'évaluer le nom (et le chemin d'accès) du fichier.
- Si la fonction at de la classe vector échoue, c'est, typiquement, parce que l'on a mal évalué le nombre d'éléments
- Si un transtypage (static_cast ou dynamic_cast) devant fournir une référence écouhe, c'est, typiquement, parce que l'on a mal identifié le type réel de l'objet référencé.
Le but d'un bloc try... catch est en réalité double:
Il ne faut pas non plus oublier qu'une exception transporte avec elle une information de contexte des plus utiles: les différentes événements cités plus haut lancent tous une exception différente (même si elles ont toutes une base commune), ce qui permet, lorsqu'on la récupère (parfois bien haut par rapport à l'endroit où l'événement s'est produit), d'apporter la meilleure réponse possible au problème.
- Il nous donne la possibilité de remettre le système dans un état cohérent si un événement "qui n'aurait pas du se produire" s'est produit, par exemple, en annulant des modifications qui n'avaient de sens que parce que l'on espérait que l'événement incriminé ne se produise pas.
- Il nous donne l'occasion de remonter à la source du problème, afin d'éviter qu'il ne se reproduise par la suite (s'il est possible d'y trouver une solution, du moins)
Dés lors, s'il est, effectivement, aberrant de vouloir placer du try... catch partout où une exception risque d'être lancée (un simple vector.add(value) peut en lancer une, vu que add risque d'appeler new, qui peut lancer une exception :aie::D), il est tout aussi aberrant de vouloir comparer try... catch à un simple goto ;)
Mais cela nous écarte du problème du PO... Et il existe déjà des débats relatifs aux avantages du goto et, je crois, des exceptions.
Je proposerais donc de recentrer le débat sur sa question existentielle qui concerne le continue.
Je l'ai déjà dit, je n'ai jamais eu à me servir d'un break en dehors d'un switch, case et je n'ai jamais eu besoin de me servir de continue.
Il serait bien sur présomptueux de ma part de dire que c'est peut être parce que je suis un bon programmeur, mais je constate que les circonstances m'ont toujours permis de m'en passer ;)
Et je ne peux m'empêcher de rappeler encore une fois qu'aucune technique de programmation ne peut être considérée comme la solution ultime à tous les problèmes, et que son utilité doit impérativement être évaluée en fonction des circonstances auxquelles nous sommes confrontés.
L'instruction continue ne fait pas exception: S'il est vrai que, très souvent, son besoin est de nature à au minimum tirer une sonnette d'alarme qui devrait t'inciter à réfléchir sur l'opportunité d'un refactoring et / ou de la factorisation de ton code, il y a des cas où il devient impossible de s'en passer sans provoquer une complexification nuisible de la logique ou du code.
Dans ce genre de cas, son utilisation est, non seulement justifiée, mais aussi sans doute très largement préférable à toute autre solution ;)
Personellement, je me reposer la question initiale a chaque fois que je tombe sur un cas où je doute.
Ce qui fait que je fais du "cas par cas". Donc parfois je l'utilise.
Il m'arrive souvent de faire des fonctions de recherche sous cette forme (pseudocode) :
C'est pas des continue mais c'est le même genre de questionnement non?Code:
1
2
3
4
5
6
7
8 fonction findMachin( string name ) return Machin for each machin in machins if machin.name == name return machin return null end
Pour les continue, il m'arrive d'en faire quand j'ai besoin de parcourir les éléments d'un conteneur pour faire plusieurs tests de "validation" avant d'effectuer une action dessu :
Code:
1
2
3
4
5
6
7
8
9
10
11
12 fonction validation() for each machin in machins if machin.name == "" || !isConditionA( machin ) // etc... continue //toutes les conditions sont passées action( machin ) end
Mais souvent quand le code prends plusieurs lignes ou aparait comme utilisable ailleurs, je factorise :
Code:
1
2
3
4
5
6
7
8
9
10 fonction validation() for each machin in machins if checkAllConditions( machin ) action( machin ) end function checkAllConditions( Machin machin ) return bool //...
Du coup, beaucoup de continue pourraient être remplacés par une fonction avec un retour booleen.
Ca m'arrive quand même d'en utiliser mais seulement quand je n'ai pas pris le temps de refactorer une fonction ou bien quand le code reste suffisamment simple pour rester lisible même apres l'ajout du continue.
Salut,
Pour moi, c'est pas tout à fait la même chose en terme de lecture de code. Un return, tu vois bien que tu sorts directement de la fonction.
Les break/continue que j'ai vu c'était plutôt comme screetch les décrit :
Et comme le dit Luc, le problème c'est /* plein de trucs */.Code:
1
2
3
4
5
6 { if(blablabla) break; /* plein de trucs */ if(dobidouwa) break; /* plein de trucs */ }
Cependant, j'ai l'impression que si on n'avait pas /* plein de trucs */, on n'aurait pas besoin de break/continue.
Je me sers de continue de temps en temps (12 occurrences sur 40 000 lignes de code, c'est pas énorme non plus).
La plupart du temps, c'est pour changer :
... en :Code:
1
2
3
4
5
6
7
8
9
10
11
12 for (...) { // Faire deux trois choses (ou rien du tout) if (caVaMal) { // Traiter le cas qui ne fonctionne pas } else { // Poursuivre le traitement } }
Je trouve que ça améliore la lisibilité (on vois tout de suite avec continue que le traitement est abandonné pour cette itération), et ça évite d'avoir trop d'indentation...Code:
1
2
3
4
5
6
7
8
9
10
11 for (...) { // Faire deux trois choses (ou rien du tout) if (caVaMal) { // Traiter le cas qui ne fonctionne pas continue; } // Poursuivre le traitement }
D'autre fois c'est simplement une question d'algorithme. Par exemple en détection de collision entre deux objets 3D, on essaye de faire un minimum de traitement pour chaque triangle du maillage. On passe donc à l'itération suivante dès qu'on est sûr que le triangle qu'on étudie n'entrera pas en collision, d'où l'emploi de continue.
Honnêtement, si vous savez lire un return ailleurs qu'en fin de fonction, alors je ne vois pas de problème à mettre des break et des continue partout :) (bon, avec modération comme toutes les bonnes choses). C'est la même gymnastique intellectuelle.