web-dev-qa-db-fra.com

Utiliser une déclaration 'goto' est-il mauvais?

Après avoir fait quelques recherches sur la manière de percer une boucle secondaire

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary loop
       // Do Something
       break; // Break main loop?
   }
}

la plupart des gens ont recommandé d'appeler la fonction 'goto'
Regardant comme l'exemple suivant:

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary Loop
       // Do Something
       goto ContinueOn; // Breaks the main loop
   }
}
ContinueOn:

Toutefois; J'ai souvent entendu dire que la déclaration "goto" est une mauvaise pratique. La photo ci-dessous illustre parfaitement mon propos:  Series found

Alors

  • Quelle est la gravité de la déclaration goto et pourquoi?
  • Existe-t-il un moyen plus efficace de casser la boucle principale que d'utiliser l'instruction 'goto'?
44
Rasmus Søborg

MODIFIER: 

Quelle est la gravité de la déclaration goto et pourquoi?

Cela dépend de la situation exacte. Je ne me souviens plus du moment où j'ai trouvé que le code était plus lisible que le refactoring. Cela dépend également de votre point de vue personnel sur la lisibilité - certaines personnes ne l'aiment pas plus que d'autres, comme cela ressort clairement des autres réponses. (Comme point d’intérêt, il est largement utilisé dans le code généré - tout le code asynchrone/wait en C # 5 est basé sur un grand nombre de gotos).

Le problème est que les situations dans lesquelles goto a tendance à être utilisé tend sont le genre de situations dans lesquelles la refactorisation facilite de toute façon les choses - alors que goto reste avec une solution qui devient difficile à suivre à mesure que le code se complique.

Existe-t-il un moyen plus efficace de casser la boucle principale que d'utiliser l'instruction 'goto'?

Absolument. Extrayez votre méthode dans une fonction distincte:

while (ProcessValues(...))
{
    // Body left deliberately empty
}

...

private bool ProcessValues()
{
   for (int i = 0; i < 15; i++)
   {
       // Do something
       return false;
   }
   return true;
}

Je préfère généralement faire cela plutôt que d'introduire une variable locale supplémentaire pour garder la trace de "ai-je fini" - bien que cela fonctionne, bien sûr.

47
Jon Skeet

Quelle est la gravité de la déclaration goto et pourquoi?

C'est vraiment mauvais pour toutes les raisons habituelles. C'est parfaitement correct d'émuler des boucles étiquetées dans des langues qui ne les prennent pas en charge.

Le remplacer par des fonctions dispersera dans de nombreux cas une logique qui devrait être lue comme la même unité. Cela rend la lecture plus difficile ... Personne ne veut suivre une série de fonctions qui ne font vraiment rien avant la fin du voyage, quand vous avez un peu oublié. Où vous êtes parti.

Le remplacer par des booléens et un tas de si et de pauses supplémentaires est vraiment maladroit et rend plus difficile de suivre les véritables intentions, comme n'importe quel bruit.

Dans Java (et javascript), ceci est parfaitement acceptable (boucles étiquetées):

outer: while( true ) {
    for( int i = 0; i < 15; ++i ) {
        break outer;
    }
}

En C #, il semble que l'équivalent très proche ne soit pas:

while( true ) {
   for (int I = 0; I < 15; I++) { 
       goto outer;
   }
}
outer:;

En raison de la Parole goto, qui a pour effet psychologique de faire perdre tout son sens commun aux gens et de les lier à xkcd quel que soit le contexte.

Existe-t-il un moyen plus efficace de casser la boucle principale que d'utiliser l'instruction 'goto'?

Dans certains cas, ce n’est pas le cas, c’est pourquoi les autres langues fournissent des boucles étiquetées et C # fournit goto. Notez que votre exemple est trop simple et que les solutions de contournement ne semblent pas trop mauvaises, car elles sont adaptées à cet exemple. En fait, je pourrais tout aussi bien suggérer ceci:

   for (int I = 0; I < 15; I++) {
       break;
   }

Que dis-tu de ça:

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            goto outer;
        }
        val = val / 2;
    }
}
outer:;

Cela vous semble-t-il toujours bon? 

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    if (!Inner(i, ref val, len))
    {
        break;
    }
}

