Les pratiques de programmation `break` et` continue` sont-elles mauvaises?

191

Mon patron continue de mentionner avec nonchalance que les mauvais programmeurs utilisent breaket continueen boucle.

Je les utilise tout le temps parce qu'ils ont un sens; laissez-moi vous montrer l'inspiration:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

Le point ici est que la fonction vérifie d’abord que les conditions sont correctes, puis exécute la fonctionnalité réelle. Même chose avec les boucles IMO:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Je pense que cela facilite la lecture et le débogage; mais je ne vois pas non plus d'inconvénient.

Mikhail
la source
2
Il ne faut pas grand chose pour oublier lequel fait quoi.
57
Non, ni est goto. Savoir quand les utiliser est la clé. Ce sont des outils dans la boîte à outils. Vous les utilisez quand ils fournissent un code clair et succinct.
Orj
67
Je ne peux pas exprimer mon soutien pour ce style de codage assez fort. Plusieurs niveaux de conditionnels imbriqués sont tellement pires que cette approche. Habituellement, je ne suis pas un militant pour le style de codage, mais c'est pour moi une rupture décisive.
Emil H
9
Évidemment, votre patron n’écrit pas (assez) de code. S'il le faisait, il saurait que tous les mots clés (oui, même goto) sont utiles dans certains cas.
Sakisk
57
Les mauvais programmeurs utilisent break et continue ne signifie pas que les bons programmeurs ne le font pas. Les mauvais programmeurs utilisent si et aussi bien.
mouviciel

Réponses:

240

Lorsqu'elles sont utilisées au début d'un bloc, lors des premières vérifications, elles agissent comme des conditions préalables, alors c'est bien.

Lorsqu'ils sont utilisés au milieu du bloc, avec du code autour, ils agissent comme des pièges cachés, alors c'est mauvais.

Klaim
la source
6
@Klaim: On peut faire valoir que toute routine comportant plusieurs points de sortie est une routine mal pondérée. Une routine correctement pondérée devrait faire une chose et une seule chose.
peu-twiddler
79
@ bit-twiddler: c'est un état d'esprit très C-ish. Une variable temporaire peut être modifiée ultérieurement, de sorte qu'une seule faute de frappe à 20 lignes d'ici puisse effacer ce résultat soigneusement élaboré. Un retour immédiat (ou une pause, ou continuer) est cependant extrêmement clair: je peux arrêter de lire maintenant parce que je sais qu'il ne peut éventuellement pas être modifié plus bas . C'est bon pour mon petit cerveau, ça facilite vraiment le code.
Matthieu M.
15
@ Matthieu je suis d'accord. Quittez un bloc lorsque vous recevez un résultat qui répond à l'objectif du bloc.
Evan Plaice
8
@ bit-twiddler - le principe du point de sortie unique est distinct du principe de la responsabilité unique. Je ne suis pas d'accord pour dire que la vérification est toujours distincte de la responsabilité unique qui incombe à WRT. En fait, "responsabilité unique" me frappe toujours comme un terme subjectif. Par exemple, dans un solveur quadratique, le calcul du discriminant devrait-il être une responsabilité distincte? Ou la formule quadratique dans son ensemble peut-elle être une responsabilité unique? Je dirais que cela dépend de la question de savoir si vous avez un usage distinct du discriminant - sinon, le traiter comme une responsabilité distincte est probablement excessif.
Steve314
10
Cette réponse est une règle de base, pas une règle stricte. Cela fonctionne dans la plupart des cas, n'hésitez pas à le casser si cela a du sens dans votre contexte.
Klaim
87

Vous pouvez lire l'article de 1974 de Donald Knuth intitulé Structured Programming (Programmation structurée) sous go to Statements ( Déclarations) , dans lequel il décrit diverses utilisations de la go tostructure qui sont souhaitables sur le plan structurel. Ils incluent l’équivalent de breaket des continuedéclarations (de nombreuses utilisations de go toin ont été développées dans des constructions plus limitées). Votre patron est-il du genre à appeler Knuth un mauvais programmeur?

(Les exemples donnés me intéressent. En règle générale, breaket ne continuesont pas appréciées par les gens qui aiment une entrée et une sortie de tout morceau de code, et ce genre de personne fronce les sourcils aussi sur plusieurs returndéclarations.)

