web-dev-qa-db-ja.com

while(true)とループブレイク-アンチパターン?

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

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

32
KeithS

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

14
RalphChapin

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

13
DeadMG

休憩のあるソリューションよりも他のソリューションを見つけるのに苦労します。まあ、私はできるようにするエイダの方法を好みます

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

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

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

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

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

どちらの

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

そして

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

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

7
AProgrammer

while(true)とbreakは一般的なイディオムです。これは、Cのような言語で中間出口ループを実装する標準的な方法です。ループの後半に終了条件とif()を格納する変数を追加することは、妥当な代替手段です。いくつかのショップはどちらかを公式に標準化するかもしれませんが、どちらも優れていません。それらは同等です。

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

3
mjfgates

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

  1. ループを終了する必要がある条件のもとで、適切な変数名/コメントで明確に示します。 (破壊状態は非表示にしないでください)

  2. 操作の順序を明確に示します。これは、オプションの3番目の操作(DoSomethingElseTo(input))をif内に配置することをお勧めします。

とはいえ、完璧主義者、真ちゅう研磨の本能が多くの時間を費やすのは簡単です。上記のifを調整するだけです。コードをコメントして次に進んでください。

2
Pat

人々は、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);
_

グッドプラクティスを守り、避けるバッドプラクティスは良いことですが、常に例外があり、心を開いてそれらの例外を実現することが重要です。

2
Dan Bray

特定の制御フローが自然にbreakステートメントとして表現される場合、breakステートメントをエミュレートするために他のフロー制御メカニズムを使用することは、本質的には決して良い選択ではありません。

(これには、ループの部分的な展開とループ本体の下方へのスライドが含まれるため、ループが条件テストと一致するようになります)

ループの最初に条件チェックを行うことで制御の流れが自然に表現されるようにループを再編成できる場合は、すばらしいです。それが可能かどうかを考えるのに時間を費やす価値があるかもしれません。しかし、そうではありません。正方形のペグを丸い穴に合わせようとしないでください。

0
user122173

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

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ネスト。私はこのアプローチに満足していません。したがって、最初のパターンが最良だと思います。

0
intrepidis

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

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