「while(true)」ループはそれほど悪いですか?[閉まっている]


218

私は数年前からJavaでプログラミングをしてきましたが、最近、正式な学位を取得するために学校に戻りました。前回の割り当てで、以下のようなループを使用することでポイントを失ったことを知って、私は非常に驚きました。

do{
     //get some input.
     //if the input meets my conditions, break;
     //Otherwise ask again.
} while(true)

ここでのテストでは、コンソール入力をスキャンしているだけですが、この種のループはbreakと似ているためgoto、使用しないことをお勧めします。

gotoとそのJavaのいとこの落とし穴を完全に理解しbreak:labelているので、それらを使用しないのは良識があります。より完全なプログラムは、たとえばプログラムを単に終了するなど、他のいくつかの回避手段を提供することにも気づきましたが、それは私の教授が引用した理由ではなかったので...

何が問題になっていdo-while(true)ますか?


23
先生に聞いてください、そのようなことはかなり主観的です。
Chris Eberle

18
この記事は、gotoの何が悪いのかを理解するのに役立ちました。との比較breakはおそらく意味がありますが、実際には誤解されています。おそらくあなたはこれについてあなたの教授を教育することができます;)私の経験では、教授はプログラミングの技術についてあまり知りません。
Magnus Hoff 2011

100
これについての唯一、本当に議論の余地のない悪いことは私の心の中にあります。これdo {} while (true)は同等でwhile(true) {}あり、後者ははるかに従来の形式であり、はるかに明確です。
Voo

11
の単純な表現力を高く評価していない人はbreak、それがない言語でプログラミングしてみてください。あなたがそれを望む前に、それはあまり多くのループを必要としません!
スティーブン

16
宿題タグに同意しません。
aaaa bbbb

回答:


220

私はそれが悪いとは言いませんが、同様に私は通常、少なくとも代替案を探します。

それが私が最初に書くものである状況では、ほとんど常に、少なくともそれをより明確なものにリファクタリングしようとします。時々それは助けにならないかもしれません(または代替手段はbool、ループの終わりを示す以外に意味のない変数を持ち、breakステートメントよりも明確ではない)ですが、少なくとも試す価値はあります。

breakフラグよりも使用する方が明確な例として、次のことを考慮してください。

while (true)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        break;
    }
    actOnInput(input);
}

次に、フラグを使用するように強制します。

boolean running = true;
while (running)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        running = false;
    }
    else
    {
        actOnInput(input);
    }
}

私は後者を読むのがより複雑であると見ています:それは余分なelseブロックがあり、actOnInputよりインデントされています、そしてあなたがtestConditionリターン時に何が起こるかを考えようとしているなら、あなたはtrueそこをチェックするためにブロックの残りを注意深く調べる必要がありますものではありませんelseかに発生するブロックrunningに設定されているfalseかいません。

このbreakステートメントは、意図をより明確に伝え、ブロックの残りの部分で、以前の状態を気にすることなく、必要なことを実行できるようにします。

これは、メソッド内の複数のreturnステートメントについて人々が持っているのとまったく同じ種類の引数であることに注意してください。たとえば、最初の数行以内でメソッドの結果を計算できる場合(たとえば、一部の入力がnull、空、またはゼロであるため)、結果を格納する変数を用意するよりも、その答えを直接返す方がわかりやすい、それから他のコードのブロック全体、そして最後returnステートメント。


3
これが最初に手が届くツールではないことに同意しますが、問題を非常にきれいに処理してくれているようで、クリーンなコードが好きです。
JHarnach、2007

3
@ X-Zero:はい、時々。「ブレイク」を「リターン」に変えることができれば、それは多くの場合良いことですが、それでも最終的にはwhile (true)
Jon Skeet

21
@真実性:私はコードのほとんどをできるだけインデントしない方がいいです。そして、複合割り当ての条件は、私にはより複雑に感じられます。
Jon Skeet、2011

3
@Vince:はい。ループの開始時に状態を簡単に表現できればすばらしいことです。しかし、できない場合もあります。それが、私たちが話している状況です。私の回答の最初の数文に注意してください。
Jon Skeet、2011