David Thornley
la source
8
La plupart des personnes qui aiment les fonctions et les procédures pour avoir des points d'entrée et de sortie uniques ont grandi sur Pascal. Pascal n’était pas la première langue que j’ai apprise, mais cela a eu un impact profond sur la structure du code à ce jour. Les gens disent toujours à quel point il est facile de lire mon code Java. C'est parce que j'évite les points de sortie multiples ainsi que les déclarations mélangées avec le code. Je fais de mon mieux pour déclarer chaque variable locale utilisée dans une méthode en haut de la méthode. Cette pratique évite les erreurs de code en m'obligeant à garder des méthodes succinctes.
peu-twiddler
5
Pascal avait aussi des fonctions imbriquées. Il suffit de dire ...
Shog9
6
De ce que je me souviens, à l'époque, la principale raison pour laquelle les gens n'aimaient pas les instructions de retour multiples dans les fonctions était parce que les débogueurs ne les traitaient pas correctement. Il était très pénible de définir un point d'arrêt à la fin d'une fonction, mais vous ne l'avez jamais touché en raison d'une instruction return précédente. Ce n'est plus un problème pour chaque compilateur que j'utilise aujourd'hui.
Dunk
28
@ bit-twiddler: Je n'en suis pas si sûr. J'utilise encore Pascal aujourd'hui, et je considère généralement la "sortie à entrée unique à une seule entrée", ou du moins la partie à une sortie unique, comme une programmation culte du fret. Je considère seulement Break, Continueet Exitcomme des outils dans ma boîte à outils; Je les utilise là où cela rend le code plus facile à suivre, mais pas là où cela rendrait les choses plus difficiles à lire.
Mason Wheeler
2
@ bit-twiddler: amen à ça. J'ajouterai également qu'une fois que vous avez trouvé des blocs qui tiennent facilement à l'écran, les points de sortie multiples deviennent beaucoup moins gênants.
Shog9
44

Je ne crois pas qu'ils sont mauvais. L'idée qu'ils sont mauvais vient de l'époque de la programmation structurée. Elle est liée à l'idée qu'une fonction doit avoir un seul point d'entrée et un seul point de sortie, c'est-à-dire un seul returnpar fonction.

Cela a du sens si votre fonction est longue et si vous avez plusieurs boucles imbriquées. Cependant, vos fonctions doivent être courtes et vous devez envelopper les boucles et leurs corps dans des fonctions courtes qui leur sont propres. Généralement, le fait de forcer une fonction à avoir un seul point de sortie peut entraîner une logique très complexe.

Si votre fonction est très courte, si vous avez une seule boucle, ou au pire deux boucles imbriquées, et si le corps de la boucle est très courte, alors il est très clair ce qu'un breakou continuefait. Il est également clair ce que font plusieurs returndéclarations.

Ces questions sont abordées dans "Clean Code" de Robert C. Martin et dans "Refactoring" de Martin Fowler.

Dima
la source
12
"Rendez vos fonctions réduites. Puis réduisez-les" "-Robert C. Martin. J'ai trouvé que cela fonctionne étonnamment bien. Chaque fois que vous voyez un bloc de code dans une fonction nécessitant un commentaire expliquant ce qu'il fait, intégrez-le dans une fonction distincte avec un nom descriptif. Même s'il ne s'agit que de quelques lignes, et même s'il n'est utilisé qu'une seule fois. Cette pratique élimine la plupart des problèmes de rupture / poursuite ou de retours multiples.
Dima
2
@ Mikhail: La complexité cyclomatique est généralement assez fortement corrélée au SLOC, ce qui signifie que le conseil peut être simplifié pour "ne pas écrire de longues fonctions".
John R. Strohm
3
L'idée d'un seul point de sortie est largement mal interprétée. Il était une fois, les fonctions n'avaient pas à renvoyer leur appelant. Ils pourraient retourner à un autre endroit. Cela se faisait couramment en langage assembleur. Fortran avait une construction spéciale pour cela; vous pouvez passer un numéro d'instruction précédé d'une esperluette CALL P(X, Y, &10)et, en cas d'erreur, la fonction pourrait passer le contrôle de cette instruction au lieu de revenir au point de l'appel.
kevin cline
@ kevincline comme on le voit avec Lua, par exemple.
Qix
1
@cjsimon vous l'avez. Non seulement les fonctions doivent être petites. Les cours devraient être petits aussi.
Dima
39