private bool Inner(int i, ref int val, int len)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            return false;
        }

        val = val / 2;
    }

    return true;
}
33
Esailija

Je vais fortement en désaccord avec toutes les autres réponses ici. Le code que vous présentez en utilisant goto n'a rien de mal à cela. Il y a un raison C # a une instruction goto, et c'est précisément pour ces types de scénarios que vous décrivez.

goto a tout simplement une stigmatisation négative, car dans les années 1970 et avant, les gens écrivaient un code horrible et totalement incontrôlable dans lequel le flux de contrôle faisait un bond en avant à cause de goto. La variable goto de C # ne permet même pas la transition entre les méthodes! Pourtant, il existe toujours cette stigmatisation irrationnelle contre elle.

À mon avis, il n’ya absolument rien de mal à utiliser une goto "moderne" pour sortir d’une boucle intérieure. Les "alternatives" proposées par les gens finissent toujours par être plus compliqué et plus difficile à lire

Les méthodes sont généralement supposées être réutilisables. Créer une méthode entièrement distincte pour la partie interne d'une boucle, qui ne sera jamais appelée à partir de cet emplacement et où l'implémentation de la méthode risque de se trouver à un emplacement éloigné du code source, n'est pas une amélioration. 

Ce genre d'exercice idiot d'éviter goto est fondamentalement l'équivalent du politiquement correct, mais dans le monde de la programmation.

31
MgSam

Je suis d'accord avec la majorité des réponses sur la gravité de la goto.

Ma suggestion pour éviter le goto est de faire quelque chose comme ça:

while (condition) { // Main Loop
   for (int i = 0; i < 15; i++) { // Secondary loop
       // Do Something
       if(someOtherCondition){
              condition = false // stops the main loop
              break; // breaks inner loop
       }
   }
}
7
Marcelo Assis

J'utilise parfois "goto" et je trouve que ça a l'air bien, comme dans l'exemple ci-dessus;

bool AskRetry(Exception ex)
{
  return MessageBox.Show(you know here...) == DialogResult.Retry;
}

void SomeFuncOrEventInGui()
{
  re:try{ SomeThing(); }
  catch (Exception ex)
  {
    if (AskRetry(ex)) goto re;
    else Mange(ex); // or throw or log.., whatever...
  }
}

Je sais que vous pouvez faire la même chose de manière récursive, mais peu importe que cela fonctionne et que je l'utilise.

6
Abdurrahim

Un de mes collègues (qui a plus de 15 ans en programmation de firmware) et que j'utilise goto tout le temps. Cependant, nous ne l'utilisons que pour gérer les exceptions !! Par exemple:

if (setsockopt(sd,...)<0){
goto setsockopt_failed;
}
...

setsockopt_failed:
close(sd);

A ma connaissance, c’est le meilleur moyen de gérer les exceptions en C. Je crois que je travaille aussi pour C #. De plus, je ne suis pas le seul à le penser: Exemples de bons gotos en C ou C++

4
H_squared

Personnellement, j'aime penser à goto comme à une "goto, un enfer" ... et à de nombreux égards, cela est vrai, car cela peut conduire à un code extrêmement immuable et à de très mauvaises pratiques.

Cela étant dit, il est toujours mis en œuvre pour une raison et lorsqu'il est utilisé, il doit être utilisé avec parcimonie, si AUCUNE autre solution n'est disponible. OO les langues se prêtent à ne pas en avoir vraiment besoin.

Les seuls cas où je vois JAMAIS qu'il soit utilisé de nos jours sont dans l'obscurcissement ... un bon exemple d'utilisation de goto pour créer du code de l'enfer, afin que les gens ne soient pas découragés d'essayer de le comprendre!

Certaines langues dépendent de mots-clés équivalents. Par exemple, dans l'assemblé x86, vous avez des mots-clés tels que JMP (Jump), JE (Jump if Equal) et JZ (Jump if Zero). Celles-ci sont fréquemment requises en langage Assembly car il n'y a pas OO - à ce niveau, et il existe peu d'autres méthodes pour se déplacer dans une application.

Autant que je sache ... restez à l'écart sauf si absolument nécessaire.

0
series0ne