Cela justifie-t-il les déclarations goto?

15

Je suis tombé sur cette question il y a une seconde, et j'en retire une partie du matériel: y a- t-il un nom pour la construction «break n»?

Cela semble être un moyen inutilement complexe pour les gens d'avoir à demander au programme de sortir d'une boucle for double-nested:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

Je sais que les manuels aiment dire que les déclarations goto sont le diable, et je ne les aime pas du tout moi-même, mais cela justifie-t-il une exception à la règle?

Je cherche des réponses qui traitent des boucles n-imbriquées.

REMARQUE: Que vous répondiez oui, non ou quelque part entre les deux, les réponses totalement étroites ne sont pas les bienvenues. Surtout si la réponse est non, fournissez une bonne raison légitime (ce qui n'est pas trop loin de la réglementation Stack Exchange de toute façon).

Panzercrisis
la source
2
Si vous pouvez écrire la condition de la boucle interne, for (j = 10; j > 0 && !broken; j--)vous n'aurez pas besoin de l' break;instruction. Si vous déplacez la déclaration de brokento avant que la boucle externe ne commence, alors si cela peut être écrit comme cela, for (i = 0; i < 10 && !broken; i++)je pense qu'aucun break; s n'est nécessaire.
FrustratedWithFormsDesigner
8
Étant donné que l'exemple provient de C # - la référence C # sur goto: goto (référence C #) - "L'instruction goto est également utile pour sortir des boucles profondément imbriquées."
OMG, je n'ai jamais su que c # avait une déclaration goto! Les choses que vous apprenez sur StackExchange. (Je ne me souviens pas de la dernière fois que j'ai souhaité une capacité de déclaration goto, cela ne semble jamais arriver).
John Wu
6
À mon humble avis, cela dépend vraiment de votre "religion de programmation" et de la phase de lune actuelle. Pour moi (et la plupart des gens autour de moi), c'est bien d'utiliser un goto pour sortir des nids profonds si la boucle est plutôt petite, et l'alternative serait une vérification superflue d'une variable booléenne introduite juste pour que la condition de boucle se brise. Cela est d'autant plus vrai lorsque vous souhaitez vous évader à mi-chemin dans le corps, ou lorsque, après la boucle la plus intérieure, il existe un code qui devrait également vérifier le bool.
PlasmaHH
1
@JohnWu gotoest en fait requis dans un commutateur C # pour passer à un autre cas après avoir exécuté un code à l'intérieur du premier cas. C'est le seul cas où j'ai utilisé goto, cependant.
Dan Lyons

Réponses:

33

Le besoin apparent d'une instruction go-to vient de votre choix d'expressions conditionnelles médiocres pour les boucles .

Vous déclarez que vous vouliez que la boucle externe continue aussi longtemps i < 10et la boucle la plus interne se poursuive aussi longtemps j > 0.

Mais en réalité, ce n'est pas ce que vous vouliez , vous n'avez tout simplement pas dit aux boucles l'état réel que vous vouliez qu'elles évaluent, puis vous voulez le résoudre en utilisant break ou goto.