Les mauvais programmeurs parlent en absolu (tout comme Sith). Les bons programmeurs utilisent la solution la plus claire possible ( toutes choses étant égales par ailleurs ).

Utiliser pause et continuer rend souvent le code difficile à suivre. Mais si les remplacer rend le code encore plus difficile à suivre, alors c'est un mauvais changement.

L'exemple que vous avez donné est certainement une situation où les pauses et les suites doivent être remplacées par quelque chose de plus élégant.

Matthew Read
la source
Oui, j'ai exagéré le nombre de conditions pour fournir des exemples de cas à régler.
Mikhail
9
Quel est un exemple du code de remplacement que vous avez suggéré? Je pensais que c'était un exemple assez raisonnable de déclarations de garde.
simgineer
Il me semble que "la solution la plus claire possible" est toujours ... possible? Comment ne pourrait-il pas y avoir une solution "la plus claire"? Mais alors, je ne suis pas du genre absolu, alors vous avez peut-être raison.
@nocomprende Je ne sais pas trop où vous voulez en venir. Le «possible» ici n'indique pas que la meilleure solution n'existe pas - seulement cette clarté parfaite et ultime n'est pas une chose. C'est subjectif, après tout.
Matthew Lu
22

La plupart des gens pensent que c'est une mauvaise idée car le comportement n'est pas facilement prévisible. Si vous lisez le code et que vous voyez que while(x < 1000){}vous présumez qu'il va s'exécuter jusqu'à x> = 1000 ... Mais s'il y a des coupures au milieu, cela ne vaut pas, vous ne pouvez donc pas vraiment faire confiance à votre en boucle ...

C’est la même raison pour laquelle les gens n’aiment pas GOTO: certes, il peut être bien utilisé, mais cela peut également conduire à un code spaghetti divin, où le code saute au hasard d’une section à l’autre.

Pour ma part, si je devais faire une boucle qui se cassait à plus d'une condition, je while(x){}basculerais alors X en faux si je devais m'éclater. Le résultat final serait le même et quiconque lirait le code saurait regarder de plus près les choses qui inversaient la valeur de X.

Satanicpuppy
la source
2
+1 très bien dit, et +1 (si je pouvais en faire un autre) pour l' while(notDone){ }approche.
FrustratedWithFormsDesigner
5
Mikhail: Le problème avec break est que la condition finale pour la boucle n'est jamais simplement énoncée à un endroit. Cela rend difficile de prédire la post-condition après la boucle. Dans ce cas trivial (> = 1000), ce n'est pas difficile. Ajoutez de nombreuses instructions if et différents niveaux d'imbrication, il peut s'avérer extrêmement difficile de déterminer la post-condition de la boucle.
S.Lott
S.Lott a frappé le clou sur la tête. L'expression qui contrôle l'itération doit inclure toutes les conditions à remplir pour pouvoir continuer l'itération.
peu-twiddler
6
Remplacer break;par x=false;ne rend pas votre code plus clair. Vous devez toujours rechercher cette déclaration dans le corps. Et dans le cas de x=false;vous devrez vérifier que cela ne x=true;tombe pas plus bas.
Sjoerd
14
Quand les gens disent "je vois x et je suppose y, mais si vous faites z cette hypothèse ne tient pas" j'ai tendance à penser "alors ne faites pas cette hypothèse stupide". Beaucoup de gens voudraient simplifier cela en "quand je vois while (x < 1000)je suppose que ça courra 1000 fois". Eh bien, il y a beaucoup de raisons pour lesquelles c'est faux, même si xc'est initialement zéro. Par exemple, qui dit xest incrémenté précisément une fois au cours de la boucle et jamais modifié de toute autre manière? Même pour votre propre hypothèse, le fait que quelque chose x >= 1000soit défini ne signifie pas que la boucle se terminera - elle peut être remise dans la plage avant que la condition ne soit vérifiée.
Steve314
14

Oui, vous pouvez [ré] écrire des programmes sans instructions break (ou revenir au milieu de boucles qui font la même chose). Mais vous devrez peut-être introduire des variables supplémentaires et / ou une duplication de code, ce qui rend généralement le programme plus difficile à comprendre. Pascal (le langage de programmation) était très mauvais surtout pour les programmeurs débutants pour cette raison. Votre patron veut essentiellement que vous programmiez dans les structures de contrôle de Pascal. Si Linus Torvalds était à votre place, il montrerait probablement le majeur à votre patron!