6
@Ismail:期待しないでください。投稿は、著者ではなく、コンテンツのみで判断されるべきです。不正確、役に立たないと感じた場合は、Eric Lippertの投稿に反対票を投じるつもりです。しかしそれはまだ起こっていません:)
Jon Skeet

100

私の知る限り、本当に。goto彼らはどこかでそれが本当に悪いと聞いたので、教師はちょうどにアレルギーがあります。それ以外の場合は、次のように記述します。

bool guard = true;
do
{
   getInput();
   if (something)
     guard = false;
} while (guard)

これはほとんど同じことです。

多分これはよりきれいです(すべてのループ情報がブロックの上部に含まれているため):

for (bool endLoop = false; !endLoop;)
{

}

4
終了条件がはるかに見やすいので、私はあなたの提案を気に入っています。つまり、コードを読むと、コードが何をしようとしているのかがすぐに理解できるようになります。私は無限ループを決して使用しませんが、2つのバージョンを頻繁に使用します。
そり

4
コンセンサスは、フラグは意図を表すので、主に破るよりも優れているということですか?それが有益であることがわかります。すべて確かな答えですが、これを承認済みとしてマークします。
JHarnach、2011

20
これらの両方は、たとえあなたが壊れていることを知っていても、ループの本体の最後に移動し続ける必要があるため、ループの残りの部分が(少なくとも状況によっては)汚染されているという問題を抱えています。私は私の答えに例を挙げます...
ジョン・スキート

8
ジョン・スキートの答えの方が好きです。また、while(true){}は{} while(true)よりもはるかに優れています
aaaa bbbb

1
ダイクストラがそのGoTo記事を書いたことが嫌いです。GoToは確かに過去に悪用されたことが多いのですが、使用するべきではないという意味ではありません。また、Exit For、Break、Exit While、Try / CatchはすべてGotoの特殊化された形式です。多くの場合、Gotosはコードを読みやすくします。そして、はい、後藤なしで何でもできることを知っています。それはそうすべきだという意味ではありません。
キビー

39

Douglas Crockfordは、JavaScriptloop構造が含まれることを望んだ方法について次のように述べています

loop
{
  ...code...
}

また、Javaloop構造を持っているために悪くなるとは思いません。

本質的には何も問題ありませんwhile(true)ループは、しかし、そこにあるそれらを阻止するために教師のための傾向が。教育の観点から見ると、生徒に無限ループを作成させて、ループが逃げられない理由を理解させないのは非常に簡単です。

しかし、彼らがめったに言及しないのは、すべてのループメカニズムをwhile(true)ループで複製できるということです。

while( a() )
{
  fn();
}

と同じです

loop
{
  if ( !a() ) break;
  fn();
}

そして

do
{
  fn();
} while( a() );

と同じです:

loop
{
  fn();
  if ( !a() ) break;
}

そして

for ( a(); b(); c() )
{
  fn();
}

と同じです:

a();
loop
{
  if ( !b() ) break;
  fn();
  c();
}

使用することを選択した構成が機能するようにループを設定できる限り、重要ではありません。それは場合に起こるに収まるようにforループ、使用forループを。

最後に、ループをシンプルにしてください。すべての反復で実行する必要がある機能がたくさんある場合は、それを関数に入れます。機能させた後は、いつでも最適化できます。


2
+1:ステートメントが含まforれる場合、ループにはさらに複雑さが伴いますcontinueが、それらは大きな拡張ではありません。
ドナルフェロー

通常、実行したいシーケンスがある場合はforループを使用し、条件を満たす必要がある場合はwhileループを使用します。これにより、コードが読みやすくなります。
dascandy

