while(true)and loop-breaking-アンチパターン?


32

次のコードを検討してください。

public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

このプロセスには、有限であるが入力依存のステップ数が含まれると仮定します。ループは、アルゴリズムの結果としてそれ自体で終了するように設計されており、無期限に実行するように設計されていません(外部イベントによってキャンセルされるまで)。ループを終了するかどうかを確認するテストは、論理的な一連のステップの途中で行われるため、現在、whileループ自体は意味のあるものをチェックしません。代わりに、チェックは概念アルゴリズム内の「適切な」場所で実行されます。

終了条件がループ構造によってチェックされないため、バグが発生しやすいため、これは悪いコードだと言われました。ループを終了する方法を理解することはより困難であり、将来の変更を考慮して誤ってブレーク条件がバイパスまたは省略される可能性があるため、バグを招く可能性があります。

これで、コードは次のように構成できます。

public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

ただし、これはコード内のメソッドの呼び出しを複製し、DRYに違反します。TransformInSomeWay後で他のメソッドに置き換えられた場合、両方の呼び出しを見つけて変更する必要があります(2つの呼び出しがあるという事実は、より複雑なコードではあまり明らかではありません)。

次のように書くこともできます。

public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

...しかし、条件チェックをループ構造にシフトすることだけが目的の変数があり、元のロジックと同じ動作を提供するために複数回チェックする必要があります。

私の側では、このコードが実際に実装しているアルゴリズムを考えると、元のコードが最も読みやすいと言います。あなたがそれを自分で行っていた場合、これはあなたがそれについて考える方法ですので、アルゴリズムに精通している人々にとって直感的です。

では、どちらが「良い」のでしょうか?ループの周りのロジックを構築することにより、whileループに条件チェックの責任を与える方が良いでしょうか?または、ループの組み込み機能をバイパスすることを意味する場合でも、要件またはアルゴリズムの概念的な説明で示されているように、「自然な」方法でロジックを構築する方がよいでしょうか?


12
おそらく、しかし、サンプルコードにも関わらず、質問されるIMOはより概念的であり、これはSEのこの分野により近いものです。ここでは、パターンまたはアンチパターンとは何かについて頻繁に説明しますが、それが本当の問題です。無限に実行するループを構築し、それからそれを破ることは悪いことですか?
キース

3
このdo ... until構造は、「プライミング」する必要がないため、これらの種類のループにも役立ちます。
Blrfl

2
ループと半分のケースには、まだ本当に良い答えがありません。
ローレンペクテル

1
「しかし、これはコード内のメソッドの呼び出しを複製し、DRYに違反します」私はその主張に同意せず、原則の誤解のように思われます。
アンディ

1
(;;)のCスタイルを好むことは別として、明示的に「ループがあり、ループステートメントをチェックインしない」ことを意味しますが、最初のアプローチは完全に正しいです。ループがあります。終了するかどうかを決める前に、やるべきことがいくつかあります。次に、いくつかの条件が満たされたら終了します。その後、実際の作業を行い、最初からやり直します。それは絶対に問題なく、理解しやすく、論理的で、冗長性がなく、正しい方法です。2番目のバリアントはひどいですが、3番目のバリアントは無意味です。ifに入るループの残りが非常に長い場合、ひどくなります。
gnasher729

回答:


14

最初のソリューションに固執します。ループは非常に複雑になる可能性があり、1つまたは複数のクリーンブレークは、複雑なif文や追加変数よりも、コード、オプティマイザー、およびプログラムのパフォーマンスを見る人にとって非常に簡単です。私の通常のアプローチは、「while(true)」のようなループを設定し、可能な限り最高のコーディングを行うことです。多くの場合、ブレークを標準のループコントロールに置き換えることができます。結局のところ、ほとんどのループは非常に単純です。終了条件は、必要があるあなたが言及理由により、ループ構造によって確認されたが、されていない、全く新しい変数を追加する場合は、ステートメントの追加、さらにはそれらのすべては、コード、繰り返しのコストでより多くのバグが発生しやすいし。


「ifステートメント、および繰り返しコード。これらはすべて、さらにバグが発生しやすい」-うん、私はifの「将来のバグ」と呼ぶ人々を試すとき
パット

13

ループ本体が自明でない場合、それは意味のある問題です。体が非常に小さい場合、このように分析することは簡単であり、まったく問題ではありません。


7

私は、他の解決策をブレークのある解決策よりもうまく見つけるのに苦労しています。まあ、私はできるようにするAdaの方法を好む

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

しかし、ブレークソリューションは最もクリーンなレンダリングです。

私のPOVでは、制御構造が欠落している場合、最もクリーンなソリューションは、データフローを使用せずに他の制御構造でエミュレートすることです。(同様に、制御構造を含むものよりも純粋なデータフローソリューションを好むデータフローの問題がある場合*)。

ループを終了する方法を理解することはより困難です。

間違えないでください。難易度がほとんど同じでなければ、議論は行われません。条件は同じで、同じ場所に存在します。

どちらの

while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

そして

while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

意図した構造をより良くレンダリングしますか?最初に投票します。条件が一致することを確認する必要はありません。(しかし、私のインデントを参照)。


1
ああ、表情、言語直接半ばに終了ループをサポート!:)
mjfgates