Il existe un résultat informatique appelé hiérarchie des structures de contrôle de Kosaraju, qui remonte à 1973 et qui est mentionné dans le célèbre article de Knuth sur le gotos de 1974. (Cet article de Knuth avait déjà été recommandé ci-dessus par David Thornley, à propos .) Ce que S. Rao Kosaraju a prouvé en 1973, c’est qu’il n’est pas possible de réécrire tous les programmes qui ont des sauts de profondeur n à plusieurs niveaux en programmes dont la profondeur de coupure est inférieure à n sans introduire de variables supplémentaires. Mais disons que ce n'est qu'un résultat purement théorique. (Ajoutez juste quelques variables supplémentaires?! Vous pouvez sûrement le faire pour faire plaisir à votre patron ...)

Ce qui est bien plus important du point de vue du génie logiciel, c’est un article plus récent, publié en 1995 par Eric S. Roberts, intitulé Sorties de boucle et programmation structurée: rouvrir le débat ( http://cs.stanford.edu/people/eroberts/papers/SIGCSE- 1995 / LoopExits.pdf ). Roberts résume plusieurs études empiriques menées par d'autres avant lui. Par exemple, lorsqu'un groupe d'étudiants de type CS101 était invité à écrire du code pour une fonction implémentant une recherche séquentielle dans un tableau, l'auteur de l'étude disait ce qui suit à propos des étudiants qui utilisaient un break / return / goto pour sortir de la boucle de recherche séquentielle lorsque l'élément a été trouvé:

Je n'ai pas encore trouvé une seule personne qui a tenté un programme en utilisant [ce style] qui a produit une solution incorrecte.

Roberts dit aussi que:

Les étudiants qui ont tenté de résoudre le problème sans utiliser un retour explicite de la boucle for ont eu beaucoup moins de succès: seuls sept des 42 étudiants ayant tenté cette stratégie ont réussi à générer des solutions correctes. Ce chiffre représente un taux de réussite inférieur à 20%.

Oui, vous êtes peut-être plus expérimenté que les étudiants de CS101, mais sans utiliser l'instruction break (ou un retour équivalent depuis le milieu des boucles), vous finirez par écrire du code qui, bien que nominalement structuré soit assez poilu en termes de logique supplémentaire les variables et la duplication de code que quelqu'un, probablement vous-même, y mettra des bugs logiques tout en essayant de suivre le style de codage de votre patron.

Je vais aussi dire ici que le papier de Roberts est beaucoup plus accessible au programmeur moyen, donc une meilleure première lecture que celle de Knuth. C'est aussi plus court et couvre un sujet plus étroit. Vous pourriez probablement même le recommander à votre patron, même s'il est le responsable plutôt que le type CS.

utilisateur3588161
la source
2
Bien que la programmation structurée ait été mon approche pendant de nombreuses années, ces dernières années sont passées à utiliser entièrement des sorties explicites à la première occasion possible. Cela accélère l'exécution et élimine presque les erreurs de logique qui se traduisaient par des boucles sans fin (suspension).
DocSalvager
9

Je n'envisage pas d'utiliser l'une ou l'autre de ces mauvaises pratiques, mais en utiliser trop dans la même boucle devrait justifier de repenser la logique utilisée dans la boucle. Utilisez-les avec parcimonie.

Bernard
la source
:) oui, l'exemple que j'ai donné était conceptuel
Mikhail
7

L'exemple que vous avez donné n'a pas besoin de pauses ni continue:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

Mon «problème» avec les 4 lignes de votre exemple est qu’elles sont toutes au même niveau mais qu’elles font des choses différentes: certaines cassent, d’autres continuent ... Vous devez lire chaque ligne.

Dans mon approche imbriquée, plus vous approfondissez, plus le code devient "utile".

Mais, si au fond de vous trouviez une raison pour arrêter la boucle (autre que la condition principale), une pause ou un retour aurait son utilité. Je préférerais cela à l'utilisation d'un drapeau supplémentaire qui doit être testé dans les conditions de haut niveau. La pause / retour est plus direct; il vaut mieux énoncer l’intention que de définir une autre variable.

