web-dev-qa-db-fra.com

Les boucles "while (true)" sont-elles si mauvaises?

Je programme en Java depuis plusieurs années, mais je viens de rentrer à l'école pour obtenir un diplôme officiel. J'ai été assez surpris d'apprendre que lors de ma dernière mission, j'avais perdu des points pour avoir utilisé une boucle comme celle ci-dessous.

do{
     //get some input.
     //if the input meets my conditions, break;
     //Otherwise ask again.
} while(true)

Maintenant, pour mon test, je recherche simplement une entrée de console, mais on m'a dit que ce type de boucle est déconseillé car utiliser break s'apparente à goto, nous ne le faisons tout simplement pas.

Je comprends parfaitement les pièges de goto et de son Java cousin break:label, et j’ai le bon sens de ne pas les utiliser. Je me rends également compte qu'un programme plus complet fournirait un autre moyen d'évasion, par exemple pour simplement mettre fin au programme, mais ce n'est pas une raison que mon professeur a citée, alors ...

Quel est le problème avec do-while(true)?

213
JHarnach

Je ne dirais pas que c'est mauvais - mais, normalement, au moins, je chercherais une alternative.

Dans les situations où c'est la première chose que j'écris, j'ai presque toujours au moins essayez de la reformuler en quelque chose de plus clair. Parfois, cela ne peut pas être aidé (ou l’alternative est d’avoir une variable bool qui ne fait rien de significatif sauf d’indiquer la fin de la boucle, moins clairement qu’une déclaration break, mais cela vaut au moins la peine d’essayer. .

Comme exemple d’exemple où il est plus facile d’utiliser break qu’un indicateur, considérons:

while (true)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        break;
    }
    actOnInput(input);
}

Forceons-le à utiliser un drapeau:

boolean running = true;
while (running)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        running = false;
    }
    else
    {
        actOnInput(input);
    }
}

Je considère ce dernier comme plus compliqué à lire: il comporte un bloc supplémentaire else, le actOnInput est plus en retrait, et si vous essayez de savoir ce qui se passe quand testCondition renvoie true, vous devez examiner attentivement le reste du bloc pour vérifier qu’il n’ya rien après le bloc else qui se produirait si running avait été réglé sur false ou non.

La déclaration break communique plus clairement l’intention et permet au reste du bloc de s’acquitter de sa tâche sans se préoccuper des conditions antérieures.

Notez que ceci est exactement le même argument que les gens ont à propos de plusieurs instructions de retour dans une méthode. Par exemple, si je peux calculer le résultat d'une méthode dans les premières lignes (par exemple parce que certaines entrées sont nulles, ou vides ou nulles), je trouve qu'il est plus facile de renvoyer cette réponse directement que d'avoir une variable pour stocker le résultat. , puis tout un bloc d’autre code, et enfin une instruction return.

217
Jon Skeet

Autant que je sache, vraiment. Les enseignants sont simplement allergiques à goto, car ils ont entendu dire que c'était vraiment mauvais. Sinon, vous écririez simplement:

bool guard = true;
do
{
   getInput();
   if (something)
     guard = false;
} while (guard)

Ce qui est presque la même chose.

Peut-être que cela est plus propre (parce que toutes les informations de bouclage sont contenues en haut du bloc):

for (bool endLoop = false; !endLoop;)
{

}
100
El Marcel

Douglas Crockford a fait une remarque sur la manière dont il souhaitait que JavaScript contienne une structure loop:

loop
{
  ...code...
}

Et je ne pense pas que Java serait encore pire si vous aviez une structure loop.

Il n'y a rien de fondamentalement faux avec les boucles while(true), mais il y a une tendance aux enseignants de les décourager. Du point de vue de l’enseignement, il est très facile de demander aux élèves de créer des boucles sans fin et de ne pas comprendre pourquoi cette boucle n’est jamais échappée.

Mais ce qu’ils mentionnent rarement, c’est que tous les mécanismes de bouclage peuvent être répliqués avec les boucles while(true).

while( a() )
{
  fn();
}

est le même que

loop
{
  if ( !a() ) break;
  fn();
}

et

do
{
  fn();
} while( a() );

est le même que:

loop
{
  fn();
  if ( !a() ) break;
}

et

for ( a(); b(); c() )
{
  fn();
}

est le même que:

a();
loop
{
  if ( !b() ) break;
  fn();
  c();
}

