web-dev-qa-db-fra.com

tandis que (vrai) et rupture de boucle - anti-modèle?

Considérez le code suivant:

public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

Supposons que ce processus implique un nombre d'étapes fini mais dépendant de l'entrée; la boucle est conçue pour se terminer d'elle-même à la suite de l'algorithme et n'est pas conçue pour s'exécuter indéfiniment (jusqu'à ce qu'elle soit annulée par un événement extérieur). Parce que le test pour voir si la boucle doit se terminer est au milieu d'un ensemble logique d'étapes, la boucle while elle-même ne vérifie actuellement rien de significatif; la vérification est effectuée à la place "appropriée" dans l'algorithme conceptuel.

On m'a dit que c'est du mauvais code, car il est plus sujet aux bogues car la condition de fin n'est pas vérifiée par la structure de la boucle. Il est plus difficile de comprendre comment vous sortiriez de la boucle et pourrait inviter des bogues car la condition de rupture pourrait être contournée ou omise accidentellement en raison de changements futurs.

Maintenant, le code pourrait être structuré comme suit:

public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

Cependant, cela duplique un appel à une méthode dans le code, violant DRY; si TransformInSomeWay était plus tard remplacé par une autre méthode, les deux appels devraient être trouvés et modifiés (et le fait qu'il y en ait deux soit moins évident dans un morceau de code plus complexe).

Vous pouvez également l'écrire comme:

public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

... mais vous avez maintenant une variable dont le seul but est de déplacer la vérification de condition vers la structure de la boucle, et doit également être vérifiée plusieurs fois pour fournir le même comportement que la logique d'origine.

Pour ma part, je dis qu'étant donné l'algorithme que ce code implémente dans le monde réel, le code original est le plus lisible. Si vous le traversiez vous-même, c'est ainsi que vous le penseriez, et il serait donc intuitif pour les personnes familiarisées avec l'algorithme.

Alors, qui est "mieux"? vaut-il mieux confier la responsabilité de la vérification de l'état à la boucle while en structurant la logique autour de la boucle? Ou est-il préférable de structurer la logique de manière "naturelle" comme indiqué par les exigences ou une description conceptuelle de l'algorithme, même si cela peut signifier contourner les capacités intégrées de la boucle?

32
KeithS

Restez avec votre première solution. Les boucles peuvent devenir très impliquées et une ou plusieurs coupures nettes peuvent être beaucoup plus faciles pour vous, quiconque regarde votre code, l'optimiseur et les performances du programme que d'élaborer des instructions if et des variables ajoutées. Mon approche habituelle est de mettre en place une boucle avec quelque chose comme "while (true)" et d'obtenir le meilleur codage possible. Souvent, je peux et je remplace alors les ruptures par un contrôle de boucle standard; la plupart des boucles sont assez simples, après tout. La condition de fin devrait être vérifiée par la structure de la boucle pour les raisons que vous mentionnez, mais pas au prix d'ajouter de nouvelles variables entières, d'ajouter des instructions if et de répéter du code, qui sont même plus sujets aux bogues.

14
RalphChapin

Ce n'est un problème significatif que si le corps de la boucle n'est pas trivial. Si le corps est si petit, alors l'analyser comme ça est trivial, et pas un problème du tout.

13
DeadMG

J'ai du mal à trouver les autres solutions mieux que celle avec break. Bon, je préfère la voie Ada qui permet de faire

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

mais la solution de rupture est le rendu le plus propre.

Mon POV est que lorsqu'il manque une structure de contrôle, la solution la plus propre est de l'imiter avec d'autres structures de contrôle, sans utiliser de flux de données. (Et de même, lorsque j'ai un problème de flux de données, je préfère des solutions de flux de données pures à celle impliquant des structures de contrôle *).

Il est plus difficile de savoir comment sortir de la boucle,

Ne vous méprenez pas, la discussion n'aurait pas lieu si la difficulté n'était pas essentiellement la même: la condition est la même et présente au même endroit.

Lequel de

while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

et

while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

rendre mieux la structure prévue? Je vote pour le premier, vous n'avez pas à vérifier que les conditions correspondent. (Mais voyez mon indentation).