4
誰もfor (;;) {これ以上書いていないのですか?(「永久に」と発音されます)。これは非常に人気がありました。
Dawood ibnカリーム2014

16

1967年に、エドガーダイクストラが業界誌に記事を書いて、コードの品質を向上させるためにgotoを高級言語から排除する必要がある理由を説明しました。「構造化プログラミング」と呼ばれるプログラミングパラダイム全体がここから生まれましたが、gotoが自動的に悪いコードを意味することに誰もが同意するわけではありません。

構造化プログラミングの最も重要な点は、コードの構造がフローを決定する必要があるということです。可能な場合は常に、goto、break、またはフローを決定し続けるのではなく、コードのフローを決定する必要があります。同様に、ループまたは関数への複数のエントリポイントと出口ポイントを持つことも、そのパラダイムでは推奨されません。

明らかにこれが唯一のプログラミングパラダイムではありませんが、オブジェクト指向プログラミング(ala Java)のような他のパラダイムに簡単に適用できることがよくあります。

あなたの教師はおそらく教えられてきたと思います。私たちのコードを構造化し、構造化プログラミングの暗黙のルールに従うことで、「スパゲッティコード」を回避するのが最善であることをクラスに教えようとしています。

ブレークを使用する実装には本質的に「誤り」はありませんが、ループの条件がwhile()条件内で明示的に指定されているコードを読む方がはるかに簡単であり、過度にトリッキーになる可能性を排除すると考える人もいます。初心者プログラマーがコードで頻繁にポップアップしているように見えるwhile(true)条件を使用すると、誤って無限ループを作成したり、コードを読みにくくしたり、不必要に混乱させたりするリスクがあります。

皮肉なことに、例外処理は構造化プログラミングからの逸脱が確実に発生する領域であり、Javaでのプログラミングにさらに進むにつれて期待されます。

また、講師が、その章またはテキストのレッスンで教えられている特定のループ構造または構文を使用する能力を実証することを期待していた可能性があり、作成したコードは機能的に同等であるにもかかわらず、そのレッスンで学習することになっていた特定のスキル。


2
これがEdsger Dijkstraの論文です。それは良い読み物です。
Roy Prins

14

入力を読み取るための通常のJava規則は次のとおりです。

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String strLine;

while ((strLine = br.readLine()) != null) {
  // do something with the line
}

そして、入力を読み取るための通常のC ++規則は次のとおりです。

#include <iostream>
#include <string>
std::string data;
while(std::readline(std::cin, data)) {
  // do something with the line
}

そしてCでは、それは

#include <stdio.h>
char* buffer = NULL;
size_t buffer_size;
size_t size_read;
while( (size_read = getline(&buffer, &buffer_size, stdin)) != -1 ){
  // do something with the line
}
free(buffer);

または、ファイル内のテキストの最長行がどれだけ長いかわかっている場合は、次のことができます

#include <stdio.h>
char buffer[BUF_SIZE];
while (fgets(buffer, BUF_SIZE, stdin)) {
  //do something with the line
}

ユーザーがquitコマンドを入力したかどうかをテストする場合は、これら3つのループ構造のいずれかを簡単に拡張できます。私はあなたのためにそれをJavaでやります:

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;

while ((line = br.readLine()) != null  && !line.equals("quit") ) {
  // do something with the line
}

したがって、breakまたはgoto正当化されるケースは確かにありますが、ファイルまたはコンソールから1行ずつ読み取る場合は、それを実行するためのwhile (true)ループは必要ありません。プログラミング言語によってすでに提供されています。入力コマンドをループ条件として使用するための適切なイディオム。


実際、while (true)これらの従来の入力ループの代わりにループを使用している場合、ファイルの終わりを確認するのを忘れている可能性があります。
ケンブルーム

3
ループの最初の部分をwhile条件に詰め込むことによって、これを効果的に達成しています。最終的なJavaの状態では、すでにかなりの量になっています。継続するかどうかを決定するために大規模な操作を行う必要がある場合は、かなり長くなる可能性があります。あなたはそれを別の関数に分割することができ、それが最善かもしれません。ただし、1つの関数でそれを実行する必要があり、続行するかどうかを決定する前に重要な作業while(true)がある場合は、最善の方法です。
poolie '28 / 07/28

@poolie:次に、ループの条件(EOFをチェックする)としてreadコマンドを使用し、breakステートメントとしてループ内の他の条件をチェックするのがおそらく最善です。
ケンブルーム

1
それが価値があることについてgets()は、一般的な慣習ではありません。バッファオーバーフローを助長します。fgets(buffer, BUFFER_SIZE, file)標準的な方法にはるかに似ています。
Dave

@Dave:使用する回答を編集しましたfgets
ケンブルーム

12

これはそれほどひどいことではありませんが、コーディング時には他の開発者を考慮する必要があります。学校でも。

仲間の開発者は、ループ宣言で、ループのexit句を確認できるはずです。あなたはそれをしませんでした。ループの途中でexit句を非表示にし、一緒に来てコードを理解しようとする他の人のためにより多くの作業を行いました。これは、「ブレーク」などが回避されるのと同じ理由です。

そうは言っても、現実の世界にはまだたくさんのコードが存在します。


4
ループが最初から始まっている場合は、ループの内部または内部while (true)に存在するbreakか、またはreturn永久に実行されることは明らかです。単純明快であることは重要ですがwhile(true)、それ自体は特に悪くはありません。ループの反復全体にわたって複雑な不変変数を持つ変数は、はるかに不安を引き起こすものの例です。
poolie '28

4
検索の深さを3レベルにしてから、なんとかしてみてください。その時点から戻るだけでは、コードははるかに悪化します。
dascandy

11

それはあなたの銃、あなたの弾丸、そしてあなたの足です...

あなたがトラブルを求めているのでそれは悪いです。あなたやこのページの他のポスターではなく、短い/シンプルなwhileループの例があります。

問題は、将来、非常にランダムな時期に発生します。別のプログラマーが原因である可能性があります。ソフトウェアをインストールする人かもしれません。エンドユーザーの可能性があります。

どうして?700K LOCアプリが、すべてのCPUが飽和するまでCPU時間の100%を徐々に消費し始める理由を見つけなければなりませんでした。それは驚くべきwhile(true)ループでした。それは大きくて厄介でしたが、次のように要約されました。

x = read_value_from_database()
while (true) 
 if (x == 1)
  ...
  break;
 else if (x ==2)
  ...
  break;
and lots more else if conditions
}

