web-dev-qa-db-ja.com

「goto」ステートメントの使用は悪いですか?

二次ループを突破する方法についていくつかの研究を行った後

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

ほとんどの人は「goto」関数を呼び出すことを勧めました
次の例のように見えます:

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

しかしながら; 「goto」ステートメントは悪い習慣だとよく耳にします。下の写真は私のポイントを完全に示しています: Series found

そう

  • Gotoステートメントは本当にどれほどひどいですか、そしてなぜですか?
  • 「goto」ステートメントを使用するよりもメインループを解除するより効果的な方法はありますか?
54
Rasmus Søborg

編集:

Gotoステートメントは本当にどれほどひどいですか、そしてなぜですか?

正確な状況によって異なります。リファクタリングよりもコードが読みやすくなったときはいつでも思い出せません。また、他の回答から明らかなように、読みやすさに対する個人的な見方にも依存します。 (興味深い点として、それはgeneratedコードで広く使用されています-C#5のすべての非同期/待機コードは事実上多くのgotoに基づいています)。

問題は、gotoが使用される傾向があるという状況ですtendとにかくリファクタリングが物事を支援する種類の状況であるのに対し、gotoは、より困難になるソリューションに固執しますコードが複雑になるので、従う必要があります。

「goto」ステートメントを使用するよりもメインループを解除するより効果的な方法はありますか?

もちろんです。メソッドを別の関数に抽出します。

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

...

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

私は通常、「終了したか」を追跡するために追加のローカル変数を導入するよりも、これを行うことを好みます-もちろんそれはうまくいきますが。

50
Jon Skeet

ここでは、他のすべての回答に強く反対します。 gotoを使用して提示するコードは、何の問題もありません。 理由C#にはgotoステートメントがあり、これは正確に、これらのタイプのシナリオ用です。

gotoは、1970年代にかつての人々がgotoのために制御フローがあちこちに飛び交う恐ろしい、完全にメンテナンス不可能なコードを書くため、単にマイナスの偏見を持っています。 C#のgotoでは、メソッド間の遷移もできません。それでも、それに対するこの不合理な偏見はまだあります。

私の意見では、「現代」のgotoを使用して内部ループから抜け出すことはまったく問題ありません。人々が提供する「代替案」は常により複雑で読みにくいになります。

メソッドは一般に再利用可能であると想定されています。ループの内部用に完全に別個のメソッドを作成し、その1つの場所からのみ呼び出されるようにし、メソッドの実装がソースコード内の離れた場所に配置される可能性がある場合は、改善されていません。

gotoを回避するためのこの種のばかげた運動は、基本的には政治的正しさと同等ですが、プログラミングの世界ではそうです。

35
MgSam

Gotoステートメントは本当にどれほどひどいですか、そしてなぜですか?

与えられたすべての通常の理由でそれは本当に悪いです。ラベル付きループをサポートしていない言語でラベル付きループをエミュレートする場合は、問題ありません。

これを関数に置き換えると、多くの場合、実際には同じユニットとして読み取る必要のあるロジックが分散されます。これにより読みにくくなります。どこから始めたのかをいくらか忘れてしまう、旅の終わりまで、実際には何もしない機能の軌跡をたどるのは好きではありません。

ブール値と追加のifとbreakでそれを置き換えることは本当に不格好であり、ノイズのように本当の意図に従うことが難しくなります。

Java (およびJavaScript)では、これは完全に受け入れ可能です(ラベル付きのループ)。

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

C#では、これに相当するものはそうではないようです。

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

単語gotoは、人々にすべての常識を捨てさせ、文脈に関係なくxkcdをリンクさせるという心理的な効果があるためです。

「goto」ステートメントを使用するよりもメインループを解除するより効果的な方法はありますか?

他の言語がラベル付きループを提供し、C#がgotoを提供するのはそのためです。あなたの例は単純すぎることに注意してください。回避策は例に合わせて調整されているので、回避策はそれほど悪く見えません。実際、私はこれを提案することもできます:

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

これはどう:

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:;

これはまだあなたによく見えますか?

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;
}
34
Esailija

私はときどき「goto」を使用し、上記の例のように、見栄えが良いとわかりました。

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...
  }
}

私はあなたが同じことを再帰的に行うことができることを知っています、しかしそれが気にかける人はうまくいくので私は使います。

7
Abdurrahim

私は行くことがいかに悪いかについての回答の大多数に同意します。

Gotoを回避するための私の提案は次のようなことです:

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
       }
   }
}
6
Marcelo Assis

私の同僚(ファームウェアプログラミングで15年以上)と私はいつもgotoを使用しています。ただし、例外の処理にのみ使用します!!例えば:

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

setsockopt_failed:
close(sd);

私の知る限り、これはCで例外を処理するための最良の方法です。私はC#でも動作すると思います。また、私がそう思っているのは私だけではありません: CまたはC++での良いgotoの例

4
H_squared

ネストされたループから抜け出すことを除いて、他の答えをここに追加するには、gotoの1つのきちんとした使用法がシンプルでクリーンな状態マシンです。

goto EntryState;

EntryState:
//do stuff
if (condition) goto State1;
if (otherCondition) goto State2;
goto EntryState;

State1:
//do stuff
if (condition) goto State2;
goto State1;

State2:
//do stuff
if (condition) goto State1;
goto State2;

これは、ステートマシンを実行するための非常にクリーンで読みやすい方法であり、しかも無料でパフォーマンスフレンドリーです! gotoなしでこれを行うことはできますが、実際にはコードが読みにくくなるだけです。 gotoを避けないでください。使用する前に少し考えてください。

0
blenderfreaky

個人的に私はgotoを「gotoは地獄につながる」と考えたいと思います...そして、多くの点で、これは非常に保守不可能なコードと本当に悪い習慣につながる可能性があるので、これは事実です。

とはいえ、それはまだ理由のために実装されており、他の解決策が利用できない場合は、使用するときに控えめに使用する必要があります。 OO言語は、本当にそれを必要としない(多くの)ことに向いています。

私がこれが最近使用されたのを目にするのは、難読化するときだけです... gotoを使用して地獄からコードを作成する素晴らしい例なので、人々はそれを理解しようとするのを阻止されます!

一部の言語は同等のキーワードに依存しています。たとえば、x86アセンブリでは、JMP(ジャンプ)、JE(等しい場合はジャンプ)、JZ(ゼロの場合はジャンプ)などのキーワードがあります。このレベルにはOOがないため、これらはアセンブリ言語で頻繁に必要とされ、アプリケーション内を移動する他の方法はほとんどありません。

AFAIK ...絶対に必要でない限り、それから離れてください。

0
Matthew Layton