不足している制御構造を制御構造でエミュレートするという点が気に入っています。それはおそらくGOTO関連の質問に対する適切な回答でしょう。セマンティック要件に適合する場合は常に言語の制御構造を使用する必要がありますが、アルゴリズムが他の何かを必要とする場合は、アルゴリズムが必要とする制御構造を使用する必要があります。
supercat

3

while(true)and breakは一般的なイディオムです。これは、Cに似た言語で終了中ループを実装する標準的な方法です。ループの後半に終了条件とif()を格納する変数を追加するのは、合理的な選択肢です。一部のショップは、どちらか一方を公式に標準化できますが、どちらも優れているわけではありません。それらは同等です。

これらの言語で作業する優秀なプログラマーは、どちらかが見つかれば、何が起こっているかを一目で知ることができるはずです。それは良い面接の質問をするかもしれません。各イディオムを使用して2つのループを誰かに渡し、「違いは何か」と尋ねます(適切な答え:「何もありません」、さらに「しかし、私は習慣的にその1つを使用します」)。


2

あなたの選択肢はあなたが思うほど悪くはありません。良いコードは:

  1. ループを終了する条件を適切な変数名/コメントで明確に示します。(破壊条件を非表示にしないでください)

  2. 操作を行う順序を明確に示します。これはDoSomethingElseTo(input)、ifの中にオプションの3番目の操作()を入れるのが良い考えです。

そうは言っても、完璧主義者の真面目で洗練された本能に多くの時間を費やすことは簡単です。上記のifを調整するだけです。コードにコメントして先に進みます。


完璧主義者、真面目な研磨の本能は、長期的には時間を節約すると思います。うまくいけば、練習を重ねることでこのような習慣を身に付け、コードを完璧にし、多くの時間を費やすことなく真鍮を磨くことできます。しかし、物理的な構造とは異なり、ソフトウェアは永遠に続き、無限の場所で使用できます。0.00000001%も高速で、信頼性が高く、使用可能で、保守可能なコードによる節約は大きくなる可能性があります。(もちろん、私の多くの研磨コードのほとんどは、長い時代遅れまたは、これらの日、他の誰かのためにお金を稼ぐの言語であるが、それはなかった私は、良い習慣を助けを開発しています。)
RalphChapin

まあ私はあなたを間違って呼ぶことはできません:-)これ意見の質問であり、良いスキルがブラス研磨から出てきた場合:素晴らしい。
パット

とにかく検討する可能性があると思います。私は確かにいかなる保証も提供したくありません。私のブラスポリッシュが私のスキルを向上させたと思うのは気分が良くなるので、この点に偏見を抱くかもしれません。
-RalphChapin

2

を使用するのは悪い習慣while (true)であり、多くの場合正しいと言われています。あなたの場合whileのステートメントは、複雑なコードの多くが含まれており、場合によっては、あなたがコードを維持することが困難で残されており、シンプルなバグが無限ループを引き起こす可能性が出て壊しているとき、それは不明です。

この例ではwhile(true)、コードが明確で理解しやすいため、使用するのが適切です。一部の人々はそれを使用することが常に悪い習慣であると考えるため、非効率で読みにくいコードを作成しても意味がありませんwhile(true)

私は同様の問題に直面しました:

$i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

を使用するとdo ... while(true)isset($array][$key]不必要に2回呼び出されること、コードが短くなり、簡潔になり、読みやすくなります。else break;最後に無限ループのバグが発生するリスクはまったくありません。

$i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

良い慣行に従い、悪い慣行を避けるのは良いことですが、例外は常に存在し、オープンな心を保ち、それらの例外を実現することが重要です。


0

特定の制御フローが自然にbreakステートメントとして表現される場合、基本的に、他のフロー制御メカニズムを使用してbreakステートメントをエミュレートすることをお勧めします。

(これには、ループが部分的に展開され、ループ本体が下にスライドして、ループが条件付きテストと一致するようになります)

ループの開始時に条件付きチェックを行うことで制御のフローが自然に表現されるようにループを再編成できる場合、素晴らしいです。それが可能かどうかを考える時間を費やす価値さえあるかもしれません。しかし、そうではありません。丸い穴に四角い釘をはめ込まないでください。


0

これで行くこともできます:

public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

または、それほど混乱しない:

bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}

1
好む人もいますが、私はループ条件は一般的に条件付きテストのために予約すべきであり、何かを行うステートメントではないことを断言します。

5
ループ条件でのコンマ式...それはひどいです。あなたはあまりにも賢いです。そして、TransformInSomeWayを式として表現できるこの些細なケースでのみ機能します。
gnasher729

0

ロジックの複雑さが手に負えなくなった場合、フロー制御のためにループの途中で無制限のループを使用することは実際に最も意味があります。次のようなコードを含むプロジェクトがあります。(ループの最後で例外に注意してください。)

L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

無限ループを壊すことなくこのロジックを実行するには、次のような結果になります。

void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

そのため、ヘルパーメソッドで例外がスローされるようになりました。また、かなり多くのif ... elseネストがあります。私はこのアプローチに満足していません。したがって、最初のパターンが最適だと思います。


確かに、私はSOでこの質問をし、炎に包まれました... stackoverflow.com/questions/47253486/…–
intrepidis
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.