最終的なelseブランチはありませんでした。値がif条件に一致しなかった場合、ループは時間の終わりまで実行され続けました。

もちろん、プログラマーは、プログラマーが期待する値を選択しなかったとしてエンドユーザーを非難しました。(次に、コード内のwhile(true)のすべてのインスタンスを削除しました。)

IMHO while(true)のような構造を使用することは、防御的なプログラミングには適していません。それはあなたを悩ませるために戻ってきます。

(しかし、i ++の場合でも、すべての行にコメントしなかった場合は、教授が辞任することを思い出します。)


3
防御的プログラミングに関するコメントの+1。
JHarnach

3
あなたの例では、while(true)の選択のためではなく、コードの周りにループを配置するという考えのために、コードは愚かです。
フロリアンF

実際、そのループのポイントは何でしたか?:)
juzzlin 2014年

6

構造化プログラミングの構成要素が(やや構造化されていない)ブレークアンドコンティニューステートメントよりも好まれているという意味では悪いです。比較すると、これらはこの原則に従って「後藤」よりも優先されます。

私は常にコードをできるだけ構造化することをお勧めします...しかし、Jon Skeetが指摘するように、それよりも構造化しないでください!


5

私の経験によれば、ほとんどの場合、ループには「メイン」状態が続きます。これは、while()演算子自体に書き込む必要がある条件です。ループを壊す可能性のある他のすべての条件は二次的なものであり、それほど重要ではありません。それらは追加のif() {break}ステートメントとして書くことができます。

while(true) 多くの場合、混乱しやすく、読みにくくなります。

これらのルールはケースの100%をカバーしているのではなく、おそらく98%しかカバーしていないと思います。


よく言った。これは、for()でforループを使用するようなものです:while(true){i ++; ...}。ループ条件を「シグネチャ」ではなくループ内に埋め込んでいます。
LegendLength 2016年