Tant que vous pouvez configurer vos boucles de manière à ce que fonctionne , la construction que vous choisissez d'utiliser soit sans importance. Si cela arrive pour tenir dans une boucle for, utilisez une boucle for.

Une dernière partie: gardez vos boucles simples. Si beaucoup de fonctionnalités doivent être remplies à chaque itération, mettez-les dans une fonction. Vous pouvez toujours l'optimiser après l'avoir fait fonctionner.

37
zzzzBov

En 1967, Dijkstra avait écrit un article dans un magazine spécialisé sur les raisons pour lesquelles il fallait éliminer les notions de base dans les langages de haut niveau afin d'améliorer la qualité du code. Tout un paradigme de programmation appelé "programmation structurée" en est issu, même si tout le monde n'est pas d'accord sur le fait que goto signifie automatiquement un mauvais code. Le noeud de la programmation structurée est essentiellement que la structure du code doit déterminer son flux plutôt que d'avoir des gotos ou des ruptures ou continue à déterminer un flux, dans la mesure du possible. De manière similaire, le fait d'avoir plusieurs points d'entrée et de sortie sur une boucle ou une fonction est également déconseillé dans ce paradigme. Évidemment, ce n'est pas le seul paradigme de programmation, mais il peut souvent être facilement appliqué à d'autres paradigmes tels que la programmation orientée objet (à la manière de Java). Vos professeurs ont probablement été formés et essaient de faire comprendre à votre classe que nous ferions mieux d'éviter le "code spaghetti" en veillant à ce que notre code soit structuré et en suivant les règles implicites de la programmation structurée. Bien qu’une implémentation utilisant break soit intrinsèquement "fausse", certains considèrent qu’il est beaucoup plus facile de lire du code lorsque la condition de la boucle est explicitement spécifiée dans la condition while () et élimine certaines possibilités d’être trop délicat. Il est certain que l’utilisation d’une condition while (vraie) qui semble apparaître fréquemment dans le code des programmeurs débutants, telle que le risque de créer accidentellement une boucle infinie ou de rendre le code difficile à lire ou à confondre inutilement.

Ironiquement, la gestion des exceptions est un domaine dans lequel une déviation par rapport à la programmation structurée sera certainement à prévoir à mesure que vous avancerez dans la programmation en Java.

Il est également possible que votre instructeur s’attende à ce que vous démontriez votre capacité à utiliser une structure ou une syntaxe de boucle particulière enseignée dans ce chapitre ou cette leçon de votre texte. Même si le code que vous avez écrit est fonctionnellement équivalent, vous n’avez peut-être pas démontré la compétence particulière que vous étiez censé apprendre dans cette leçon.

15
Jessica Brown

La convention d'usage Java pour la lecture des entrées est la suivante:

import Java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String strLine;

while ((strLine = br.readLine()) != null) {
  // do something with the line
}

Et la convention C++ habituelle pour lire les entrées est la suivante:

#include <iostream>
#include <string>
std::string data;
while(std::readline(std::cin, data)) {
  // do something with the line
}

Et en C, c'est

#include <stdio.h>
char* buffer = NULL;
size_t buffer_size;
size_t size_read;
while( (size_read = getline(&buffer, &buffer_size, stdin)) != -1 ){
  // do something with the line
}
free(buffer);

ou si vous êtes convaincu de savoir combien de temps dure la plus longue ligne de texte de votre fichier, vous pouvez le faire.

#include <stdio.h>
char buffer[BUF_SIZE];
while (fgets(buffer, BUF_SIZE, stdin)) {
  //do something with the line
}

Si vous testez pour voir si votre utilisateur a entré une commande quit, il est facile d'étendre l'une de ces 3 structures de boucle. Je vais le faire dans Java pour vous:

import Java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;

while ((line = br.readLine()) != null  && !line.equals("quit") ) {
  // do something with the line
}

Ainsi, bien qu'il y ait des cas où break ou goto soit justifié, si vous ne faites que lire un fichier ou la console ligne par ligne, vous ne devriez pas avoir besoin d'une while (true) boucle pour l'accomplir - votre langage de programmation vous a déjà fourni un idiome approprié pour utiliser la commande d'entrée en tant que condition de boucle.

14
Ken Bloom

Ce n'est pas si terrible, mais vous devez prendre en compte les autres développeurs lors de la programmation. Même à l'école.

