私は数年前から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)
の何が問題になっていますか?
私はそれがbadだとは言わないでしょうが、同様に私は通常少なくとも代替を探します。
それが私が最初に書くことである状況では、少なくともtryをより明確なものにリファクタリングするために、私はほとんど常にそうします。時には助けられない場合もあります(または、代わりに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
を返すときに何が起こるかを考えている場合は、残りのブロックを注意深く調べる必要がありますafterelse
がrunning
に設定されているかどうかにかかわらず発生するfalse
ブロックがないことを確認します。
break
ステートメントは、意図をより明確に伝え、以前の状態を心配することなく、ブロックの残りの部分が必要なことを実行できるようにします。
これは、メソッド内の複数のreturnステートメントについて人々が持っているのとまったく同じ種類の引数であることに注意してください。たとえば、最初の数行内でメソッドの結果を解決できる場合(たとえば、一部の入力がnull、空、またはゼロであるため)、結果を保存する変数を持つよりも、その答えを直接返す方がわかりやすい、他のコードのブロック全体、および最終的にreturn
ステートメント。
本当に何もない。教師はgoto
に対してアレルギーがあるだけなのです。そうでなければあなたはただ書くだろう:
bool guard = true;
do
{
getInput();
if (something)
guard = false;
} while (guard)
これはほとんど同じことです。
たぶんこれはきれいです(すべてのループ情報はブロックの一番上に含まれているので):
for (bool endLoop = false; !endLoop;)
{
}
Douglas Crockfordは、JavaScriptがloop
構造体をどのように含んでいることを望んでいるかについて発言しました。
loop
{
...code...
}
私はJavaがloop
構造を持っているためにさらに悪いとは思わない。
while(true)
ループに本質的に悪いことは何もありませんが、はであり、教師がそれらを推奨しない傾向があります。教育の観点からすると、生徒に無限のループを作成させ、そのループがこれまでエスケープされない理由を理解することは非常に簡単です。
しかし、彼らがめったに言及していないのは、allループメカニズムは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();
}
worksのようにループを設定できる限り、使用する構成は重要ではありません。がfor
ループに収まるようにになっている場合は、for
ループを使用します。
最後の1つは、ループを単純にすることです。すべての反復で発生する必要がある機能がたくさんある場合は、それを関数に入れます。正常に機能した後はいつでも最適化できます。
1967年にDijkstraは、業界誌にコードの品質を向上させるためになぜ後藤を高級言語から排除すべきかについての記事を書いた。 「構造化プログラミング」と呼ばれる全体的なプログラミングパラダイムがこのことから生まれましたが、gotoが自動的に悪いコードを意味することに全員が同意するわけではありません。構造化プログラミングの最も重要な点は、コードの構造によって可能な限りフローを決定したり中断したりするのではなく、フローを決定することです。同様に、ループまたは関数への複数の入り口点と出口点を持つことも、そのパラダイムではお勧めできません。明らかにこれが唯一のプログラミングパラダイムではありませんが、しばしばオブジェクト指向プログラミング(ala Java)のような他のパラダイムに容易に適用することができます。あなたの先生はおそらく教えられていて、私たちのコードが構造化されていることを確認し、構造化プログラミングの暗黙のルールに従うことによって "スパゲッティコード"を避けることが最善であるとあなたのクラスに教えようとしています。 breakを使用する実装には本質的に「間違った」ものは何もありませんが、ループの条件がwhile()条件内で明示的に指定されているコードを読むのがはるかに簡単だと考える人もいます。誤って無限ループを作成したり、コードを読みにくくなったり、不必要に混乱させたりする危険性など、初心者プログラマがコード内で頻繁に出現するように思われるwhile(true)条件を使用することには間違いなく落とし穴があります。
皮肉なことに、例外処理は、構造化プログラミングからの逸脱が確実に起こり、Javaでのプログラミングに進むにつれて予想される分野です。
また、その章で説明されている特定のループ構造や構文を使用したり、テキストのレッスンを実行したりする機能を実演することを講師が期待している可能性もあります。あなたがそのレッスンで学んでいるはずだった特定のスキル。
入力を読み取るための一般的な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
が正当化されるケースは確かにありますが、ファイルやコンソールから行単位で読み込むだけであれば、それを実現するためにwhile (true)
ループを使用する必要はありません。ループ条件として入力コマンドを使用するための適切な慣用句がすでに提供されています。
それほどひどいことではありませんが、コーディングをするときは他の開発者を考慮に入れる必要があります。学校でも。
あなたの仲間の開発者は、ループ宣言で、あなたのループのためのexit節を見ることができるはずです。あなたはそうしませんでした。あなたはループの途中でexit節を隠しました。これは、 "break"のようなことが避けられるのと同じ理由です。
そうは言っても、実社会ではまだたくさんのコードの中にこのようなことがあるのが分かるでしょう。
それはあなたの銃、あなたの弾丸そしてあなたの足です...
あなたがトラブルを求めているのでそれは悪いです。あなたやこのページの他のポスターには、短い/単純なwhileループの例はありません。
トラブルは将来、非常にランダムな時期に始まるでしょう。他のプログラマーが原因の可能性があります。それはソフトウェアをインストールする人かもしれません。それはエンドユーザーかもしれません。
どうして?私は、すべてのCPUが飽和状態になるまで、なぜ700K LOCアプリが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)のインスタンスをすべて削除しました。)
私見はwhile(true)のような構造を使うのは良い防御的プログラミングではありません。それはあなたを悩ませるために戻ってきます。
(ただし、i ++の場合でも、すべての行をコメントアウトしなかった場合は、教授が格下げしたことを思い出します。)
構造化プログラミング構文が(多少構造化されていない)breakおよびcontinueステートメントよりも好まれるという意味では、悪いことです。比較すると、この原則によると、 "goto"よりも優先されます。
Jon Skeetが指摘しているように、それ以上構造化しないでください。
私の経験によると、ほとんどの場合、ループには「主な」状態が続きます。これは、while()演算子自体に書き込まれるべき条件です。ループを破る可能性がある他のすべての条件は二次的なもので、それほど重要ではありません。それらは追加のif() {break}
ステートメントとして書くことができます。
while(true)
は混乱を招きやすく、読みにくいです。
私はこれらの規則が100%の事件をカバーしているのではなく、おそらくそれらの98%しかカバーしていないと思います。
Whileループをいつ終了するかを示すためにブールフラグを使用することもできます。 Break
およびgo to
は、ソフトウェアを保守するのが難しい理由 - ソフトウェア危機(tm) - であり、避けなければならず、簡単にすることもできます。
あなたが実用的かどうかは疑問です。実用的なコーダーはその単純な状況でただbreakを使うかもしれません。
しかし、break
を使用するとコードの可読性と保守性が困難になるような複雑なネストループの場合のように、不適切な状況で使用する可能性があります。
必ずしもwhile (true)
を使わない理由についての答えではありませんが、私はいつも このコミックとそれに付随する作者の声明 do-whileの代わりにwhileをやるべき理由についての簡潔な説明を見つけました。
あなたの質問に関して:で本質的な問題はありません
while(true) {
do_stuff();
if(exit_time) {
break;
}
}
...ifあなたがしていることを知っていて、exit_time
がいつかtrue
に評価されることを確認すること。
教師がwhile(true)
の使用をお勧めしません。あなたが自分のしていることを正確に知っているということになるまでは、重大なミスを犯すのは簡単な方法だからです。
はい、それはかなり悪いと思います。少なくとも、多くの開発者にとってはそうです。ループ条件について考えていないのは開発者の症状です。その結果、エラーが発生しやすくなります。
1)do -while(true)
に問題はありません
2)あなたの先生は間違っています。
NSFS !!:
3)ほとんどの教師は教師でありプログラマーではありません。
たぶん私は不運です。または私は経験が足りないかもしれません。しかし、break
を内部に持つwhile(true)
を扱うことを思い出すたびに、 Extract Method towhile-blockを適用するコードを改善することが可能でした。これはwhile(true)
を保持しましたが(偶然にも)すべてのbreak
sをreturn
sに変換しました。
私の経験では、休憩なしの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);
}
}
break
ステートメントを使用したwhile(true)
には大きな問題はありませんが、コードの可読性がわずかに低下すると考える人もいるかもしれません。意味のある名前を変数に与え、適切な場所で式を評価してください。
あなたの例では、次のようなことをしたほうがはるかに明確に思えます。
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);
繰り返しますが、簡単な例では、どちらも非常に読みやすいです。しかし、ループが非常に大きくなったり深くネストされたりした場合(これはおそらくすでにリファクタリングされているはずであることを意味します)、最初のスタイルは少し明確になるかもしれません。
私にとって、問題は読みやすさです。
真の条件を持つwhileステートメントは、ループについて何も伝えません。それを理解するという仕事ははるかに難しくなります。
これら2つの断片のうち、理解しやすいものは何でしょうか。
do {
// Imagine a Nice chunk of code here
} while(true);
do {
// Imagine a Nice chunk of code here
} while(price < priceAllowedForDiscount);
それはもっと美的なことで、あなたが明示的に知っているコードを読むのはずっと簡単ですなぜループは宣言の中で止まるでしょう。
私は多くの関数の中で似たような、しかし反対の論理を使ったものを使っています。
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 */
}
私は一般的にそれが良い考えと考えられていない理由はあなたがそれが完全な可能性のために構文を使用していないということであると言うでしょう。また、学生が「手荷物」を持ってくると、多くのプログラミング講師はそれを好まないと思う傾向があります。それは、彼らが彼らの学生のプログラミングスタイルへの主な影響力であることを好むと私が思うことを意味します。だから、おそらくそれは単に講師の悩みの種です。
ループがバックグラウンドスレッドで実行されている場合は悪いかもしれません。そのため、UIスレッドを終了してアプリケーションを閉じても、そのコードは実行され続けます。他の人がすでに言ったように、あなたはキャンセルの方法を提供するために常にある種のチェックを使うべきです。
私はあなたの先生に休憩を使うのは実をつけるために木の枝を壊すのと同じようにあなたが実を得て、枝がまだ生きているように、他のトリックを使う(枝をお辞儀をする)。