これはgotoステートメントを正当化しますか?


15

私はこの質問に少し前に出くわしました、そして私はそこから材料のいくつかを引き出しています:「break n」コンストラクトの名前はありますか?

これは、二重にネストされたforループから抜け出すようにプログラムに指示する必要がある人々にとって、不必要に複雑な方法のようです。

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

goto文は悪魔だと言うのが好きな教科書を知っています。私はそれらが好きではありませんが、これは規則の例外を正当化するのでしょうか?

nネストされたforループに対処する答えを探しています。

注:「はい」、「いいえ」、またはその中間のいずれで回答しても、完全に近い考え方の回答は歓迎されません。特に答えが「いいえ」の場合は、正当で正当な理由を提供してください(とにかくStack Exchangeの規制からそれほど遠くない)。


2
内側のループ条件をfor (j = 10; j > 0 && !broken; j--)次のように記述できる場合は、break;ステートメントは必要ありません。broken外側のループが始まる前にto の宣言を移動した場合、それがそのように記述できれば、sは必要ないfor (i = 0; i < 10 && !broken; i++)と思います。 break;
FrustratedWithFormsDesigner 14

8
例を考えるとC#からさ-上のC#リファレンスgoto後藤(C#リファレンス) - 「goto文も深くネストされたループから抜け出すために便利です。」

OMG、c#にgotoステートメントがあることは知らなかった!StackExchangeで学ぶこと。(最後にgotoステートメント機能を希望したときのことは思い出せませんが、これまで登場したことはないようです)。
ジョン・ウー

6
私見それは本当にあなたの「プログラミング宗教」と現在のムーンフェイズに依存します。私(および私の周りのほとんどの人)にとって、ループがかなり小さい場合はgotoを使用して深いネストから抜け出すことは問題ありません。代わりに、ループ条件を中断するために導入されたbool変数の余分なチェックがあります。これは、ボディの途中で抜け出したい場合や、最も内側のループの後にboolもチェックする必要のあるコードがある場合に、さらにそうなります。
PlasmaHH 14

1
@JohnWu gotoは、最初のケース内でコードを実行した後、別のケースにフォールスルーするC#スイッチで実際に必要です。ただし、これがgotoを使用した唯一のケースです。
ダンライオンズ14

回答:


33

go-toステ​​ートメントの明らかな必要性は、ループに対して不適切な条件式を選択したことから生じます

外側のループをできるだけ長く継続さi < 10せ、最も内側のループを限り長く継続させたいと述べていますj > 0

しかし、実際にはそれはあなたが望んいたものではありません。ループに評価したい実際の条件を伝えなかったので、breakまたはgotoを使用してループを解決します。

ループに、で始まる真の意図を伝えると、ブレークやgotoは必要ありません。

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

他の回答に関する私のコメントをご覧ください。元のコードiは外側のループの終わりに印刷されるため、コードを100%同等に見せるために増分を短絡することが重要です。私はあなたの答えが間違っていると言っているのではなく、ただループをすぐに破ることがコードの重要な側面であることを指摘したかっただけです。
pswg 14

1
@pswg iOPの質問の外側のループの最後にコードが表示されません。
Tulainsコルドバ

OPはここでの私の質問のコードを参照しましたが、その部分は省略しました。ところで、私はあなたに投票しませんでした。ループ条件を正しく選択することに関して、答えは完全に正しいです。
pswg 14

3
@pswg gotoを本当に本当に本当に正当化したいのであれば、1つを必要とする問題を仕掛けることができますが、一方で、私は20年間専門的にプログラミングしており、必要な現実世界の問題はありません1。
Tulainsコルドバ

1
コードの目的は、gotoそれを使用するように思われる質問を生み出したが、そうでbreak(n)はない言語での基本的なアクションを説明する場合でも、有効なユースケースを提供することではありませんでした(より良いものがあると確信しています)サポートします。
pswg 14

34

この場合、コードを別のルーチンにリファクタリングしてreturnから、内側のループからリファクタリングできます。これにより、外側のループから抜け出す必要がなくなります。

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

    return false;
}

2
つまり、はい、あなたが示すものは、ネストされたループから抜け出すための不必要に複雑な方法ですが、いいえ、gotoをより魅力的にしないリファクタリングする方法があります。
ロジャー14

2
ちょっとした注意:元のコードでiは、外側のループの終了後に出力されます。それを実際に反映することiは重要な値であり、ここでreturn true;およびreturn false;は両方である必要がありますreturn i;
pswg

+1は、この回答を投稿しようとしており、これが受け入れられる回答であると考えています。その方法はネストされたループアルゴリズムの特定のサブセットでのみ機能し、必ずしもコードをよりきれいにするわけではないため、受け入れられた答えが受け入れられたとは信じられません。この回答の方法は一般的に機能します。
ベン・リー

6

いいえ、goto必要だとは思いません。このように書くと:

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

持つ&&!broken両方のループにすることで、時に両方のループから抜け出すことができますi==j

私には、このアプローチが望ましいです。ループ条件は非常に明確ですi<10 && !broken

動作するバージョンはここで見ることができます:http : //rextester.com/KFMH48414

コード:

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);
}

出力:

i == j == 1の場合、ループが中断します
2
0

なぜbroken最初の例も使用するのでしょうか?内側のループでbreakを使用するだけです。また、絶対にを使用する必要がある場合broken、なぜループの外側で宣言するのですか?ただ、それを宣言iのループ。whileループに関しては、使用しないでください。正直に言って、gotoバージョンよりも読みにくくなっていると思います。
ルイスキューバル14