3

なぜ使用しないのかについての答えは必ずしもありませんがwhile (true)、私はこの漫画と付随する作者の発言が、なぜdo-whileではなくwhileをするのかについての簡潔な説明であることに気づきました。

あなたの質問に関して:に固有の問題はありません

while(true) {
   do_stuff();
   if(exit_time) {
      break;
   }
}

... あなたが何をしているを知っていて、exit_timeある時点でそれがに評価されることを確認する場合true

教師は、自分がwhile(true)何をしているかを正確に理解できるようになるまでは、重大な間違いを犯す簡単な方法であるため、使用を勧めません。


とループの終わりにを設定するよりも、設定exit_time = false; while(!exit_time) { execute_stuff(); }と設定のdo { execute_stuff(); } while(! exit_time );両方がはるかに明確だと思います。ブレークはループへのショートサーキットです。ループの途中でショートサーキットとして使用する場合はまったく問題ありませんが、whileステートメントの条件とループの最後にブレークがあるかどうかを評価する必要があります。if( condition ) { break; }while(true)
ジンボブ博士、2011

その漫画に関して:do-whileはできるすべてのwhileができるわけではありませんが、do-whileはできることもできる一方で、whileができることを行う方が良い場合があります。
Theraot 2014

3

ブールフラグを使用して、whileループをいつ終了するかを示すことができます。Breakそしてgo to、ソフトウェアが維持するのが難しい理由でした-software-crisis(tm)-そして、回避されるべきであり、簡単にそれもまた可能です。

あなたが実用的であるかどうかは問題です。実用的なコーダーは、その単純な状況では単にbreakを使用する場合があります。

しかし、それらを使用しないという習慣を身に付けるのは良いことです。それ以外の場合は、を使用するとコードの可読性と保守性が難しくなる複雑なネストされたループなど、不適切な状況でそれらを使用することができますbreak


8
私の経験では、ブールフラグを維持することはおそらく明確ではなく、それほど明確ではない可能性があるからですか?
Jon Skeet、2011

3
@Jon Skeetさて、それは良い習慣を身につけること、または休憩を使って悪いものを自分で訓練することの問題です。"running"と呼ばれるboolは明確ではありませんか?その明白で、デバッグが簡単で、前に述べたように、良い習慣は維持するのに良いものです。問題はどこだ?
Daniel Leschkowski、2011

4
その後、持っている私を必要とすることを実行していると呼ばれるBOOL if (running)私が望むすべてがループを終了するときに、コードのすべての残りの部分をインデント、ループ内では間違いなくある少ない述べ、単純なbreak文よりも私には明確なまさに私が欲しいもの。ブレイクを悪い習慣として公理的に使用することを検討しているようです-私はそのようには考えていません。
Jon Skeet

2
ループ内にif(実行中)があるのはなぜですか?ループの終わりのブレークと同様に、ブレークを使用する代わりにブレークアウトしてフラグを反転するかどうかを確認し、while(true)ではなくwhile(running)でフラグを使用します。私は真剣にあなたのポイントを取得しません。私は実用的なコーダは、特定の状況でブレークを使用する場合がありますことを同意するだろうが、私は本当にあなたが私の提案の下で作られたコメントを取得いけない
ダニエルLeschkowski

3
終了するかどうかを決定した後、ループ内で何もする必要がないと想定しています。たとえば、OPの状況で、辞めない場合は、さらに入力を求める必要があります。
Jon Skeet

3

多分私は運が悪いです。あるいは、経験が足りないのかもしれません。しかし、私が扱っ思い出すたびにwhile(true)持つbreak内部は、適用コード向上させることができた抽出方法をながら、ブロックまま、while(true)すべての変換が、(偶然?)breakに秒return秒。

while(true)休憩なし(つまり、リターンまたはスローあり)の私の経験では、非常に快適で理解しやすいです。


  void handleInput() {
      while (true) {
          final Input input = getSomeInput();
          if (input == null) {
              throw new BadInputException("can't handle null input");
          }
          if (input.isPoisonPill()) {
              return;
          }
          doSomething(input);
      }
  }