Vos collègues développeurs devraient pouvoir voir la clause exit de votre boucle, à la déclaration de la boucle. Tu n'as pas fait ça. Vous avez caché la clause de sortie au milieu de la boucle, ce qui a rendu plus de travail pour quelqu'un d'autre qui essaye de comprendre votre code. C'est la même raison pour laquelle des choses comme "pause" sont évitées.

Cela étant dit, vous verrez toujours des choses comme celle-ci dans BEAUCOUP de code dans le monde réel.

12
rfeak

C'est ton arme, ta balle et ton pied ...

C'est mauvais parce que vous demandez des ennuis. Ce ne sont ni vous ni les autres affiches sur cette page qui ont des exemples de boucles courtes/simples.

Les problèmes commenceront à un moment très aléatoire dans le futur. Cela pourrait être causé par un autre programmeur. Ce pourrait être la personne qui installe le logiciel. Ce peut être l'utilisateur final.

Pourquoi? Je devais découvrir pourquoi une application 700K LOC commencerait à graver 100% du temps processeur jusqu'à ce que chaque processeur soit saturé. C'était une boucle incroyable (vraie). C'était gros et méchant, mais cela se résumait à:

x = read_value_from_database()
while (true) 
 if (x == 1)
  ...
  break;
 else if (x ==2)
  ...
  break;
and lots more else if conditions
}

Il n'y avait pas d'autre branche finale. Si la valeur ne correspond pas à une condition if, la boucle continue à s'exécuter jusqu'à la fin du temps.

Bien entendu, le programmeur a reproché aux utilisateurs finaux de ne pas avoir sélectionné la valeur attendue par le programmeur. (J'ai ensuite éliminé toutes les occurrences de while (true) dans le code.)

IMHO ce n'est pas une bonne programmation défensive d'utiliser des constructions comme while (true). Il reviendra vous hanter.

(Mais je me souviens que les professeurs ont déclassé si nous ne commentions pas chaque ligne, même pour i ++;)

11
jqa

Selon mon expérience, dans la plupart des cas, les boucles ont la condition "principale" pour continuer. C'est la condition dans laquelle l'opérateur while () doit être écrit. Toutes les autres conditions susceptibles de rompre la boucle sont secondaires, sans importance, etc. Elles peuvent être écrites sous la forme d'instructions if() {break} supplémentaires.

while(true) est souvent source de confusion et est moins lisible.

Je pense que ces règles ne couvrent pas 100% des cas mais probablement seulement 98%.

5
AlexR

C'est mauvais en ce sens que les constructions de programmation structurée sont préférées aux déclarations break et continue. Ils sont, par comparaison, préférés à "goto" selon ce principe.

Je recommanderais toujours de rendre votre code aussi structuré que possible ... même si, comme le souligne Jon Skeet, ne le rendez pas plus structuré!

5
Patrick87

Vous pouvez simplement utiliser un indicateur booléen pour indiquer quand mettre fin à la boucle while. Break et go to étaient des raisons pour lesquelles le logiciel était trop difficile à maintenir - la crise du logiciel (tm) - et devrait être évité, et peut facilement l'être aussi.

C'est une question de savoir si vous êtes pragmatique ou non. Les codeurs pragmatiques pourraient simplement utiliser break dans cette situation simple.

Mais il est bon d’avoir l’habitude de ne pas les utiliser, sinon vous pourriez les utiliser hors d’habbit dans des situations inappropriées, comme dans les boucles imbriquées complexes où la lisibilité et la maintenabilité de votre code deviennent plus difficiles en utilisant break.

3
Daniel Leschkowski

Bien que ce ne soit pas nécessairement une réponse quant à pourquoi ne pas utiliser while (true), j'ai toujours trouvé cette déclaration de l'auteur comique et qui l'accompagne une explication succincte de la raison pour laquelle il est préférable de faire tout le temps.

En ce qui concerne votre question: Il n'y a pas de problème inhérent avec

while(true) {
   do_stuff();
   if(exit_time) {
      break;
   }
}

... si vous savez ce que vous faites et vous vous assurez que exit_time sera évalué à un moment donné à true.

Les enseignants vous déconseillent d’utiliser while(true) car, à moins que vous sachiez exactement ce que vous faites, c’est un moyen facile de commettre une grave erreur.

3
Shadur

Je pense que oui, c'est assez mauvais ... ou du moins, pour beaucoup de développeurs. C'est symptomatique des développeurs qui ne pensent pas à leurs conditions de boucle. Par conséquent, il y a risque d'erreur.

3
Griff Sob Becker

Peut-être que je suis malchanceux. Ou peut-être que je manque juste d'expérience. Mais chaque fois que je me souviens d'avoir eu affaire à while(true) ayant break à l'intérieur, il était possible d'améliorer le code en utilisant Méthode d'extraction à en bloquant , qui a gardé la while(true) mais (par coïncidence?) a transformé tous les breaks en returns.

D'après mon expérience, while(true) sans pause (c'est-à-dire avec des retours ou des lancers) sont assez confortables et faciles à comprendre.


  void handleInput() {
      while (true) {
          final Input input = getSomeInput();
          if (input == null) {
              throw new BadInputException("can't handle null input");
          }
          if (input.isPoisonPill()) {
              return;
          }
          doSomething(input);
      }
  }