Peter Frings
la source
+1 Mais en réalité, vos <comparaisons doivent correspondre <=à la solution des PO
El Ronnoco
3
Dans la plupart des cas, si une personne est si profonde qu'il faut utiliser l'interruption / le retour pour gérer le contrôle de flux, sa fonction / méthode est trop complexe.
peu-twiddler
6

La "méchanceté" dépend de la façon dont vous les utilisez. J'utilise généralement des ruptures dans les constructions en boucle SEULEMENT lorsque cela me permet d'économiser des cycles qui ne peuvent pas être sauvegardés par le refactoring d'un algorithme. Par exemple, parcourir une collection à la recherche d'un élément avec une valeur dans une propriété spécifique définie sur true. Si tout ce que vous avez besoin de savoir, c'est que l'un des éléments avait cette propriété définie sur true, une fois que vous obtenez ce résultat, une pause est utile pour terminer la boucle de manière appropriée.

Si l'utilisation d'une pause ne rend pas le code plus facile à lire, plus court à exécuter ou à enregistrer les cycles de traitement de manière significative, il est préférable de ne pas les utiliser. J'ai tendance à coder au "plus petit commun dénominateur" lorsque cela est possible pour m'assurer que toute personne qui me suit peut facilement consulter mon code et comprendre ce qui se passe (je ne réussis pas toujours à cela). Les ruptures réduisent cela car elles introduisent des points d’entrée / sortie impairs. Si elles sont mal utilisées, elles peuvent se comporter comme une déclaration "goto" inusitée.

Joel Etherton
la source
Je suis d'accord avec les deux points! En ce qui concerne votre deuxième point - je pense qu'il est plus facile de suivre mon post original car il se lit comme l'anglais. Si vous avez une combinaison de conditions dans 1 si déclaration, alors c'est presque un déchiffrement de comprendre ce qui doit se passer pour que si exécute true.
Mikhail
@ Mikhail: Les échantillons que vous avez fournis sont un peu altruistes pour un réalisme spécifique. À mon avis, ces exemples sont clairs, concis et faciles à lire. La plupart des boucles ne sont pas comme ça. La plupart des boucles ont une autre sorte de logique qu'elles effectuent et des conditions conditionnelles potentiellement beaucoup plus compliquées. C'est dans ces cas où interrompre / continuer peut ne pas être la meilleure utilisation car cela brouille la logique lors de la lecture.
Joel Etherton
4

Absolument pas ... Oui, l'utilisation de gotoest mauvaise car elle détériore la structure de votre programme et il est également très difficile de comprendre le flux de contrôle.

Mais l'utilisation de déclarations telles que breaket continuesont absolument nécessaires de nos jours et considérées comme une mauvaise pratique de programmation.

Et aussi pas si difficile à comprendre le flux de contrôle en cours d'utilisation de breaket continue. Dans des constructions telles que switchla breakdéclaration est absolument nécessaire.

Radheshyam Nayak
la source
2
Je n'ai pas utilisé «continue» depuis que j'ai appris le C en 1981. C'est une fonctionnalité de langage inutile, car le code ignoré par l'instruction continue peut être encapsulé par une instruction de contrôle conditionnelle.
peu-twiddler
12
Je préfère utiliser continuer dans ces cas, car cela garantit que mon code ne devienne pas un code de flèche. Je déteste plus le code de flèche que les déclarations de type. J'ai aussi lu: "si cette déclaration est vraie, sautez le reste de cette boucle et passez à la prochaine itération". Très utile quand on se trouve au tout début d'une boucle for (moins utile dans les boucles while).
Jsternberg
@jsternberg Pour la victoire! :-)
Notinlist
3

La notion essentielle vient de pouvoir analyser sémantiquement votre programme. Si vous avez une seule entrée et une seule sortie, le calcul nécessaire pour indiquer les états possibles est considérablement plus facile que si vous devez gérer des chemins de béquille.

En partie, cette difficulté se traduit par la possibilité de raisonner conceptuellement sur votre code.

Franchement, votre deuxième code n'est pas évident. Qu'est-ce que ça fait? Est-ce que continuer 'continue', ou est-ce que ça suit la boucle? Je n'ai aucune idée. Au moins votre premier exemple est clair.

