web-dev-qa-db-fra.com

À l'intérieur d'une boucle for, dois-je déplacer la condition de rupture dans le champ de condition si possible?

Parfois, j'ai besoin de boucles qui nécessitent une pause comme celle-ci:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

Je suis mal à l'aise d'écrire

if(condition){
    break;
}

car il consomme 3 lignes de code. Et j'ai trouvé que la boucle peut être réécrite comme:

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

Ma question est donc la suivante: est-ce une bonne pratique de déplacer la condition dans le champ de condition pour réduire les lignes de codes si possible?

48
ocomfd

Ces deux exemples que vous avez donnés ne sont pas fonctionnellement équivalents. Dans l'original, le test de condition est effectué après la section "un autre code", alors que dans la version modifiée, il est effectué en premier, au début du corps de la boucle.

Le code ne doit jamais être réécrit dans le seul but de réduire le nombre de lignes. Évidemment, c'est un bon bonus quand cela fonctionne de cette façon, mais cela ne devrait jamais être fait au détriment de la lisibilité ou de l'exactitude.

154
Pete

Je n'achète pas l'argument selon lequel "il consomme 3 lignes de code" et est donc mauvais. Après tout, vous pouvez simplement l'écrire comme suit:

if (condition) break;

et consomme juste une ligne.

Si le if apparaît à mi-chemin dans la boucle, il doit bien sûr exister en tant que test séparé. Cependant, si ce test apparaît à la fin du bloc, vous avez créé une boucle qui a un test de continuation aux deux extrémités de la boucle, ajoutant éventuellement à la complexité du code. Sachez cependant que parfois le test if (condition) n'a de sens qu'après l'exécution du bloc.

Mais en supposant que ce n'est pas le cas, en adoptant l'autre approche:

for(int i=0;i<array.length && !condition;i++){
    //some other code
}

les conditions de sortie sont maintenues ensemble, ce qui peut simplifier les choses (surtout si "n autre code" est long).

Il n'y a pas de bonne réponse ici bien sûr. Soyez donc conscient des idiomes du langage, des conventions de codage de votre équipe, etc.

51
David Arno

Ce genre de question a suscité un débat presque aussi longtemps que la programmation a été en cours. Pour jeter mon chapeau dans le ring, j'opterais pour la version avec la condition break et voici pourquoi (pour ce ( cas spécifique ) ):

La boucle for est juste là pour spécifier les valeurs d'itération dans la boucle. Le codage de la condition de rupture à l'intérieur de ce qui brouille juste la logique IMO.

Le fait d'avoir un break distinct indique clairement que pendant le traitement normal, les valeurs sont telles que spécifiées dans la boucle for et le traitement doit continuer sauf où la condition intervient.

En guise de contexte, j'ai commencé à coder un break, puis j'ai mis la condition dans la boucle for pendant des années (sous la direction d'un programmeur exceptionnel), puis je suis retourné au break style car il semblait juste plus propre (et c'était le style dominant dans les différents tomes de code que je consommais).

C'est une autre de ces guerres sacrées à la fin de la journée - certains disent que vous devriez jamais sortir artificiellement d'une boucle, sinon ce n'est pas une vraie boucle. Faites tout ce qui vous semble lisible (pour vous et pour les autres). Si la réduction des frappes est vraiment votre truc, allez jouer au golf le week-end - bière en option.

49
Robbie Dee

for les boucles sont pour l'itération sur quelque chose1 - ils ne sont pas seulement lol-allons-tirer-quelques-choses-aléatoires-du-corps-et-les-mettre-dans-la-boucle-en-tête - les trois expressions ont des significations très spécifiques:

  • Le premier sert à initialiser le itérateur. Il peut s'agir d'un index, d'un pointeur, d'un objet d'itération ou autre - tant qu'il est utilisé pour itération.
  • La seconde est de vérifier si nous sommes arrivés à la fin.
  • Le troisième est pour faire avancer l'itérateur.

L'idée est de séparer la gestion des itérations ( comment itérer) de la logique à l'intérieur de la boucle ( quoi à faire à chaque itération). De nombreuses langues ont généralement une boucle pour chaque qui vous soulage des détails de l'itération, mais même si votre langue n'a pas cette construction, ou si elle ne peut pas être utilisée dans votre scénario - vous devez toujours limiter l'en-tête de la boucle à la gestion des itérations.

