Est-ce que while (true) with break est une mauvaise pratique de programmation?
j'utilise souvent ce motif de code:
while(true) {
//do something
if(<some condition>) {
break;
}
}
un autre programmeur m'a dit que c'était une mauvaise pratique et que je devais la remplacer par la plus standard:
while(!<some condition>) {
//do something
}
son raisonnement était que vous pourriez "oublier la pause" trop facilement et avoir une boucle sans fin. Je lui ai dit que dans le deuxième exemple, vous pourriez tout aussi facilement mettre dans une condition qui n'est jamais retourné vrai et donc tout aussi facilement avoir une boucle sans fin, de sorte que les deux sont également valide les pratiques.
en outre, je préfère souvent le premier car il rend le code plus facile à lire quand vous avez plusieurs points de rupture, c'est-à-dire plusieurs conditions qui sortent de la boucle.
est-ce que quelqu'un peut enrichir cet argument en ajoutant des preuves pour un côté ou l'autre?
23 réponses
il y a une divergence entre les deux exemples. Le premier exécutera le "faire quelque chose" au moins une fois à chaque fois, même si la déclaration n'est jamais vraie. Le second ne fera "quelque chose" que lorsque l'énoncé sera évalué à true.
je pense que ce que vous cherchez est un do-while. J'ai 100% d'accord que while (true)
n'est pas une bonne idée car cela rend difficile de maintenir ce code et la façon de s'échapper de la boucle est très goto
esque qui est considéré comme une mauvaise pratique.
, Essayez:
do {
//do something
} while (!something);
Vérifiez la syntaxe exacte de votre documentation linguistique. Mais regardez ce code, il fait essentiellement ce qui est dans le do, puis vérifie la portion while pour voir si elle devrait le faire à nouveau.
pour citer ce développeur noté de jours passés, Wordsworth:
...
En vérité la prison, à laquelle nous condamnons
Nous-mêmes, pas de prison est; et donc pour moi,
Dans les humeurs diverses, "c'était un passe-temps pour être lié
Heureux si quelques âmes (pour leurs besoins doivent être)
Qui ont ressenti le poids de trop de liberté,
Je devrais trouver un bref réconfort là-bas, comme j'ai trouvé.
Wordsworth accepte les strictes exigences du sonnet comme un cadre libérateur, plutôt que comme une camisole de force. Je dirais que le cœur de la" programmation structurée " est d'abandonner la liberté de construire arbitrairement des diagrammes de flux complexes en faveur d'une facilité libératrice de compréhension.
I librement d'accord que parfois une sortie anticipée est la façon la plus simple d'exprimer une action. Cependant, d'après mon expérience, lorsque je me force à utiliser les structures de contrôle les plus simples possibles (et que je songe réellement à concevoir dans ces limites), je constate le plus souvent que le résultat est un code plus simple et plus clair. L'inconvénient avec
while (true) {
action0;
if (test0) break;
action1;
}
est qu'il est facile de laisser action0
et action1
devenir de plus en plus gros morceaux de code, ou ajouter "juste une autre" séquence test-break-action, jusqu'à ce qu'il devienne difficile de pointer vers une ligne précise et de répondre à la question, "quelles conditions puis-je savoir tenir à ce point?"Donc, sans établir de règles pour les autres programmeurs, j'essaie d'éviter l'idiome while (true) {...}
dans mon propre code chaque fois que c'est possible.
Quand vous pouvez écrire votre code dans le formulaire
while (condition) { ... }
ou
while (!condition) { ... }
avec pas de sortie ( break
, continue
, ou goto
) dans le corps , cette forme est préférée, parce que quelqu'un peut lire le code et comprendre la condition de fin juste en regardant l'en-tête . Ce qui est bon.
mais beaucoup de boucles ne correspondent pas à ce modèle, et le boucle infinie avec sortie(S) explicite (s) au milieu est un modèle honorable . (Boucles avec continue
sont généralement plus difficiles à comprendre que les boucles avec break
.) Si vous voulez une preuve ou une autorité de citer, ne cherchez pas plus loin que le célèbre papier de Don Knuth sur programmation structurée avec des déclarations Goto ; vous trouverez tous les exemples, arguments et explications que vous pourriez vouloir.
un petit point d'idiome: l'écriture while (true) { ... }
vous présente comme un ancien programmeur Pascal ou peut-être de nos jours un programmeur Java. Si vous écrivez en C ou C++, l'idiome préféré est
for (;;) { ... }
il n'y a pas de bonne raison pour cela, mais vous devriez l'écrire de cette façon parce que c'est la façon dont les programmeurs C s'attendent à le voir.
je préfère
while(!<some condition>) {
//do something
}
mais je pense que c'est plus une question de lisibilité, plutôt que sur la possibilité "d'oublier la pause."Je pense que l'oubli du break
est un argument plutôt faible, car ce serait un bug et vous le trouverez et le corrigerez immédiatement.
l'argument que j'ai contre l'utilisation d'un break
pour sortir d'une boucle sans fin est que vous utilisez essentiellement l'énoncé break
comme un goto
. Je ne suis pas religieusement contre l'utilisation de goto
(si le langage le supporte, c'est du jeu), mais j'essaie de le remplacer s'il y a une alternative plus lisible.
dans le cas de nombreux break
points je les remplacerais par
while( !<some condition> ||
!<some other condition> ||
!<something completely different> ) {
//do something
}
la consolidation de toutes les conditions d'arrêt de cette façon, il est beaucoup plus facile de voir ce qui va mettre fin à cette boucle. break
pourrait être saupoudré, et c'est tout sauf lisible.
while (true) peut avoir du sens si vous avez de nombreux états et que vous voulez arrêter si tout échoue
while (true) {
if (!function1() ) return;
if (!function2() ) return;
if (!function3() ) return;
if (!function4() ) return;
}
est mieux que
while (!fail) {
if (!fail) {
fail = function1()
}
if (!fail) {
fail = function2()
}
........
}
Javier a fait un commentaire intéressant sur ma réponse précédente (celle qui cite Wordsworth):
je pense while(true){} est un plus "pure" construire qu'while(condition){}.
et je ne pouvais pas répondre correctement en 300 caractères (désolé!)
dans mon enseignement et mon mentorat, j'ai défini de façon informelle la "complexité" comme "la quantité du reste du code que je dois avoir dans ma tête pour pouvoir vous comprenez cette ligne ou cette expression?"Plus j'ai de choses à garder à l'esprit, plus le code est complexe. Plus le code me dit explicitement, moins il est complexe.
ainsi, dans le but de réduire la complexité, laissez-moi répondre à Javier en termes d'exhaustivité et de force plutôt que de pureté.
je pense à ce fragment de code:
while (c1) {
// p1
a1;
// p2
...
// pz
az;
}
comme exprimant deux choses simultanément:
- le corps (entier) sera répété aussi longtemps que
c1
reste vrai, et - au point 1, où
a1
est exécutée,c1
est garantie à conserver.
la différence est celle de la perspective; la première de celles-ci a à voir avec le comportement extérieur et dynamique de l'ensemble de la boucle en général, tandis que la seconde est utile pour comprendre la garantie statique interne sur laquelle je peux compter tout en pensant à a1
en particulier. Bien sûr, l'effet net de a1
peut invalider c1
, exigeant que je réfléchisse davantage sur ce sur quoi je peux compter au point 2, etc.
mettons en place un exemple précis (minuscule) pour réfléchir à la condition et à la première action:
while (index < length(someString)) {
// p1
char c = someString.charAt(index++);
// p2
...
}
le problème "externe" est que la boucle fait clairement quelque chose dans someString
qui ne peut être fait aussi longtemps que index
est positionné dans le someString
. Cela crée une attente que nous allons modifier soit index
ou someString
dans le corps (à un endroit et d'une manière qui ne sont pas connus jusqu'à ce que j'examine le corps) de sorte que la fin se produit éventuellement. Cela me donne à la fois le contexte et l'attente de penser au corps.
le problème "interne" est que nous sommes assurés que l'action suivant le point 1 sera légale, donc en lisant le code au point 2 je peux penser à ce qui est être fait avec une valeur de char I savoir a été obtenu légalement. (Nous ne pouvons même pas évaluer la condition si someString
est un réf nul, mais je suppose aussi que nous avons gardé contre cela dans le contexte autour de cet exemple!)
en contraste, une boucle de la forme:
while (true) {
// p1
a1;
// p2
...
}
me laisse tomber sur les deux questions. Sur le plan extérieur, je me demande si cela signifie que je vraiment devrait s'attendre cette boucle à Boucler pour toujours (par exemple la boucle de répartition des événements principaux d'un système d'exploitation), ou s'il se passe quelque chose d'autre. Cela ne me donne ni un contexte explicite pour lire le corps, ni une attente de ce qui constitue un progrès vers la fin (incertaine).
au niveau interne, j'ai absolument pas garantie explicite sur toutes les circonstances qui peuvent tenir au point 1. La condition true
, qui est bien sûr vrai partout, est le plus faible possible sur ce que nous savons, à n'importe quel moment dans le programme. Comprendre les conditions préalables d'une action sont des informations très précieuses quand on essaie de penser à ce que l'action accomplit!
donc, je suggère que l'idiome while (true) ...
est beaucoup plus incomplet et faible, et donc plus complexe, que while (c1) ...
selon la logique que j'ai décrite ci-dessus.
la première est acceptable s'il y a plusieurs façons de rompre la boucle, ou si la condition de rupture ne peut pas être exprimée facilement au sommet de la boucle (par exemple, le contenu de la boucle doit être exécuté à mi-chemin mais l'autre moitié ne doit pas être exécutée, lors de la dernière itération).
mais si vous pouvez l'éviter, vous devriez, parce que la programmation devrait être sur l'écriture des choses très complexes de la manière la plus évidente possible, tout en mettant en œuvre des fonctionnalités correctement et performantly. C'est pourquoi votre ami est, dans le cas général, les corriger. La façon de votre ami d'écrire des constructions de boucle est beaucoup plus évidente (en supposant que les conditions décrites dans le paragraphe précédent n'obtiennent pas).
parfois, vous avez besoin de boucle infinie, par exemple l'écoute sur le port ou l'attente de connexion.
Alors while(true)... ne devrait pas être classé comme bon ou mauvais, laisser la situation décider ce qu'il faut utiliser
Cela dépend de ce que vous essayez de faire, mais en général je préfère mettre le conditionnel dans le temps.
- c'est plus simple, puisque vous n'avez pas besoin d'un autre test dans le code.
- c'est plus facile à lire, puisque vous n'avez pas à aller à la chasse pour une pause à l'intérieur de la boucle.
- vous réinventez la roue. Le but de tout cela est de faire quelque chose aussi longtemps qu'un test est vrai. Pourquoi subvertir qu'en mettant la pause condition de quelque part d'autre?
j'utiliserais une boucle while(true) si j'écrivais un démon ou un autre processus qui devrait fonctionner jusqu'à ce qu'il soit tué.
S'il y a une (et une) condition de rupture non exceptionnelle, il est préférable de placer cette condition directement dans le dispositif de contrôle du débit (the while). Voir tout en (vrai) { ... } me fait penser, en tant que lecteur de code, qu'il n'y a pas de façon simple d'énumérer les conditions de rupture et me fait penser "regardez attentivement ceci et pensez soigneusement aux conditions de rupture (ce qui est mis devant eux dans la boucle actuelle et ce qui pourrait avoir été mis dans la boucle précédente) "
en bref, je suis avec votre collègue dans le cas le plus simple, mais tout en(vrai){ ... } n'est pas rare.
L'idéal du consultant réponse: ça dépend. La plupart des cas, la bonne chose à faire est d'utiliser une boucle while
while (condition is true ) {
// do something
}
ou un "repeat until" qui est fait dans un langage de type C avec
do {
// do something
} while ( condition is true);
si l'un de ces deux cas fonctionne, utilisez-les.
Parfois, comme dans la boucle interne d'un serveur, vous vraiment dire qu'un programme doit continuer jusqu'à ce que quelque chose interruptions externes. (Pensez, par exemple, une httpd daemon -- il ne va pas s'arrêter à moins que ça plante ou il est arrêté par un arrêt.)
alors et seulement alors utiliser un certain temps (1):
while(1) {
accept connection
fork child process
}
cas Final est l'occasion rare où vous voulez faire une partie de la fonction avant la fin. Dans ce cas, utilisez:
while(1) { // or for(;;)
// do some stuff
if (condition met) break;
// otherwise do more stuff.
}
il y a une question essentiellement identique déjà dans est-il vrai ... BREAK ... END alors qu'un bon design? . @Glomek a répondu (dans un billet sous-estimé):
parfois, c'est un très bon design. Voir Programmation Structurée Avec les Instructions Goto par Donald Knuth pour quelques exemples. J'utilise souvent cette idée de base pour les boucles qui tournent "n et demi fois", en particulier lire/traiter les boucles. Cependant, j'essaie généralement n'avoir qu'une seule instruction break. Cela rend plus facile de raisonner sur l'état du programme au bout de la boucle termine.
un peu plus tard, j'ai répondu avec le commentaire connexe, et aussi tristement sous-estimé, (en partie parce que je N'ai pas remarqué Glomek la première fois round, je pense):
un article fascinant est Knuth "programmation structurée avec aller aux déclarations" de 1974 (disponible dans son livre ' Literate Programmation", et probablement ailleurs aussi). Il discute, entre autres choses, les moyens contrôlés de sortir des boucles, et (n'utilisant pas le terme) la boucle-et-demi déclaration.
Ada fournit également des constructions en boucle, y compris
loopname:
loop
...
exit loopname when ...condition...;
...
end loop loopname;
le code de la question originale est semblable à celui de l'intention.
une différence entre L'article SO référencé et celui-ci est le ' final break'; c'est une boucle à un seul coup qui utilise break pour sortir de la boucle plus tôt. On s'est demandé si c'était aussi un bon style - je n'ai pas le renvoi sous la main.
non, ce n'est pas mal puisque vous ne connaissez pas toujours la condition de sortie lorsque vous configurez la boucle ou peut-être plusieurs conditions de sortie. Cependant, il faut plus de soin pour prévenir une boucle infinie.
ce n'est pas tant le alors que(vrai) partie qui est mauvaise, mais le fait que vous devez briser ou goto out de lui qui est le problème. break et goto ne sont pas vraiment des méthodes acceptables de contrôle du débit.
Je ne vois pas non plus l'intérêt. Même dans quelque chose qui parcourt toute la durée du programme, vous pouvez au moins avoir un booléen " Quit ou quelque chose que vous affectez la valeur true pour sortir de la boucle correctement dans une boucle comme while(!Quitter.)..
le problème est que tous les algorithmes ne respectent pas le modèle" while(cond){action}".
le modèle de boucle générale est comme ceci:
loop_prepare
loop:
action_A
if(cond) exit_loop
action_B
goto loop
after_loop_code
quand il n'y a pas d'action_A vous pouvez le remplacer par:
loop_prepare
while(cond)
action_B
after_loop_code
quand il n'y a pas d'action_B vous pouvez le remplacer par:
loop_prepare
do action_A
while(cond)
after_loop_code
Dans le cas général, action_A sera exécutée n fois et action_B sera exécuté (n-1) fois.
un exemple de la vie réelle est : Imprimez tous les éléments d'une table séparée par des virgules. Nous voulons tous les éléments n avec les virgules (n-1).
vous pouvez toujours faire quelques trucs pour coller au modèle while-loop, mais cela va toujours répéter le code ou vérifier deux fois la même condition (pour chaque boucle) ou ajouter une nouvelle variable. Ainsi, vous serez toujours moins efficace et moins lisible que le tout-vrai-break modèle de la boucle.
Exemple de (mauvais) "astuce" : ajouter variable et condition
loop_prepare
b=true // one more local variable : more complex code
while(b): // one more condition on every loop : less efficient
action_A
if(cond) b=false // the real condition is here
else action_B
after_loop_code
exemple de (mauvais) "truc" : répéter le code. Le code répété ne doit pas être oublié lors de la modification d'une des deux sections.
loop_prepare
action_A
while(cond):
action_B
action_A
after_loop_code
Note: dans le dernier exemple, le programmeur peut brouiller (volontairement ou non) le code en mélangeant le "loop_prepare" avec le premier "action_A", et action_B avec le second action_A. Donc il peut avoir le sentiment qu'il ne fait pas ça.
il a probablement raison.
Fonctionnellement les deux peuvent être identiques.
Toutefois, pour les lisibilité et de comprendre le flux de programme, le while(condition), c'est mieux. La pause ressemble plus à un goto. Le while (condition) est très clair sur les conditions qui continuent la boucle, etc. Ça ne veut pas dire que break est mal, mais peut être moins lisible.
quelques avantages d'utiliser ce dernier concept qui me viennent à l'esprit:
-
il est plus facile de comprendre ce que fait la boucle sans chercher des ruptures dans le code de la boucle.
-
si vous n'utilisez pas d'autres ruptures dans le code de boucle, il n'y a qu'un seul point de sortie dans votre boucle et c'est la condition while ().
-
finit généralement par être moins code, ce qui ajoute à la lisibilité.
je préfère le while(!) parce qu'elle traduit plus clairement et immédiatement l'intention de la boucle.
on a beaucoup parlé de lisibilité ici et de son très bien construit, mais comme avec toutes les boucles qui ne sont pas fixes dans la taille (c'est-à-dire. faire tout et tout) vous courrez un risque.
His reasoning was that you could "forget the break" too easily and have an endless loop.
dans une boucle de temps, vous demandez en fait un processus qui s'exécute indéfiniment à moins que quelque chose arrive, et si cela ne se produit pas dans un certain paramètre, vous obtiendrez exactement ce que vous vouliez... une boucle sans fin.
je pense que l'avantage d'utiliser" while(true) " est probablement de permettre la condition de sortie multiple plus facile à écrire, surtout si ces conditions de sortie doit apparaître dans un endroit différent à l'intérieur du bloc de code. Cependant, pour moi, ça pourrait être chaotique quand je dois faire un dry-run du code pour voir comment le code interagit.
personnellement, je vais essayer d'éviter tout en (vrai). La raison est que chaque fois que je regarde en arrière au code écrit précédemment, je trouve habituellement que je dois comprendre quand il lance/arrête plus de ce qu'il fait réellement. Par conséquent, avoir à localiser les" pauses " d'abord est un peu gênant pour moi.
S'il y a un besoin pour la condition de sortie multiple, j'ai tendance à reformuler la logique de détermination de la condition dans une fonction séparée de sorte que le bloc de boucle semble propre et plus facile à comprendre.
ce que votre ami recommande est différent de ce que vous avez fait. Votre propre code est plus proche de
do{
// do something
}while(!<some condition>);
qui exécute toujours la boucle au moins une fois, quelle que soit la condition.
mais il y a des moments où les pauses sont parfaitement correctes, comme d'autres l'ont mentionné. En réponse au souci de votre ami de "oublier la pause", j'écris souvent sous la forme suivante:
while(true){
// do something
if(<some condition>) break;
// continue do something
}
par bonne indentation, le point de rupture est clair pour le premier lecteur de temps du code, regarder aussi structurel que les codes qui cassent au début ou en bas d'une boucle.
utilisant des boucles comme
while(1) { faire des trucs }
est nécessaire dans certaines situations. Si vous faites de la programmation de systèmes intégrés (pensez aux microcontrôleurs comme PICs, MSP430, et la programmation DSP), alors presque tout votre code sera dans une boucle while(1). Quand vous codez pour DSPs parfois vous avez juste besoin d'un certain temps(1){} et le reste du code est une routine de service d'interruption (ISR).
j'aime for
boucles de mieux:
for (;;)
{
...
}
ou même
for (init();;)
{
...
}
mais si la fonction init()
ressemble au corps de la boucle, vous êtes mieux avec
do
{
...
} while(condition);