Paul Nathan
la source
Lorsque je travaille dans un projet que le responsable oblige de les utiliser, je devais utiliser un organigramme pour me rappeler son utilisation, et les différents chemins de sortie rendaient le code plus confus ...
umlcat
votre commentaire "Franchement" est syntaxique - "next" est une chose en Perl; les langages normaux utilisent "continuer" pour signifier "sauter cette boucle"
Mikhail
@Mik, peut-être que "sauter cette itération" serait une meilleure description. Dr. IM Pedantic
Pete Wilson
@ Mikhail: Bien sûr. Mais quand on travaille dans beaucoup de langues, cela peut entraîner des erreurs lorsque la syntaxe n'est pas courante entre les langues.
Paul Nathan
Mais les mathématiques ne sont pas la programmation. Le code est plus expressif que les mathématiques. Je reçois cette seule entrée / sortie unique peut plus agréable mais ordinogrammes regarder à quel prix (Ie. Où peut rendre le code pause / retour de meilleurs résultats)?
Evan Plaice
3

Je remplacerais votre deuxième extrait de code par

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

pas pour des raisons de rareté - je pense réellement que c'est plus facile à lire et pour que quelqu'un comprenne ce qui se passe. En règle générale, les conditions de vos boucles doivent être contenues uniquement dans celles qui ne sont pas jonchées dans tout le corps. Cependant, il existe des situations où breaket qui continuepeuvent aider à la lisibilité. breakplus que continueje pourrais ajouter: D

El Ronnoco
la source
Bien que je ne sois pas d’accord, «j’estime que c’est plus facile à lire», c’est le même but que le code ci-dessus. Je n'avais jamais vraiment pensé à «rompre» en tant qu'opérateur de court-circuit, mais c'est parfaitement logique. En ce qui concerne "En règle générale, les conditions de vos boucles doivent être contenues uniquement dans ces conditions", que faites-vous lorsque vous traitez une boucle foreach puisqu'il n'y a vraiment aucun moyen d'insérer une logique conditionnelle dans la définition de la boucle?
Evan Plaice
@Evan La condition ne s'applique pas à une foreachboucle, car elle ne fera qu'itérer chaque élément d'une collection. Une forboucle est similaire en ce sens qu’elle ne doit pas avoir de point de terminaison conditionnel. Si vous avez besoin d'un point de terminaison conditionnel, vous devez utiliser une whileboucle.
El Ronnoco
1
@Evan, cependant, je vois ce que vous voulez dire - c’est-à-dire «Que faire si vous devez sortir d’une boucle foreach? - Eh bien, il ne devrait y avoir qu'un breakmaximum à mon avis de la boucle.
El Ronnoco
2

Je ne suis pas d'accord avec votre patron. Il y a des endroits appropriés pour breaket continuepour être utilisé. En fait, la raison pour laquelle les exceptions et la gestion des exceptions ont été introduites dans les langages de programmation modernes est que vous ne pouvez pas résoudre tous les problèmes en utilisant simplement structured techniques.

En passant, je ne veux pas commencer une discussion religieuse ici, mais vous pourriez restructurer votre code pour qu'il soit encore plus lisible, comme ceci:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

Sur une autre note latérale

Personnellement, je n'aime pas l'utilisation de ( flag == true )in conditionals car, si la variable est déjà un booléen, vous introduisez une comparaison supplémentaire qui doit se produire lorsque la valeur du booléen a la réponse que vous souhaitez - à moins bien sûr que vous soyez certain que votre compilateur optimiser cette comparaison supplémentaire.

Zeke Hansell
la source
Je me suis laissé aller sur cette question pendant des années. Votre chemin ('if (flag) {...}') est beaucoup plus concis et peut-être plus "professionnel" ou "expert". Mais, vous savez, cela signifie souvent qu'un lecteur / responsable doit s'interrompre brièvement pour rappeler ce que signifie la construction; et pour être sûr du sens du test. Actuellement, j'utilise 'if (flag == true) {...}' simplement parce que cela semble être une meilleure documentation. Le mois prochain? Quien Sabe?
Pete Wilson
2
@Pete - Le terme n'est pas laconique mais élégant . Vous pouvez choisir ce que vous voulez, mais si vous craignez que le lecteur / responsable ne comprenne pas ce que signifie booleanou ce que signifie une terminologie concise / élégante, alors vous feriez peut-être mieux embaucher des responsables plus intelligents ;-)
Zeke Hansell
@Pete, je maintiens également ma déclaration sur le code généré. Vous effectuez une autre comparaison en comparant un indicateur à une valeur constante avant d'évaluer la valeur booléenne de l'expression. Pourquoi rendre la tâche plus difficile qu’elle ne l’a été, la variable flag a déjà la valeur que vous souhaitez!
Zeke Hansell
+1 bon travail. Certainement beaucoup plus élégant que l'exemple précédent.
Evan Plaice
2