Donc, vous devez vous demander - votre condition concerne-t-elle la gestion des itérations ou la logique à l'intérieur de la boucle? Il y a de fortes chances que cela concerne la logique à l'intérieur de la boucle - elle doit donc être vérifiée à l'intérieur de la boucle plutôt que dans l'en-tête.

1Contrairement à d'autres boucles, qui "attendent" que quelque chose se passe. Une boucle for/foreach devrait avoir le concept d'une "liste" d'éléments sur lesquels itérer - même si cette liste est paresseuse ou infinie ou abstraite.

43
Idan Arye

Je recommande d'utiliser la version avec if (condition). Il est plus lisible et plus facile à déboguer. L'écriture de 3 lignes de code supplémentaires ne vous cassera pas les doigts, mais facilitera la compréhension de votre code par la prochaine personne.

8
Aleksander

Si vous parcourez une collection où certains éléments doivent être ignorés, je recommande une nouvelle option:

  • Filtrez votre collection avant d'itérer

Les deux Java et C # rendent cela relativement simple à faire. Je ne serais pas surpris si C++ avait un moyen de créer un itérateur sophistiqué pour ignorer certains éléments qui ne s'appliquent pas. Mais ce n'est que vraiment une option si la langue de votre choix le prend en charge, et la raison pour laquelle vous utilisez break est parce qu'il y a des conditions selon que vous traitez un élément ou non.

Sinon, il y a une très bonne raison de faire de votre condition une partie de la boucle for - en supposant que son évaluation initiale est correcte.

  • Il est plus facile de voir quand une boucle se termine tôt

J'ai travaillé sur du code où votre boucle for prend quelques centaines de lignes avec plusieurs conditions et des calculs complexes en cours. Les problèmes que vous rencontrez avec ceci sont:

  • break les commandes enfouies dans la logique sont difficiles à trouver et parfois surprenantes
  • Le débogage nécessite de parcourir chaque itération de la boucle pour vraiment comprendre ce qui se passe
  • Une grande partie du code ne dispose pas de refactorisations facilement réversibles ou de tests unitaires pour aider à l'équivalence fonctionnelle après une réécriture

Dans cette situation, je recommande les directives suivantes par ordre de préférence:

  • Filtrez la collection pour éliminer le besoin d'un break si possible
  • Rendre la partie conditionnelle des conditions de boucle for
  • Placez les conditions avec le break en haut de la boucle si possible
  • Ajoutez un commentaire sur plusieurs lignes attirant l'attention sur la coupure et pourquoi elle est là

Ces directives sont toujours soumises à la exactitude de votre code. Choisissez l'option qui représente le mieux l'intention et améliorez la clarté de votre code.

8
Berin Loritsch

Je recommande fortement d'adopter l'approche la moindre surprise sauf si vous gagnez un avantage significatif en faisant autrement.

Les gens ne lisent pas chaque lettre lors de la lecture d'un mot, et ils ne lisent pas chaque mot lors de la lecture d'un livre - s'ils sont capables de lire, ils regardent les contours des mots et des phrases et laissent leur cerveau remplir le reste .

Il est donc probable que le développeur occasionnel suppose que ce n'est qu'une norme pour la boucle et ne le regarde même pas:

for(int i=0;i<array.length&&!condition;i++)

Si vous voulez utiliser ce style malgré tout, je vous recommande de changer les parties for(int i=0;i< et ;i++) qui indique au cerveau du lecteur qu'il s'agit d'une norme pour la boucle.


Une autre raison d'aller avec if-break est que vous ne pouvez pas toujours utiliser votre approche avec la condition for-. Vous devez utiliser if-break lorsque la condition de rupture est trop complexe pour être masquée dans une instruction for, ou s'appuie sur des variables qui ne sont accessibles qu'à l'intérieur de for boucle.

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

Donc, si vous décidez d'aller avec if-break, vous êtes couvert. Mais si vous décidez d'utiliser des conditions for-, vous devez utiliser un mélange de conditions if-break et for-. Pour être cohérent, vous devrez déplacer les conditions d'avant en arrière entre les conditions for- et les if-break chaque fois que les conditions changent .

8
Peter

Votre transformation suppose que tout ce que condition est évalué à true lorsque vous entrez dans la boucle. Nous ne pouvons pas commenter l'exactitude de cela dans ce cas, car il n'est pas défini.

