J'utilise souvent ce modèle 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 devrais le remplacer par le plus standard:
while(!<some condition>) {
//do something
}
Son raisonnement était que vous pouviez "oublier la pause" trop facilement et avoir une boucle sans fin. Je lui ai dit que dans le deuxième exemple, vous pouviez tout aussi bien mettre dans une condition qui ne reviendrait jamais vraie, et donc tout aussi facilement avoir une boucle sans fin, donc les deux sont des pratiques également valables.
De plus, je préfère souvent le premier car cela facilite la lecture du code lorsque vous avez plusieurs points de rupture, c’est-à-dire plusieurs conditions qui sortent de la boucle.
Quelqu'un peut-il enrichir cet argument en ajoutant des preuves pour l'une ou l'autre des parties?
Il y a une différence 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 que "faire quelque chose" lorsque l'instruction sera évaluée à true.
Je pense que ce que vous recherchez est une boucle à faire tout le temps. Je suis 100% d'accord sur le fait que while (true)
n'est pas une bonne idée car il est difficile de maintenir ce code et la façon dont vous échappez à la boucle est très goto
esque, ce qui est considéré comme une mauvaise pratique.
Essayer:
do {
//do something
} while (!something);
Consultez la documentation de chaque langue pour connaître la syntaxe exacte. Mais regardez ce code, il fait essentiellement ce qui est dans le faire, puis vérifie la partie tant que pour voir si il devrait le refaire.
Pour citer ce développeur noté de l'époque, Wordsworth:
...
En vérité, la prison à laquelle nous sommes condamnés
Nous-mêmes, aucune prison n'est; et donc pour moi,
De temps en temps, il était temps de se lier
Dans le maigre terrain du Sonnet;
Heureux si des âmes (car leurs besoins doivent être)
Qui ont senti le poids de trop de liberté,
Devrait trouver un réconfort bref, comme je l'ai trouvé.
Wordsworth acceptait les exigences strictes du sonnet comme un cadre libérateur plutôt que comme une camisole de force. Je suggérerais que le cœur de la "programmation structurée" consiste à renoncer à la liberté de créer des graphes de flux arbitrairement complexes au profit d'une plus grande facilité de compréhension.
Je conviens librement que parfois une sortie précoce est le moyen le plus simple d’exprimer une action. Cependant, mon expérience m'a montré que lorsque je me forçais à utiliser les structures de contrôle les plus simples possibles (et que je réfléchissais vraiment à la conception dans ces contraintes), je trouvais le plus souvent que le résultat était un code plus simple et plus clair. L'inconvénient avec
while (true) {
action0;
if (test0) break;
action1;
}
est-il facile de laisser action0
et action1
devenir des morceaux de code de plus en plus volumineux, ou d’ajouter "juste une autre" séquence test-break-action, jusqu’à ce qu’il devienne difficile de pointer une ligne spécifique et de répondre à la question "Quelles conditions est-ce que je sais tenir à ce stade? " Donc, sans créer de règles pour les autres programmeurs, j'essaie d'éviter autant que possible l'idiome while (true) {...}
dans mon propre code.
Quand vous pouvez écrire votre code sous la forme
while (condition) { ... }
ou
while (!condition) { ... }
avec no exits (break
, continue
ou goto
) dans le corps, cette forme est préférable, car quelqu'un peut lire le code et comprendre la condition de terminaison simplement en regardant l'en-tête. C'est bon.
Mais beaucoup de boucles ne correspondent pas à ce modèle, et la boucle infinite avec une ou plusieurs sorties explicites au milieu est un modèle honorable. (Les boucles avec continue
sont généralement plus difficiles à comprendre que les boucles avec break
.) Si vous voulez citer des preuves ou une autorité, citez le fameux article de Don Knuth sur Programmation structurée avec instructions Goto ; vous trouverez tous les exemples, arguments et explications que vous pourriez souhaiter.
Un petit point d'idiome: écrire while (true) { ... }
fait de vous 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 à cela, mais vous devriez l'écrire de cette façon car 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é que de pouvoir "oublier la pause". Je pense qu'oublier le break
est un argument plutôt faible, car ce serait un bug et que vous le trouveriez et le corriger tout de suite.
L'argument que j'ai contre l'utilisation de break
pour sortir d'une boucle sans fin est que vous utilisez essentiellement l'instruction break
en tant que goto
. Je ne suis pas religieusement opposé à l'utilisation de goto
(si le langage le supporte, c'est un jeu juste), mais j'essaie de le remplacer s'il existe une alternative plus lisible.
Dans le cas de plusieurs points break
, je les remplacerais par
while( !<some condition> ||
!<some other condition> ||
!<something completely different> ) {
//do something
}
En consolidant toutes les conditions d'arrêt de cette manière, il est beaucoup plus facile de voir ce qui va mettre fin à cette boucle. Des déclarations break
pourraient être éparpillées, ce qui est tout sauf lisible.
Javier a fait un commentaire intéressant sur ma réponse précédente (celle citant Wordsworth):
Je pense que while (true) {} est une construction plus «pure» que 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 manière informelle la "complexité" comme "combien du reste du code il me faut dans la tête pour pouvoir comprendre cette simple ligne ou expression?" Plus je dois garder à l’esprit, plus le code est complexe. Plus le code me dit explicitement, moins il est complexe.
Alors, dans le but de réduire la complexité, permettez-moi de 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:
c1
reste vrai, eta1
est exécuté, c1
est garanti à conserver.La différence est une de perspective. le premier concerne le comportement dynamique externe de l'ensemble de la boucle en général, tandis que le second 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
, ce qui nécessite que je réfléchisse davantage à ce sur quoi je peux compter au point 2, etc.
Mettons un (petit) exemple spécifique en place 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 "extérieur" est que la boucle fait clairement quelque chose dans someString
qui ne peut être fait que tant que index
est positionné dans someString
. Cela crée une attente selon laquelle nous modifierons soit index
, soit someString
dans le corps (à un endroit et de manière inconnue jusqu'à ce que j'examine le corps), de sorte que la résiliation se produise. Cela me donne à la fois le contexte et l’attente de penser au corps.
Le problème "interne" est que nous avons la garantie que l'action qui suit le point 1 sera légale. Par conséquent, en lisant le code au point 2, je peux penser à ce qui est fait avec une valeur char. Je sais a été légalement obtenu. (Nous ne pouvons même pas évaluer la condition si someString
est une référence nulle, mais je suppose également que nous nous en sommes bien gardés dans le contexte de cet exemple!)
En revanche, une boucle de la forme:
while (true) {
// p1
a1;
// p2
...
}
me laisse tomber sur les deux questions. Au niveau extérieur, je me demande si cela signifie que je vraiment devrais m'attendre à ce que cette boucle se répète indéfiniment (par exemple, la boucle de répartition d'événements d'un système d'exploitation), ou s'il se passe autre chose. Cela ne me donne ni un contexte explicite pour lire le corps, ni une attente de ce qui constitue un progrès vers une terminaison (incertaine).
Au niveau interne, j'ai absolument aucune garantie explicite de toute circonstance pouvant être maintenue au point 1. La condition true
, qui est bien sûr vraie partout, est la déclaration la plus faible possible sur ce que nous pouvons savoir à tout moment. le programme. Comprendre les conditions préalables d’une action est une information très précieuse lorsque vous essayez 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.
while (true) peut avoir un sens si vous avez plusieurs déclarations et que vous voulez vous arrêter en cas d'échec
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()
}
........
}
La première est OK s'il y a plusieurs façons de rompre la boucle ou si la condition de rupture ne peut pas être exprimée facilement en haut 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. , à la dernière itération).
Mais si vous pouvez l'éviter, vous devriez le faire, car la programmation doit consister à écrire des choses très complexes de la manière la plus évidente possible, tout en implémentant les fonctionnalités correctement et de manière performante. C'est pourquoi votre ami est, dans le cas général, correct. La manière dont votre ami écrit les constructions de boucle est beaucoup plus évidente (en supposant que les conditions décrites dans le paragraphe précédent ne sont pas remplies).
Parfois, vous avez besoin d'une boucle infinie, par exemple, l'écoute sur le port ou l'attente d'une connexion.
Donc, bien que (vrai) ... ne devrait pas être classé comme bon ou mauvais, laissez la situation décider quoi utiliser
Cela dépend de ce que vous essayez de faire, mais en général, je préfère mettre le conditionnel de côté.
J'utiliserais une boucle while (vraie) si j'écrivais un démon ou un autre processus qui devrait s'exécuter jusqu'à ce qu'il soit tué.
Ce n'est pas tant la partie tant que (vrai) la partie qui est mauvaise, mais le fait que vous devez en sortir ou en sortir est le problème. break and goto ne sont pas vraiment des méthodes acceptables de contrôle de flux.
Moi aussi je ne vois pas vraiment le point. Même dans quelque chose qui passe en boucle pendant toute la durée d'un programme, vous pouvez au moins avoir un booléen appelé Quit ou quelque chose que vous définissez sur true pour sortir de la boucle correctement dans une boucle, comme tout (! Quit) ... Non il suffit d'appeler la pause à un moment quelconque et de sauter,
S'il existe une (et une seule) condition de rupture non exceptionnelle, il est préférable de placer cette condition directement dans la construction de contrôle de flux (le temps). Voir tout le monde (vrai) {...} me fait penser, en tant que lecteur de code, qu’il n’existe pas de moyen simple d’énumérer les conditions de pause et me fait réfléchir. les dans la boucle en cours et ce qui aurait pu être défini dans la boucle précédente) "
En bref, je suis avec votre collègue dans le cas le plus simple, mais bien que (vrai) {...} ne soit pas rare.
La réponse parfaite du consultant: ça dépend. Dans la plupart des cas, la bonne chose à faire est d'utiliser une boucle while
while (condition is true ) {
// do something
}
ou un "répéter jusqu'à" qui est fait dans une langue C-like avec
do {
// do something
} while ( condition is true);
Si l'un ou l'autre de ces cas fonctionne, utilisez-les.
Parfois, comme dans la boucle interne d'un serveur, vous voulez vraiment dire qu'un programme doit continuer jusqu'à ce que quelque chose de externe l'interrompe. (Par exemple, un démon httpd - il ne s’arrêtera pas sauf s’il se bloque ou s’il est arrêté par un arrêt).
ALORS ET SEULEMENT ALORS utilisez un moment (1):
while(1) {
accept connection
fork child process
}
Le dernier cas est la rare occasion où vous souhaitez effectuer une partie de la fonction avant de terminer. Dans ce cas, utilisez:
while(1) { // or for(;;)
// do some stuff
if (condition met) break;
// otherwise do more stuff.
}
Non, ce n'est pas mauvais, car vous pouvez ne pas toujours connaître la condition de sortie lors de la configuration de la boucle ou avoir plusieurs conditions de sortie. Cependant, il faut plus de soin pour éviter une boucle infinie.
Le problème est que tous les algorithmes ne respectent pas le modèle "while (cond) {action}".
Le modèle de boucle général 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é n fois et action_B sera exécuté (n-1) fois.
Un exemple concret est le suivant: affiche tous les éléments d’un tableau séparés par des virgules ..__ Nous voulons tous les n éléments avec des virgules (n-1).
Vous pouvez toujours faire quelques astuces pour vous en tenir au modèle de boucle While, mais cela répète toujours le code ou vérifie deux fois la même condition (pour chaque boucle) ou ajoute une nouvelle variable. Vous serez donc toujours moins efficace et moins lisible que le modèle de boucle while-true-break.
Exemple de "mauvais" tour: ajouter une variable et une 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" tour: répétez le code. Le code répété ne doit pas être oublié lors de la modification de l'une des deux sections.
loop_prepare
action_A
while(cond):
action_B
action_A
after_loop_code
Remarque: dans le dernier exemple, le programmeur peut masquer (volontairement ou non) le code en mélangeant "loop_prepare" avec le premier "action_A" et action_B avec le deuxième action_A. Donc, il peut avoir le sentiment qu'il ne le fait pas.
Il y a déjà une question pratiquement identique dans SO en Est-ce que WHILE TRUE… BREAK… END WHILE est un bon design? . @Glomek a répondu (dans un message sous-estimé):
Parfois, c'est un très bon design. Voir Programmation structurée avec instructions Goto par Donald Knuth pour quelques exemples. J'utilise souvent cette idée de base pour les boucles qui s'exécutent "n fois et demi", en particulier les boucles de lecture/traitement. Cependant, j'essaie généralement de n'avoir qu'une déclaration de rupture. Il est ainsi plus facile de raisonner sur l’état du programme après la fin de la boucle.
Un peu plus tard, j'ai répondu avec le commentaire associé, et aussi terriblement sous-estimé, (en partie parce que je n'ai pas remarqué que Glomek était à sa première sortie, je pense):
Un article fascinant est la "Programmation structurée avec déclarations" de Knuth de 1974 (disponible dans son livre "Literate Programming", et probablement aussi ailleurs). Il aborde, entre autres choses, les moyens contrôlés de rompre les boucles et (sans utiliser le terme) la déclaration boucle et demi.
Ada fournit également des constructions en boucle, notamment:
loopname:
loop
...
exit loopname when ...condition...;
...
end loop loopname;
Le code de la question initiale est similaire à celui-ci dans l'intention.
Une différence entre l'élément référencé SO et ceci est la "pause finale"; Il s’agit d’une boucle simple qui utilise la pause pour sortir de la boucle plus tôt. On s'est demandé si c'était aussi un bon style - je n'ai pas la référence croisée sous la main.
Ce que votre ami a recommandé 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 vont parfaitement, comme mentionné par d'autres. En réponse à l'inquiétude de votre ami "oubliez 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 du code, aussi structurel que des codes cassés au début ou à la fin d'une boucle.
en utilisant des boucles comme
while (1) {faire des choses}
est nécessaire dans certaines situations. Si vous programmez des systèmes intégrés (pensez à des microcontrôleurs tels que PIC, MSP430 et DSP), alors presque tout votre code sera dans une boucle while (1). Lorsque vous codez pour des DSP, vous avez parfois besoin d’un peu de temps (1) {} et le reste du code est une routine de service d’interruption (ISR).
Il a probablement raison.
Fonctionnellement, les deux peuvent être identiques.
Cependant, pour la lisibilité et la compréhension du déroulement du programme, le moment (condition) est meilleur. La pause sent plus d'une sorte de goto. Le moment (condition) est très clair sur les conditions qui continuent la boucle, etc. Cela ne veut pas dire que la pause est fausse, mais peut être moins lisible.
Quelques avantages de l’utilisation de cette dernière construction me viennent à l’esprit:
il est plus facile de comprendre ce que fait la boucle sans chercher de rupture dans le code de la boucle.
si vous n'utilisez pas d'autres sauts dans le code de la 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 de code, ce qui améliore la lisibilité.
J'aime mieux les boucles for
:
for (;;)
{
...
}
ou même
for (init();;)
{
...
}
Mais si la fonction init()
ressemble au corps de la boucle, vous êtes meilleur avec
do
{
...
} while(condition);
Je préfère l'approche while (!), Car elle traduit plus clairement et immédiatement l'intention de la boucle.
On a beaucoup parlé de lisibilité ici et de sa très bonne construction, mais comme pour toutes les boucles dont la taille n’est pas fixée (c’est-à-dire faire tout le temps), vous courez un risque.
His reasoning was that you could "forget the break" too easily and have an endless loop.
Dans une boucle while, vous demandez en fait un processus qui fonctionne indéfiniment sauf si quelque chose se produit, et si cela ne se produit pas dans un paramètre donné, vous obtiendrez exactement ce que vous vouliez ... une boucle sans fin.
Je pense que l’avantage de l’utilisation de "while (true)" est probablement de permettre l’écriture de plusieurs conditions de sortie, en particulier si ces conditions de sortie doivent apparaître à un emplacement différent du bloc de code. Cependant, pour moi, cela pourrait être chaotique de devoir exécuter un code à sec pour voir comment le code interagit.
Personnellement, je vais essayer d'éviter tout (vrai). La raison en est que chaque fois que je regarde le code écrit précédemment, je constate généralement que je dois savoir quand il tourne/se termine plus que ce qu'il est réellement. Par conséquent, avoir à localiser les "pauses" en premier est un peu gênant pour moi.
S'il y a un besoin de condition de sortie multiple, j'ai tendance à reformuler la logique de détermination de la condition en une fonction distincte afin que le bloc de boucle ait l'air propre et plus facile à comprendre.