Je suis d'accord avec ton patron. Ils sont mauvais parce qu'ils produisent des méthodes avec une complexité cyclomatique élevée. Ces méthodes sont difficiles à lire et à tester. Heureusement, il existe une solution facile. Extrayez le corps de la boucle dans une méthode distincte, où "continue" devient "retour". "Retour" est préférable, car après "retour", c'est fini - vous ne vous inquiétez pas pour l'État local.

Pour "break", extrayez la boucle elle-même dans une méthode distincte en remplaçant "break" par "return".

Si les méthodes extraites nécessitent un grand nombre d'arguments, c'est une indication pour extraire une classe - collectez-les dans un objet contextuel.

Kevin Cline
la source
0

Je pense que c'est seulement un problème quand imbriqué profondément dans plusieurs boucles. Il est difficile de savoir à quelle boucle vous vous mettez. Il peut être difficile de suivre une poursuite aussi, mais je pense que la vraie peine vient des pauses - la logique peut être difficile à suivre.

Davidhaskins
la source
2
En fait, il est facile de voir si vous indenter correctement, avoir la sémantique de ces déclarations intériorisée et ne pas être trop fatiguée.
@delnan - c'est beaucoup d'hypothèses;)
davidhaskins
2
Oui, surtout le dernier.
Michael K
Quoi qu'il en soit, le n ° 1 est de toute façon nécessaire pour la programmation sérieuse, le n ° 2 doit être attendu de tous les programmeurs appelés, et le n ° 3 est très utile en général;)
Certaines langues (uniquement les scripts que je connais) prennent en charge break 2; pour d'autres, j'imagine que des drapeaux bool temporaires sont utilisés
Mikhail
0

Tant qu'ils ne sont pas utilisés en mode déguisé, comme dans l'exemple suivant:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Je vais bien avec eux. (Exemple vu dans le code de production, meh)

SuperBloup
la source
Recommanderiez-vous dans de tels cas de créer des fonctions?
Mikhail
Oui et si ce n’est pas possible, un simple goto. Quand je vois un do, je pense à la répétition, pas au contexte pour simuler un saut.
SuperBloup
oh je suis d'accord, je demandais juste comment vous le feriez :) Les informaticiens le font à plusieurs reprises
Mikhail
0

Je n'aime aucun de ces styles. Voici ce que je préférerais:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Je n'aime vraiment pas utiliser returnpour abandonner une fonction. Cela ressemble à un abus de return.

Utiliser breakaussi n'est pas toujours clair à lire.

Mieux encore pourrait être:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

moins d'imbrication et les conditions complexes sont refactorisées en variables (dans un vrai programme, il faudrait évidemment avoir de meilleurs noms, évidemment ...)

(Tout le code ci-dessus est pseudo-code)

FrustratedWithFormsDesigner
la source
11
Vous préféreriez vraiment 3 niveaux d'imbrication par rapport à ce que j'ai tapé ci-dessus?
Mikhail
@ Mikhail: Oui, ou j'attribuerais le résultat de la condition à une variable. Je trouve cela beaucoup plus facile à comprendre que la logique de breaket continue. Terminer une boucle de façon anormale me semble bizarre et je ne l’aime pas.
FrustratedWithFormsDesigner
1
Ironiquement, vous avez mal interprété mes conditions. continuesignifie ignorer la fonctionnalité aller à la boucle suivante; pas "continuer avec l'exécution"
Mikhail
@ Mikhail: Ah. Je ne l'utilise pas souvent et quand je le lis, le sens m'embrouille. Une autre raison pour laquelle je n'aime pas ça : P Donnez-moi une minute pour mettre à jour ...
FrustratedWithFormsDesigner
7
Trop d'imbrication détruit la lisibilité. Et parfois, éviter la pause / continuer introduit la nécessité d'inverser votre logique sur vos tests conditionnels, ce qui peut conduire à une mauvaise interprétation de ce que votre code est en train de faire - je dis juste '
Zeke Hansell
0