2
gnat

Il n'y a pas de problème majeur avec les commandes while(true) avec break, bien que certains puissent penser que cela réduit légèrement la lisibilité du code. Essayez de donner des noms significatifs aux variables, évaluez les expressions au bon endroit.

Pour votre exemple, il semble beaucoup plus clair de faire quelque chose comme:

do {
   input = get_input();
   valid = check_input_validity(input);    
} while(! valid)

Cela est particulièrement vrai si la boucle do while devient longue - vous savez exactement où vérifier si une itération supplémentaire est en cours. Toutes les variables/fonctions ont des noms appropriés au niveau de l'abstraction. L’instruction while(true) vous indique que le traitement n’est pas à la place que vous pensiez.

Peut-être que vous voulez une sortie différente la deuxième fois dans la boucle. Quelque chose comme

input = get_input();
while(input_is_not_valid(input)) {
    disp_msg_invalid_input();
    input = get_input();
}

semble plus lisible pour moi alors

do {
    input = get_input();
    if (input_is_valid(input)) {
        break;
    }
    disp_msg_invalid_input();
} while(true);

Encore une fois, avec un exemple trivial, les deux sont assez lisibles; mais si la boucle devient très grande ou profondément imbriquée (ce qui signifie que vous devriez probablement déjà avoir refactorisé), le premier style peut être un peu plus clair.

2
dr jimbob

Pour moi, le problème est la lisibilité.

Une instruction while avec une condition vraie ne vous dit rien sur la boucle. Cela rend le travail de compréhension beaucoup plus difficile.

Quoi de plus facile à comprendre à partir de ces deux extraits?

do {
  // Imagine a Nice chunk of code here
} while(true);

do {
  // Imagine a Nice chunk of code here
} while(price < priceAllowedForDiscount);
1
Vince Panuccio

C’est plus une question d’esthétique, beaucoup plus facile à lire du code où vous savez explicitement pourquoi la boucle s’arrête juste dans la déclaration de la boucle.

1
Petey B

J'utilise quelque chose de similaire, mais avec une logique opposée, dans beaucoup de mes fonctions.

DWORD dwError = ERROR_SUCCESS;

do
{
    if ( (dwError = SomeFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }

    if ( (dwError = SomeOtherFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }
}
while ( 0 );

if ( dwError != ERROR_SUCCESS )
{
    /* resource cleanup */
}
1
Jim Fell

Je dirais qu'en général, la raison pour laquelle ce n'est pas considéré comme une bonne idée est que vous n'utilisez pas la construction à son plein potentiel. De plus, j'ai tendance à penser que beaucoup d'enseignants en programmation n'aiment pas que leurs étudiants arrivent avec "des bagages". J'entends par là que je pense qu'ils aiment être la principale influence sur le style de programmation de leurs étudiants. Alors peut-être que ce n'est qu'une bête noire de l'instructeur.

1
Paul

1) Rien ne va pas avec un do -while(true)

2) Votre professeur a tort.

NSFS !!:

3) La plupart des enseignants sont des enseignants et non des programmeurs.

0
Pacerier

Cela peut être mauvais si votre boucle s'exécute sur un thread d'arrière-plan. Ainsi, lorsque vous fermez votre application en mettant fin à un thread d'interface utilisateur, ce morceau de code continue de s'exécuter. Comme d'autres l'ont déjà dit, vous devriez toujours utiliser une sorte de chèque pour permettre l'annulation.

0
Dmitry

J'imagine qu'utiliser une pause chez votre professeur, c'est comme casser une branche d'arbre pour obtenir le fruit. Utilisez d'autres astuces (inclinez la branche) pour obtenir le fruit et la branche est toujours en vie. :)

0
Bimal Sharma