Après avoir réussi à "réduire les lignes de code" ici, vous pouvez ensuite regarder

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

et appliquer la même transformation, arriver à

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

Ce qui est une transformation invalide, comme vous le faites maintenant sans condition further code là où vous ne l'aviez pas fait auparavant.

Un schéma de transformation beaucoup plus sûr consiste à extraire some other code et évaluer condition dans une fonction distincte

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}
4
Caleth

TL; DR Faites ce qui se lit le mieux dans votre situation particulière

Prenons cet exemple:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

Dans ce cas, nous ne voulons pas que le code de la boucle for s'exécute si something est vrai, il est donc raisonnable de déplacer le test de something dans le champ de condition.

for ( int i = 1; i < array.length && !something; i++ )

Là encore, selon que something peut être défini sur true avant la boucle, mais pas à l'intérieur, cela pourrait offrir plus de clarté:

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

Imaginez maintenant ceci:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

Nous envoyons là un message très clair. Si something devient vrai pendant le traitement du tableau, abandonnez alors toute l'opération à ce stade. Pour déplacer le chèque dans le champ de condition, vous devez:

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

On peut dire que le "message" est devenu confus, et nous vérifions la condition de défaillance à deux endroits - et si la condition de défaillance change et que nous oublions l'un de ces endroits?

Il existe bien sûr de nombreuses formes différentes pour une boucle et une condition, toutes avec leurs propres nuances.

Écrivez le code pour qu'il se lise bien (c'est évidemment quelque peu subjectif) et de manière concise. En dehors d'un ensemble draconien de directives de codage tous "les règles" ont des exceptions. Écrivez ce que vous pensez qui exprime le mieux la prise de décision et minimisez les risques d'erreurs futures.

4
Grimm The Opiner

Bien que cela puisse être intéressant car vous voyez toutes les conditions dans l'en-tête de la boucle et break peut être source de confusion à l'intérieur de grands blocs, une boucle for est un modèle où la plupart des programmeurs s'attendent à boucler sur une certaine plage.

D'autant plus que vous le faites, l'ajout d'une condition de rupture supplémentaire peut provoquer de la confusion et il est plus difficile à voir s'il est fonctionnellement équivalent.

Souvent, une bonne alternative consiste à utiliser un while ou do {} while boucle qui vérifie les deux conditions, en supposant que votre tableau a au moins un élément.

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

En utilisant une boucle qui vérifie uniquement une condition et n'exécute pas de code à partir de l'en-tête de boucle, vous indiquez très clairement quand la condition est vérifiée et quelle est la condition réelle et quel est le code avec des effets secondaires.

2
allo

À mon avis, peu de raisons disent que vous ne devriez pas.

  1. Cela réduit la lisibilité.
  2. La sortie peut ne pas être identique. Cependant, cela peut être fait avec un do...while boucle (pas while) car la condition est vérifiée après une exécution de code.

Mais en plus de cela, considérez ceci,

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

Maintenant, pouvez-vous vraiment y parvenir en ajoutant ces conditions dans cette condition for?

1
Harsh

C'est mauvais, car le code est destiné à être lu par les humains - les boucles non idiomatiques for sont souvent déroutantes à lire, ce qui signifie que les bogues sont plus susceptibles de se cacher ici, mais le code d'origine a peut-être été possible à améliorer tout en le gardant à la fois court et lisible.

Si vous voulez trouver une valeur dans un tableau (l'exemple de code fourni est quelque peu générique, mais il peut tout aussi bien s'agir de ce modèle), vous pouvez simplement essayer explicitement d'utiliser une fonction fournie par un langage de programmation spécifiquement à cette fin. Par exemple (pseudo-code, aucun langage de programmation n'ayant été spécifié, les solutions varient en fonction d'un langage particulier).

array.find(item -> item.valid)

Cela devrait sensiblement raccourcir la solution tout en la simplifiant car votre code indique précisément ce dont vous avez besoin.

1
Konrad Borowski

Si vous craignez qu'une instruction for trop complexe soit difficile à lire, c'est certainement une préoccupation valable. Le gaspillage d'espace vertical est également un problème (quoique moins critique).