Non, c'est un moyen de résoudre un problème, et il existe d'autres moyens de le résoudre.

De nombreux langages courants (Java, .NET (C # + VB), PHP, écrivez le vôtre) utilisent "break" et "continue" pour sauter des boucles. Ils ont tous deux "structuré goto (s)" phrases.

Sans eux:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

Avec eux:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Notez que les codes "break" et "continue" sont plus courts et convertissent généralement les phrases "while" en "phrases" pour "ou" foreach ".

Les deux cas sont une question de style de codage. Je préfère ne pas les utiliser , car le style prolixe me permet de mieux contrôler le code.

En fait, je travaille dans certains projets, où il était obligatoire d'utiliser ces phrases.

Certains développeurs peuvent penser qu'ils ne sont pas necesarilly, mais hypothétiques, si nous devions les supprimer, nous devons également supprimer "while" et "do while" ("répéter jusqu'au", vous les gars pascal) ;-)

Conclusion, même si je préfère ne pas les utiliser, je pense que c’est une option, pas une mauvaise pratique de programmation.

umlcat
la source
Désolé d'être difficile, mais votre deuxième exemple ne contient pas la sortie lorsque la clé n'est pas trouvée (donc, bien sûr, il semble beaucoup plus court).
FrustratedWithFormsDesigner
2
sans oublier que le premier exemple ne fonctionne que si la clé est la dernière de la liste.
Mikhail
@FrustratedWithFormsDesigner EXACTEMENT. Je mets sur purpouse pour indiquer pourquoi est préférable cette méthode ;-)
umlcat
Cependant, vous avez deux routines avec une sémantique différente. par conséquent, ils ne sont pas logiquement équivalents.
peu-twiddler
2
Votre deuxième exemple a deux bogues, un syntaxique et un logique. 1. Il ne compilera pas car l'itérateur n'est pas déclaré en dehors de la portée de la boucle for (donc non disponible dans la sortie de chaîne). 2. Même si l'itérateur a été déclaré en dehors de la boucle, si la clé n'est pas trouvée dans la collection, la sortie en chaîne affichera la clé du dernier élément de la liste.
Evan Plaice
0

Je ne suis pas contre continueet breaken principe, mais je pense que ce sont des constructions de très bas niveau qui peuvent très souvent être remplacées par quelque chose d'encore meilleur.

J'utilise C # comme exemple ici. Prenons le cas de la volonté de parcourir une collection, mais nous ne voulons que les éléments qui remplissent certains prédicats, et nous ne voulons pas faire plus de 100 itérations maximum.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

Cela semble raisonnablement propre. Ce n'est pas très difficile à comprendre. Je pense cependant que cela gagnerait beaucoup à être plus déclaratif. Comparez-le à ce qui suit:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Peut-être que les appels Où et prendre ne devraient même pas être dans cette méthode. Peut-être que ce filtrage devrait être fait AVANT que la collection soit passée à cette méthode. Quoi qu’il en soit, en nous éloignant des éléments de bas niveau et en nous concentrant davantage sur la logique métier proprement dite, nous comprenons mieux ce qui nous intéresse réellement. Il devient plus facile de séparer notre code en modules cohésifs plus conformes aux bonnes pratiques de conception, etc. sur.

Des éléments de bas niveau subsisteront toujours dans certaines parties du code, mais nous voulons le cacher autant que possible, car il faut de l'énergie mentale pour pouvoir raisonner à la place des problèmes de l'entreprise.

kai
la source
-1

Code Complete contient une section intéressante sur l'utilisation gotoet les retours multiples d'une routine ou d'une boucle.

En général, ce n'est pas une mauvaise pratique. breakou continuedire exactement ce qui se passe ensuite. Et je suis d'accord avec ça.

Steve McConnell (auteur de Code Complete) utilise presque les mêmes exemples que vous pour montrer les avantages de l'utilisation de différentes gotoinstructions.

Cependant, une utilisation excessive breakou continuepourrait conduire à un logiciel complexe et non maintenable.

Grzegorz Gierlik
la source