3

はい、それはかなり悪いと思います...少なくとも、多くの開発者にとっては。ループ状態を考慮しない開発者の症状です。その結果、エラーが発生しやすくなります。


2

大きな問題はありませんwhile(true)とのbreak文は、しかし、いくつかは、そのは少しのコードの可読性を下げると思うことがあります。変数に意味のある名前を付け、適切な場所で式を評価してください。

あなたの例では、次のようなことをする方がはるかに明確です:

do {
   input = get_input();
   valid = check_input_validity(input);    
} while(! valid)

これは、do whileループが長くなる場合に特に当てはまります。追加の反復があるかどうかを確認するためのチェックがどこで行われているかが正確にわかります。すべての変数/関数には、抽象化のレベルで適切な名前があります。このwhile(true)ステートメントは、処理があなたが思った場所にないことを伝えています。

多分あなたはループを通して2回目に異なる出力が欲しいです。何かのようなもの

input = get_input();
while(input_is_not_valid(input)) {
    disp_msg_invalid_input();
    input = get_input();
}

私にはもっと読みやすいようです

do {
    input = get_input();
    if (input_is_valid(input)) {
        break;
    }
    disp_msg_invalid_input();
} while(true);

繰り返しますが、簡単な例では、どちらも非常に読みやすくなっています。ただし、ループが非常に大きくなったり、入れ子が深くなったりした場合(つまり、おそらく既にリファクタリングが行われているはずです)、最初のスタイルが少し明確になる可能性があります。


1

私は多くの機能で類似したものを使用していますが、ロジックは逆です。

DWORD dwError = ERROR_SUCCESS;

do
{
    if ( (dwError = SomeFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }

    if ( (dwError = SomeOtherFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }
}
while ( 0 );

if ( dwError != ERROR_SUCCESS )
{
    /* resource cleanup */
}

1

それは美的感覚のほうが多く、ループの宣言でループが停止する理由を明示的に知っているコードを読むのがはるかに簡単です。


1

一般的に、それが良いアイデアと見なされない理由は、構造を最大限に活用していないためです。また、学生が「荷物」を持って来ると、多くのプログラミングインストラクターが気に入らないと思う傾向があります。つまり、彼らが学生のプログラミングスタイルに最も影響を与えることが好きだと思います。だから、おそらくそれはインストラクターのうんざりだ。


1

私にとって、問題は読みやすさです。

真の条件を持つwhileステートメントは、ループについて何も伝えません。それはそれを理解する仕事をはるかに困難にします。

これらの2つのスニペットから何を理解する方が簡単でしょうか?

do {
  // Imagine a nice chunk of code here
} while(true);

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);

0

先生に休憩を使うのは、木の枝を割って果物を手に入れるようなものだと思います。他のいくつかのトリックを使って(枝を下げて)果物を手に入れ、枝がまだ生きているようにします。


0

1)に問題はありません do -while(true)

2)あなたの先生は間違っています。

NSFS !!:

3)ほとんどの教師は教師であり、プログラマーではありません。


@Connellは先生がそれを聞くまで待ちます!
パセリエ、2011

1
私はプログラミングの面で独学です。私はで判断しなければならないすべては...すべてが絶対位置のdivたようにDreamweaverを使用し、ウェブサイトを作るために私たちを教え中等学校での私のITの先生、である
コネル

@Connellに私の投稿を表示して同意を示します
Pacerier

2
「できない人、教えなさい。」-それはかなり大きな一般化だと思いませんか?医師、エンジニア、パイロットなどは、インストラクターの能力が不足しているため、独学で学習する必要がありますか?
filip-fku 2011

@ filip-fkuすごいすごい〜すごい!
Pacerier 2011

0

ループがバックグラウンドスレッドで実行されている場合は悪い可能性があります。そのため、UIスレッドを終了してアプリケーションを閉じると、そのコードは引き続き実行されます。他の人がすでに述べたように、キャンセルの方法を提供するために、常に何らかのチェックを使用する必要があります。

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