225

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

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

私のテストでは、コンソール入力をスキャンしているだけですが、 usingbreakは に似ているため、この種のループは推奨されないと言われgotoました。

goto私はと Java のいとこの の落とし穴を完全に理解しbreak:labelており、それらを使用しないという良識があります。また、より完全なプログラムがあれば、たとえばプログラムを終了するなど、他の脱出手段が提供されることもわかっていますが、それは私の教授が挙げた理由ではなかったので...

何が問題なのdo-while(true)ですか?

4

21 に答える 21

222

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

それが私が最初に書く状況では、ほとんどの場合、少なくともそれをより明確なものにリファクタリングしようとします。仕方がない場合もありますが (または、代わり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。また、 return のときに何が起こるかを理解しようとしている場合は、ブロックの残りの部分を注意深く調べて、そこにあることを確認する必要があります。に設定されているかどうかに関係なく発生するブロックののものではありません。testConditiontrueelserunningfalse

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

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

于 2011-07-27T19:55:11.807 に答える
103

AFAIK何も、本当に。goto先生はどこかでそれが本当に悪いと聞いたので、ただアレルギーがあります。それ以外の場合は、次のように記述します。

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

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

おそらくこれはよりクリーンです(すべてのループ情報がブロックの上部に含まれているため):

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

}
于 2011-07-27T19:57:44.053 に答える
19

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

構造化プログラミングの要点は、コードの構造がフローを決定する必要があるということです。同様に、ループまたは関数への複数のエントリ ポイントとエグジット ポイントを持つことも、そのパラダイムでは推奨されません。

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

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

break を使用する実装に本質的に「問題」があるわけではありませんが、ループの条件が while() 条件内で明示的に指定されている場合、コードが非常に読みやすくなり、過度にトリッキーになる可能性が排除されると考える人もいます。while(true) 条件を使用すると、誤って無限ループを作成したり、読みにくいコードや不必要に混乱したコードを作成したりするリスクなど、初心者のプログラマーがコードで頻繁に使用するように思われる落とし穴が確実に存在します。

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

また、インストラクターは、テキストのその章またはレッスンで教えられている特定のループ構造または構文を使用する能力を示すことを期待していた可能性があります。そのレッスンで学ぶはずだった特定のスキル。

于 2011-07-28T21:09:03.707 に答える
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)。プログラミング言語が既に提供しています。入力コマンドをループ条件として使用するための適切なイディオムを使用します。

于 2011-07-28T00:42:23.547 に答える
12

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

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

そうは言っても、現実の世界にはまだたくさんのコードでこのようなものが見られます。

于 2011-07-27T19:58:47.080 に答える
12

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

面倒くさいからダメです。短い/単純なwhileループの例を持っているのは、あなたやこのページの他のポスターではありません.

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

なんで?すべての CPU が飽和状態になるまで、700K の LOC アプリが徐々に 100% の CPU 時間を消費し始める理由を突き止める必要がありました。それは驚くべき 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) のすべてのインスタンスを削除しました。)

私見ですが、while(true) のような構造を使用するのは防御的なプログラミングではありません。それはあなたを悩ませるために戻ってきます。

(しかし、i++ であっても、すべての行にコメントを付けないと、教授が評価を下げたのを覚えています;)

于 2011-07-29T02:06:47.653 に答える
6

構造化されたプログラミング構造が (やや構造化されていない) break および continue ステートメントよりも好まれるという意味で、これは悪いことです。比較すると、この原則に従って「goto」よりも優先されます。

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

于 2011-07-27T20:00:51.263 に答える
5

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

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

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

于 2011-07-27T19:59:18.720 に答える
3

を使用しない理由について必ずしも答えはありませんが、このコミックとそれに付随する著者の声明は、do-while ではなく while を使用する理由についての簡潔な説明であるwhile (true)ことが常にわかりました。

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

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

...自分が何をしているのを知っていて、exit_timeある時点で評価されることを確認している場合true

教師は、while(true)自分が何をしているのかを正確に理解できるようになるまで、そしてそうでない限り、重大な間違いを犯しやすい方法であるため、使用を思いとどまらせる.

于 2011-07-28T12:24:02.333 に答える
3

while ループをいつ終了するかを示すには、ブール型フラグを使用するだけです。Breakそしてgo to、ソフトウェアの保守が困難である理由 (software-crisis(tm)) があり、回避する必要があり、簡単に回避することもできます。

あなたが実用的かどうかの問題です。実用的なコーダーは、その単純な状況でブレークを使用するだけかもしれません。

しかし、それらを使用しないという習慣を身につけるのは良いことです。そうしないと、複雑なネストされたループのように、break.

于 2011-07-27T19:56:58.620 に答える
3

はい、かなり悪いと思います...または少なくとも、多くの開発者にとって。これは、開発者がループ条件について考えていないことを示しています。その結果、エラーが発生しやすくなります。

于 2011-08-04T02:31:14.413 に答える
2

多分私は不運です。あるいは、経験が足りないのかもしれません。しかし、内部にあることを思い出すたびに、抽出メソッドをwhile-blockに適用するコードを改善することができましたwhile(true)。これにより偶然の一致で?)すべてのsがsに変換されました。breakwhile(true)breakreturn

私の経験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);
      }
  }
于 2011-07-28T13:28:15.067 に答える
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);

繰り返しになりますが、簡単な例を使用すると、どちらも非常に読みやすくなります。しかし、ループが非常に大きくなったり、ネストが深くなったりした場合 (つまり、すでにリファクタリングしているはずです)、最初のスタイルの方が少し明確かもしれません。

于 2011-07-28T04:36:22.607 に答える
1

それはより美学的なものであり、ループの宣言でループが停止する理由を明示的に知っているコードを読むのがはるかに簡単です。

于 2011-07-27T19:57:09.250 に答える
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 */
}
于 2011-07-28T16:44:25.237 に答える
1

一般的に、それが良いアイデアと見なされない理由は、その構造を最大限に活用していないからだと思います。また、多くのプログラミング講師は、学生が「荷物」を持って来るのを嫌うと思いがちです。つまり、彼らは学生のプログラミング スタイルに主な影響を与えることを望んでいると思います。おそらく、それはインストラクターの単なるペットピーブです。

于 2011-07-29T20:56:15.463 に答える
1

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

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

これら 2 つのスニペットのうち、どちらが理解しやすいでしょうか?

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

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);
于 2011-08-03T03:19:25.120 に答える
0

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

于 2011-08-02T15:08:44.720 に答える
0

先生に break を使うのは、果物を得るために木の枝を壊すようなものだと思います。果物を手に入れても枝がまだ生きているように、他のトリック (枝を曲げる) を使用します。:)

于 2011-07-29T07:37:56.967 に答える