Si vous dites aux boucles vos véritables intentions pour commencer , alors pas besoin de pauses ou de goto.

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}
Tulains Córdova
la source
Voir mes commentaires sur les autres réponses. Le code original s'imprime ià la fin de la boucle externe, donc court-circuiter l'incrément est important pour rendre le code 100% équivalent. Je ne dis rien dans votre réponse est faux, je voulais juste souligner que rompre la boucle immédiatement était un aspect important du code.
pswg
1
@pswg Je ne vois pas d'impression de code ià la fin de la boucle externe dans la question OP.
Tulains Córdova
OP a référencé le code de ma question ici , mais a omis cette partie. Je ne t'ai pas déçu, BTW. La réponse est entièrement correcte en ce qui concerne le choix correct des conditions de boucle.
pswg
3
@pswg Je suppose que si vous voulez vraiment vraiment vraiment justifier un goto, vous pouvez résoudre un problème qui en a besoin, d'autre part, je programme professionnellement depuis deux décennies et je n'ai pas un seul problème réel qui nécessite un.
Tulains Córdova
1
Le but du code n'était pas de fournir un cas d'utilisation valide pour goto(je suis sûr qu'il y en a de meilleurs), même s'il a engendré une question qui semble l'utiliser en tant que telle, mais pour illustrer l'action de base de break(n)dans des langues qui ne l'ont pas été. t le soutenir.
pswg
34

Dans ce cas, vous pouvez refactoriser le code dans une routine distincte, puis uniquement à returnpartir de la boucle interne. Cela annulerait la nécessité de sortir de la boucle extérieure:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}
Roger
la source
2
C'est-à-dire que oui, ce que vous montrez est une façon inutilement complexe de rompre avec les boucles imbriquées, mais non, il existe un moyen de refactoriser qui ne rend pas un goto plus attrayant.
Roger
2
Juste une remarque: dans le code d'origine, il s'imprime iaprès la fin de la boucle extérieure. Donc, pour vraiment refléter cela, ic'est la valeur importante, ici le return true;et return false;devrait être les deux return i;.
pswg
+1, était sur le point de publier cette réponse, et je pense que cela devrait être la réponse acceptée. Je ne peux pas croire que la réponse acceptée ait été acceptée parce que cette méthode ne fonctionne que pour un sous-ensemble spécifique d'algorithmes de boucle imbriquée, et ne rend pas nécessairement le code beaucoup plus propre. La méthode de cette réponse fonctionne généralement.
Ben Lee
6

Non, je ne pense pas qu'un gotosoit nécessaire. Si vous l'écrivez comme ceci:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

Avoir &&!brokensur les deux boucles vous permet de sortir des deux boucles quand i==j.

Pour moi, cette approche est préférable. Les conditions de boucle sont extrêmement claires: i<10 && !broken.

Une version de travail peut être consultée ici: http://rextester.com/KFMH48414

Code:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Production:

la boucle se brise lorsque i == j == 1
2
0
FrustratedWithFormsDesigner
la source
Pourquoi utilisez-vous même brokendans votre premier exemple du tout ? Utilisez simplement la pause sur la boucle intérieure. De plus, si vous devez absolument l'utiliser broken, pourquoi le déclarer en dehors des boucles? Déclarez-le simplement dans la iboucle. Quant à la whileboucle, veuillez ne pas l'utiliser. Je pense honnêtement qu'elle parvient à être encore moins lisible que la version goto.
luiscubal
@luiscubal: Une variable nommée brokenest utilisée parce que je n'aime pas utiliser l' breakinstruction (sauf dans switch-case, mais c'est tout). Il est déclaré en dehors de la boucle externe au cas où vous voudriez casser TOUTES les boucles quand i==j. Vous avez raison, en général, il brokensuffit de déclarer avant la boucle la plus externe qu'il se cassera.
FrustratedWithFormsDesigner
Dans votre mise i==2à jour, à la fin de la boucle. La condition de réussite doit également court-circuiter l'incrément s'il doit être équivalent à 100% à a break 2.
pswg
2
Personnellement, je préfère broken = (j == i)plutôt que le if et, si le langage le permet, de mettre la déclaration cassée à l'intérieur de l'initialisation de la boucle externe. Bien qu'il soit difficile de dire ces choses pour cet exemple spécifique, car la boucle entière est un no-op (elle ne renvoie rien et n'a aucun effet secondaire) et si elle fait quelque chose, elle le fait peut-être à l'intérieur du if (bien que j'aime toujours broken = (j ==i); if broken { something(); }mieux personnellement)
Martijn
@Martijn Dans mon code d'origine, il s'imprime iaprès la fin de la boucle externe. En d'autres termes, la seule réflexion vraiment importante est quand exactement elle se détache de la boucle extérieure.
pswg
3

La réponse courte est "ça dépend". Je préconise de choisir en ce qui concerne la lisibilité et la compréhensibilité de votre code. Le chemin d'accès peut être plus lisible que de modifier la condition, ou vice versa. Vous pouvez également prendre en compte le principe de la moindre surprise, les directives utilisées dans la boutique / le projet et la cohérence de la base de code. Par exemple, goto pour quitter le commutateur ou pour la gestion des erreurs est une pratique courante dans la base de code du noyau Linux. Notez également que certains programmeurs peuvent avoir des problèmes de lecture de votre code (soit soulevé avec le mantra "goto is evil", ou tout simplement pas appris parce que leur professeur le pense) et certains d'entre eux pourraient même "vous juger".