7
AProgrammer

tandis que (vrai) et break est un idiome commun. C'est la manière canonique d'implémenter des boucles de mi-sortie dans des langages de type C. Ajouter une variable pour stocker la condition de sortie et un if () autour de la seconde moitié de la boucle est une alternative raisonnable. Certains magasins peuvent uniformiser officiellement l'un ou l'autre, mais aucun n'est supérieur; ils sont équivalents.

Un bon programmeur travaillant dans ces langages devrait être capable de savoir ce qui se passe en un coup d'œil s'il voit l'un ou l'autre. Cela pourrait faire une bonne petite question d'entrevue; remettez à quelqu'un deux boucles, une en utilisant chaque idiome, et demandez-leur quelle est la différence (bonne réponse: "rien", avec peut-être un ajout de "mais j'utilise habituellement CELA.")

3
mjfgates

Vos alternatives ne sont pas aussi mauvaises que vous ne le pensez. Un bon code devrait:

  1. Indiquez clairement avec de bons noms de variables/commentaires dans quelles conditions la boucle doit être terminée. (La condition de rupture ne doit pas être cachée)

  2. Indiquez clairement l'ordre des opérations. C'est là que placer la 3e opération optionnelle (DoSomethingElseTo(input)) à l'intérieur du if est une bonne idée.

Cela dit, il est facile de laisser les instincts perfectionnistes et de polissage du laiton prendre beaucoup de temps. Je voudrais juste modifier le si comme mentionné ci-dessus; commentez le code et continuez.

2
Pat

Les gens disent que c'est une mauvaise pratique d'utiliser while (true), et la plupart du temps, ils ont raison. Si votre instruction while contient beaucoup de code compliqué et qu'il n'est pas clair quand vous sortez de if, alors vous vous retrouvez avec du code difficile à maintenir et un simple bogue pourrait provoquer une boucle infinie.

Dans votre exemple, vous avez raison d'utiliser while(true) car votre code est clair et facile à comprendre. Il ne sert à rien de créer du code inefficace et plus difficile à lire, simplement parce que certaines personnes considèrent toujours que c'est une mauvaise pratique d'utiliser while(true).

J'étais confronté à un problème similaire:

$i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

L'utilisation de do ... while(true) évite que isset($array][$key] Soit appelé deux fois inutilement, le code est plus court, plus propre et plus facile à lire. Il n'y a absolument aucun risque qu'un bogue de boucle infinie soit introduit avec else break; À la fin.

$i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

Il est bon de suivre bonnes pratiques et d'éviter mauvaises pratiques mais il y a toujours des exceptions et il est important de garder l'esprit ouvert et de réaliser ces exceptions.

2
Dan Bray

Si un flux de contrôle particulier s'exprime naturellement sous la forme d'une instruction break, il n'est pratiquement jamais préférable d'utiliser d'autres mécanismes de contrôle de flux pour émuler une instruction break.

(notez que cela comprend le déroulement partiel de la boucle et le glissement du corps de la boucle vers le bas de sorte que la boucle commence coïncide avec le test conditionnel)

Si vous pouvez réorganiser votre boucle afin que le flux de contrôle s'exprime naturellement en ayant une vérification conditionnelle au début de la boucle, tant mieux. Cela vaut peut-être même la peine de réfléchir à la possibilité. Mais non, n'essayez pas de placer une cheville carrée dans un trou rond.

0
user122173

Lorsque la complexité de la logique devient difficile à utiliser, l'utilisation d'une boucle sans limite avec des interruptions de mi-boucle pour le contrôle de flux est en fait la plus logique. J'ai un projet avec du code qui ressemble à ceci. (Remarquez l'exception à la fin de la boucle.)

L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

Pour faire cette logique sans rupture de boucle illimitée, je me retrouverais avec quelque chose comme ceci:

void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

Ainsi, l'exception est maintenant levée dans une méthode d'assistance. Il a également beaucoup de if ... else imbrication. Je ne suis pas satisfait de cette approche. Par conséquent, je pense que le premier modèle est le meilleur.

0
intrepidis

Vous pouvez également aller avec ceci:

public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

Ou moins confus:

bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}
0
Eclipse