(Personnellement, je n'aime pas la règle selon laquelle les instructions if doivent toujours avoir des accolades, ce qui est en grande partie la cause de l'espace vertical gaspillé ici. Notez que le problème est gaspillage espace vertical. tiliser l'espace vertical avec des lignes significatives ne devrait pas être un problème.)

Une option consiste à le diviser en plusieurs lignes. C'est certainement ce que vous devez faire si vous utilisez des instructions for très complexes, avec plusieurs variables et conditions. (C'est pour moi une meilleure utilisation de l'espace vertical, en donnant autant de lignes que possible quelque chose de significatif.)

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

Une meilleure approche pourrait être d'essayer d'utiliser une forme plus déclarative et moins impérative. Cela variera selon la langue, ou vous devrez peut-être écrire vos propres fonctions pour activer ce genre de chose. Cela dépend également de ce que condition est réellement. Vraisemblablement, il doit pouvoir changer d'une manière ou d'une autre, en fonction de ce qui se trouve dans le tableau.

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void
0
Dave Cousineau

Oui, mais votre syntaxe n'est pas conventionnelle et donc mauvaise (tm)

J'espère que votre langue prend en charge quelque chose comme

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}
0
Ewan

Une chose qui a été oubliée: lorsque je romps une boucle (ne fais pas toutes les itérations possibles, quelle que soit la façon dont elles sont implémentées), il est généralement vrai que non seulement je veux quitter la boucle, mais je veux aussi savoir pourquoi cela s'est produit . Par exemple, si je cherche un élément X, non seulement je romps avec la boucle, mais je veux aussi savoir que X a été trouvé et où il se trouve.

Dans cette situation, avoir une condition en dehors de la boucle est tout à fait naturel. Je commence donc par

bool found = false;
size_t foundIndex;

et dans la boucle j'écrirai

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

avec la boucle for

for (i = 0; i < something && ! found; ++i)

Maintenant, vérifier la condition dans la boucle est assez naturel. La présence d'une instruction "break" dépend de la question de savoir s'il y a un traitement supplémentaire dans la boucle que vous souhaitez éviter. Si un traitement est nécessaire et qu'un autre ne l'est pas, vous pourriez avoir quelque chose comme

if (! found) { ... }

quelque part dans votre boucle.

0
gnasher729

Je pense que la suppression de break peut être une bonne idée, mais pas pour enregistrer des lignes de code.

L'avantage de l'écrire sans break est que la post-condition de la boucle est évidente et explicite. Lorsque vous avez terminé la boucle et exécuté la ligne suivante du programme, votre condition de boucle i < array.length && !condition devait être faux. Par conséquent, soit i était supérieur ou égal à la longueur de votre tableau, soit condition est vrai.

Dans la version avec break, il y a un problème caché. La condition de boucle dit que la boucle se termine lorsque vous avez exécuté un autre code pour chaque index de tableau valide, mais il y a en fait, une instruction break qui pourrait terminer la boucle avant cela, et vous ne la verrez que si vous examinez très attentivement le code de la boucle. Et les analyseurs statiques automatisés, y compris l'optimisation des compilateurs, pourraient également avoir du mal à déduire les post-conditions de la boucle.

C'est la raison pour laquelle Edgar Dijkstra a écrit "Goto considéré comme nuisible". Ce n'était pas une question de style: sortir d'une boucle rend difficile de raisonner formellement sur l'état du programme. J'ai écrit plein de break; déclarations dans ma vie, et une fois en tant qu'adulte, j'ai même écrit un goto (pour sortir de plusieurs niveaux d'une boucle interne critique et continuer avec une autre branche de l'arbre de recherche). Cependant, je suis beaucoup plus susceptible d'écrire une instruction conditionnelle return à partir d'une boucle (qui n'exécute jamais le code après la boucle et ne peut donc pas masquer ses post-conditions) qu'un break .

Postscript

En fait, la refactorisation du code pour donner le même comportement peut en fait nécessiter plus de lignes de code. Votre refactoring pourrait être valide pour certains autres codes ou si nous pouvons prouver que condition commencera faux. Mais votre exemple équivaut dans le cas général à:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

Si un autre code n'est pas à une ligne, vous pouvez alors le déplacer vers une fonction afin de ne pas vous répéter, puis vous avez presque refactorisé votre boucle dans une fonction récursive de queue.

Je reconnais que ce n'était pas le point principal de votre question, cependant, et vous restiez simple.

0
Davislor