D'une manière générale, un aller plus loin dans la même fonction est souvent acceptable, surtout si le saut n'est pas trop long. Votre cas d'utilisation de quitter une boucle imbriquée est l'un des exemples connus de goto "pas si mal".

FabienAndre
la source
2

Dans votre cas, goto est une amélioration suffisante pour paraître tentante, mais ce n'est pas tant une amélioration qu'il vaut la peine d'enfreindre la règle contre leur utilisation dans votre base de code.

Pensez à votre futur critique: il devra penser: "Est-ce que cette personne est un mauvais codeur ou est-ce en fait un cas où le goto en valait la peine? Permettez-moi de prendre 5 minutes pour décider par moi-même ou faire une recherche sur le l'Internet." Pourquoi diable feriez-vous cela à votre futur codeur alors que vous ne pouvez pas utiliser goto entièrement?

En général, cette mentalité s'applique à tout "mauvais" code et le définit même: si cela fonctionne maintenant, et est bon dans ce cas, mais crée des efforts pour vérifier qu'il est correct, ce qui à cette époque un goto fera toujours, alors ne le faites pas fais le.

Cela étant dit, oui, vous avez produit l'un des cas légèrement plus épineux. Tu peux:

  • utiliser des structures de contrôle plus complexes, comme un drapeau booléen, pour sortir des deux boucles
  • mettre votre boucle intérieure dans sa propre routine (idéal idéal)
  • mettre les deux boucles dans une routine et revenir au lieu de casser

Il est très difficile pour moi de créer un cas où une double boucle n'est pas si cohérente qu'elle ne devrait de toute façon pas être sa propre routine.

Soit dit en passant, la meilleure discussion à ce sujet que j'ai vue est le code complet de Steve McConnell . Si vous aimez penser à ces problèmes de manière critique, je vous recommande fortement de lire, même de couverture en couverture malgré son immensité.

Djechlin
la source
1
Ce sont nos programmeurs de tâches qui lisent et comprennent le code. Pas seulement du code simpliste, sauf si c'est la nature de votre base de code. Il y a et il y aura toujours des exceptions à la généralité (et non à la règle) contre l'utilisation de gotos en raison du coût compris. Chaque fois que l'avantage l'emporte sur le coût, c'est quand goto doit être utilisé; comme c'est le cas pour toutes les décisions de codage en génie logiciel.
dietbuddha
1
@dietbuddha il y a un coût à violer les conventions de codage de logiciel acceptées qui doivent être prises en compte de manière adéquate. Il y a un coût à augmenter la diversité des structures de contrôle utilisées dans un programme même si cette structure de contrôle peut être bien adaptée à une situation. Il y a un coût à casser l'analyse statique du code. Si tout cela en vaut toujours la peine, alors qui suis-je pour discuter.
djechlin
1

TLD; DR: non en particulier, oui en général

Pour l'exemple spécifique que vous avez utilisé, je dirais non pour les mêmes raisons que beaucoup d'autres ont signalées. Vous pouvez facilement refactoriser le code sous une forme équivalente qui élimine le besoin de goto.

En général, la proscription contre gotoest une généralité basée sur la nature comprise gotoet le coût de son utilisation. Une partie du génie logiciel prend des décisions de mise en œuvre. La justification de ces décisions se produit en pesant le coût par rapport aux avantages et chaque fois que les avantages l'emportent sur le coût, la mise en œuvre est justifiée. La controverse découle de différentes évaluations, à la fois en termes de coûts et d'avantages.

Le fait est qu'il y aura toujours des exceptions où a gotoest justifié, mais seulement pour certains en raison de valorisations différentes. Le dommage est que de nombreux ingénieurs excluent simplement les techniques dans une ignorance délibérée car ils les lisent sur Internet plutôt que de prendre le temps de comprendre pourquoi.

Dietbuddha
la source
Pouvez-vous recommander des articles sur la déclaration goto expliquant davantage le coût?
Thys
-1

Je ne pense pas que ce cas justifie une exception, car cela peut s'écrire:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
ptyx
la source