@luiscubal:という名前の変数brokenが使用されます。これは、breakステートメントを使用するのが好きではないためです(switch-caseを除きますが、それだけです)。ときにすべてのループを中断したい場合に備えて、外側のループの外側で宣言されていますi==j。あなたは正しい、一般的に、brokenそれが壊れる最も外側のループの前に宣言する必要があるだけです。
FrustratedWithFormsDesigner 14

アップデートでi==2は、ループの最後に。成功条件は、aに100%相当する場合、増分を短絡する必要がありますbreak 2
pswg 14

2
broken = (j == i)はifよりも個人的に好みます。言語で許可されている場合は、外側のループの初期化の中に壊れた宣言を入れます。この特定の例ではこれらのことを言うのは難しいですが、ループ全体がノーオペレーションであり(何も返さず、副作用もありません)、それが何かをする場合は、おそらくif内でそれを行います(私はまだbroken = (j ==i); if broken { something(); }より良いのが好きですが)個人的に)
Martijn 14

@Martijn元のコードでiは、外側のループの終了後に出力されます。言い換えれば、本当に重要なのは、外側のループから抜け出したときだけだということです。
pswg 14

3

短い答えは「依存する」です。コードの読みやすさと理解可能性に関して選択することを推奨します。gotoの方法は、条件を微調整するよりも読みやすいかもしれません。また、最小限の驚きの原則、ショップ/プロジェクトで使用されるガイドライン、およびコードベースの一貫性を考慮することもできます。例えば、gotoはスイッチを離れたり、エラー処理のためにLinuxカーネルコードベースで一般的な方法です。また、一部のプログラマーはコードの読み取りに問題があり(「goto is evil」というマントラで発生するか、教師がそう考えているために学習できない)、一部のプログラマーが「あなたを判断する」ことさえあります。

一般的に言えば、ジャンプが長すぎない場合は特に、同じ関数でさらに進むgotoがしばしば受け入れられます。ネストされたループを離れるユースケースは、「それほど悪くない」gotoの既知の例の1つです。


2

あなたの場合、gotoは魅力的に見えるほど改善されていますが、それほど改善されていないので、コードベースでそれらを使用することに対するルールを破る価値があります。

あなたの将来のレビュアーについて考えてみてください:「この人は悪いコーダーなのか、それともgotoがそれだけの価値のあるケースなのか、自分で判断するか、またはインターネット。" gotoを完全に使用できないのに、なぜ将来的に将来のコーダーに対してこれを行うのでしょうか?

一般的に、この考え方はすべての「悪い」コードに適用され、さらに定義されます。現在動作し、この場合は良好であるが、正しいことを確認するための努力を作成します。やれ。

そうは言っても、はい、やや厄介なケースの1つを作成しました。してもいいです:

  • ブールフラグのようなより複雑な制御構造を使用して、両方のループから抜け出します。
  • 内部ループを独自のルーチン内に配置します(理想的です)
  • 両方のループをルーチンに入れて、中断する代わりに戻ります

ダブルループがあまりにもまとまりがなく、とにかくそれ自身のルーチンであってはならないケースを作成することは私にとって非常に難しいです。

ちなみに、私がこれまでに見てきた中で最も良い議論は、Steve McConnellのCode Completeです。これらの問題を批判的に考えることを楽しんでいるなら、その広大さにもかかわらず、カバーツーカバーでさえ読むことを強くお勧めします。


1
コードを読んで理解するのは私たちの仕事のプログラマーです。コードベースの性質でない限り、単純なコードだけではありません。コストが理解されているため、gotoの使用に対する一般性(ルールではない)には例外があります。gotoを使用する必要がある場合は、メリットがコストを上回る場合はいつでも、ソフトウェアエンジニアリングにおけるすべてのコーディング決定の場合と同様です。
dietbuddha

1
@dietbuddhaは、適切に考慮されるべき受け入れられたソフトウェアコーディング規約に違反するコストがあります。この制御構造が状況に適している場合でも、プログラムで使用される制御構造の多様性を高めるにはコストがかかります。コードの静的分析を壊すにはコストがかかります。これがまだそれだけの価値があるなら、誰が私が主張するのか。
djechlin 14

1

TLD; DR:特になし、一般的にはあり

あなたが使用した特定の例については、他の多くの人が指摘したのと同じ理由でノーと言うでしょう。コードを簡単にリファクタリングして同等の適切な形式にすることで、goto

一般的に、禁止gotoは、gotoそれを使用することの理解された性質とコストに基づく一般性です。ソフトウェアエンジニアリングの一部は、実装に関する決定を下すことです。これらの決定の正当化は、費用と便益を比較検討することにより行われ、便益が費用を上回る場合はいつでも実装が正当化されます。コストと利益の両方の異なる評価のために論争が生じます。

重要なのは、a gotoが正当化される例外が常に存在するということですが、評価が異なるためにa のみが存在するということです。残念なことに、多くのエンジニアは、理由を理解するのに時間をかけるのではなく、インターネット上でそれを読むため、意図的な無知の技術を単に除外しています。


gotoステートメントで、コストをさらに説明する記事を推奨できますか?
2006

-1

これは次のように書くことができるため、このケースは例外を正当化するとは思わない。

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
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.