web-dev-qa-db-ja.com

forループ内で、可能であれば、ブレーク条件を条件フィールドに移動する必要がありますか?

時々私はこのようなブレークを必要とするループが必要です:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

書くことに不快に感じる

if(condition){
    break;
}

3行のコードを消費するためです。そして、ループは次のように書き換えられることがわかりました。

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

だから私の質問は、可能であれば条件を条件フィールドに移動してコード行を減らすことは良い習慣ですか?

48
ocomfd

あなたが与えたこれら二つの例は機能的に同等ではありません。オリジナルでは、条件テストは「その他のコード」セクションの後に行われますが、変更バージョンでは、最初にループ本体の開始時に行われます。

行数を減らすことだけを目的としてコードを書き直すことはできません。明らかに、そのように機能する場合、それは素晴らしいボーナスですが、読みやすさや正確さを犠牲にして行われるべきではありません。

154
Pete

「3行のコードを消費する」という主張はよくありません。結局のところ、次のように書くことができます。

_if (condition) break;
_

1行を消費します。

ifがループの途中にある場合は、当然、それを別のテストとして存在させる必要があります。ただし、このテストがブロックの最後にある場合は、ループの両端に継続テストがあるループが作成されているため、コードがさらに複雑になる可能性があります。ただし、if (condition)テストは、ブロックが実行された後にのみ意味をなす場合があることに注意してください。

しかし、そうではないと仮定すると、他のアプローチを採用することによって:

_for(int i=0;i<array.length && !condition;i++){
    //some other code
}
_

終了条件は一緒に保持されるため、物事を簡素化できます(特に「他のコードが長い場合)」。

もちろん、正解はありません。したがって、言語のイディオム、チームのコーディング規約などに注意してください。しかし、機能的に意味がある場合は、全体として、単一テストアプローチを採用します。

51
David Arno

この種の質問は、プログラミングが行われている限り、議論のきっかけとなっています。私の帽子をリングに投げ込むには、break条件付きのバージョンを選びます。理由は次のとおりです(この固有の場合):

forループは、ループ内の反復値を指定するためだけにあります。ロジックIMOを混乱させるその中での破壊状態のコーディング。

別のbreakがあると、通常の処理中に値がforループで指定されたとおりになり、条件が発生した場所で処理が続行されますexcept

少し背景として、breakのコーディングを開始し、(例外的なプログラマーの指示の下で)forループに条件を何年も入れてから、breakスタイルに戻しました。私が消費していたさまざまなコードテーマの一般的なスタイル)。

これは1日の終わりの聖なる戦争のもう1つです。ある人はあなたがすべきだと言う人もいますneverループから人為的に脱出します。そうでなければ、それは本当のループではありません。自分が読みやすいと思うことをすべて実行します(自分自身と他の人の両方にとって)。キーストロークを減らすことが本当に重要な場合は、週末にコードゴルフをしてください-ビールはオプションです。

49
Robbie Dee

forループは反復用です何かの上に1 -それらはlol-let's-pull-some-random-stuff-from-the-body-and-put-them-in-the-loop-headerだけではありません-3つの式には非常に特定の意味があります。

  • 最初のものはiteratorを初期化するためのものです。 iterationに使用されている限り、インデックス、ポインタ、反復オブジェクトなど何でもかまいません。
  • 2つ目は、最後に到達したかどうかを確認するためのものです。
  • 3番目は、イテレーターを進めるためのものです。

アイデアは、ループ内のロジック(whatをループごとに(howto iterate)から分離することです)。多くの言語には通常、反復の詳細から解放されるfor-eachループがありますが、言語にその構成がない場合や、シナリオで使用できない場合でも、ループヘッダーを制限する必要があります反復処理に。

だから、あなたは自分自身に尋ねる必要があります-あなたの状態は反復処理またはループ内のロジックについてですか?たぶん、それはループ内のロジックに関するものです。そのため、ヘッダー内ではなくループ内でチェックする必要があります。

1他のループとは対照的に、それは何かが起こるのを「待っている」。 for/foreachループには、反復する項目の「リスト」の概念が必要です-そのリストが遅延または無限または抽象であっても。

43
Idan Arye

if (condition)付きのバージョンを使用することをお勧めします。読みやすく、デバッグも簡単です。 3行追加してコードを書いても、問題はありませんが、次の人がコードを理解しやすくなります。

8
Aleksander

特定の要素をスキップする必要があるコレクションを反復処理する場合は、新しいオプションをお勧めします。

  • 反復する前にコレクションをフィルタリングします

JavaとC#の両方を使用すると、これは比較的簡単です。C++に、適用されない特定の要素をスキップするためのファンシーなイテレータを作成する方法があったとしても、驚かないでしょう。しかし、これは選択した言語でサポートされている場合は本当にオプションです。breakを使用する理由は、要素を処理するかどうかに条件があるためです。

それ以外の場合、条件をforループの一部にする非常に十分な理由があります-初期評価が正しいと仮定します。

  • ループが早期に終了したときに簡単に確認できます

私はあなたのforループが数百行を取り、いくつかの条件文といくつかの複雑な数学がそこで行われるコードに取り組んできました。これで遭遇する問題は次のとおりです。

  • ロジックに埋め込まれたbreakコマンドは見つけるのが難しく、意外なこともあります
  • デバッグでは、何が起こっているのかを正確に理解するために、ループの各反復をステップ実行する必要があります
  • 多くのコードには、簡単に元に戻せるリファクタリングや、書き換え後の機能の等価性を支援する単体テストがありません

その状況では、私は次のガイドラインを優先順に推奨します。

  • コレクションをフィルタリングして、可能であればbreakの必要性を排除します
  • Forループ条件の条件部分を作成する
  • 可能であれば、ループの先頭にbreakを含む条件を配置します
  • 休憩に注意を引く複数行のコメントを追加し、なぜそれがあるのか

これらのガイドラインは常に、コードの正確性の影響を受けます。意図を最もよく表し、コードの明確さを改善できるオプションを選択してください。

8
Berin Loritsch

他の方法で大幅なメリットを得ない限り、最小の驚きのアプローチを取ることを強くお勧めします。

人々は、単語を読むときにすべての文字を読むわけではなく、本を読むときにすべての単語を読むわけではありません。読書に長けている場合は、単語や文の概要を見て、残りの部分を脳に記入させます。

そのため、開発者がこれをループの単なる標準であると想定し、それを見さえしない可能性があります。

for(int i=0;i<array.length&&!condition;i++)

とにかくそのスタイルを使いたいなら、パーツを交換することをお勧めしますfor(int i=0;i<および;i++)これは、ループの標準であることを読者の脳に伝えます。


if-breakを使用するもう1つの理由は、for- conditionで常にアプローチを使用できるとは限らないことです。ブレーク条件が複雑すぎてifステートメント内に隠すことができない場合、またはbreakループ内でのみアクセス可能な変数に依存している場合は、for-forを使用する必要があります。

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

したがって、if-breakを使用することにした場合は、カバーされます。ただし、for- conditionsを使用する場合は、if-breakfor- conditionsを組み合わせて使用​​する必要があります。一貫性を保つために、条件が変化するたびに、for- conditionsとif-breakの間で条件を前後に移動する必要がありますです。

8
Peter

変換では、ループに入るときにconditiontrueに評価されると想定しています。定義されていないため、このインスタンスではその正しさについてコメントすることはできません。

ここで「コード行の削減」に成功したら、次に進んで

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

同じ変換を適用して、

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

以前は無条件にfurther codeを無条件に行うようになったため、これは無効な変換です。

より安全な変換スキームは、some other codeを抽出し、conditionを別の関数に評価することです。

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}
4
Caleth

TL; DR特定の状況で最もよく読むものをすべて実行

この例を見てみましょう:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

この場合、somethingがtrueの場合にforループのコードを実行したくないので、somethingのテストを条件フィールドに移動することは妥当です。

for ( int i = 1; i < array.length && !something; i++ )

繰り返しになりますが、ループの前にsomethingtrueに設定できるかどうかに応じて、ループ内ではない場合、より明確になる可能性があります。

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

これを想像してみてください:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

そこに非常に明確なメッセージを送っています。配列の処理中にsomethingがtrueになった場合は、この時点で操作全体を中止します。チェックを条件フィールドに移動するには、次のことを行う必要があります。

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

間違いなく、「メッセージ」は泥だらけになり、2つの場所で障害状態をチェックしています。障害状態が変化し、そのうちの1つを忘れた場合はどうなりますか?

もちろん、forループやconditionにはさまざまな形があり、すべて独自のニュアンスがあります。

読みやすく(これは明らかに主観的です)、簡潔にコードを記述します。厳格なコーディングガイドラインのセットの外all "rules"には例外があります。あなたが感じていることを書いて、最善の意思決定を表現し、将来の間違いの可能性を最小限に抑えます。

4

ループヘッダーのすべての条件がわかり、breakが大きなブロック内で混乱する可能性があるため、魅力的かもしれませんが、forループは、ほとんどのプログラマーが特定の範囲でループすることを期待するパターンです。

特にそうするので、追加のブレーク条件を追加すると混乱が生じる可能性があり、機能的に同等である場合は 見づらい になります。

多くの場合、配列に少なくとも1つの要素があると仮定して、両方の条件をチェックするwhileまたはdo {} whileループを使用することをお勧めします。

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

条件をチェックするだけで、ループヘッダーからコードを実行しないループを使用すると、条件がチェックされたときに、実際の条件と副作用のあるコードが明確になります。

2
allo

私の意見では、あなたがすべきではない理由はほとんどありません。

  1. これにより、読みやすさが低下します。
  2. 出力は同じでない場合があります。ただし、do...whileループ(whileではなく)。条件がコード実行後にチェックされるため。

それに加えて、これを考慮して、

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

では、これらの条件をfor条件に追加することで、本当にそれを実現できますか?

1
Harsh

これは悪いことです。コードは人間が読み取ることを意図しているためです。非慣用的なforループは読み取りを混乱させることがよくあります。つまり、バグがここに隠れている可能性が高くなりますが、元のコードは可能だった可能性があります短くて読みやすくすると同時に改善します。

配列内の値を検索したい場合(提供されているコードサンプルはやや一般的なものですが、このパターンの場合もあります)、その目的のために特別にプログラミング言語によって提供されている関数を明示的に使用することができます。たとえば(プログラミング言語が指定されていないため、疑似コード、ソリューションは特定の言語によって異なります)。

array.find(item -> item.valid)

あなたのコードがあなたが必要とするものを具体的に言っているので、これはそれを単純化する一方で、ソリューションを著しく短縮するはずです。

1
Konrad Borowski

過度に複雑なforステートメントが読みづらいのではないかと心配している場合、それは間違いなく有効な懸念事項です。垂直方向のスペースを無駄にすることも問題です(それほど重要ではありません)。

(個人的には、ifステートメントには常に中括弧が必要であるという規則が嫌いです。これは、主にここで垂直スペースが無駄になる原因です。問題はwasting垂直スペースです。使用意味のある行のある垂直スペースは問題になりません。)

1つのオプションは、それを複数の行に分割することです。これは、複数の変数と条件を含む非常に複雑なforステートメントを使用している場合に行うべきことです。 (これは、垂直方向のスペースをより効果的に使用するために、可能な限り多くの行に意味のあるものを与えています。)

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

より適切なアプローチは、より宣言的で命令的でない形式を使用することです。これは言語によって異なりますが、この種のことを可能にするために独自の関数を書く必要があるかもしれません。 conditionが実際に何であるかも異なります。おそらく、配列の内容に応じて、何らかの方法で変更できる必要があります。

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void
0
Dave Cousineau

はい、しかしあなたの構文は型破りで、それゆえ悪い(tm)

うまくいけば、あなたの言語は次のようなものをサポートしています

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}
0
Ewan

忘れられたことの1つ:ループから抜けるとき(実装方法に関係なく、可能なすべての反復を行うわけではありません)、通常はループを終了するだけでなく、これが発生した理由も知りたいです。 。たとえば、アイテムXを探す場合、ループから抜けるだけでなく、Xが見つかったこととその場所も知りたいです。

そのような状況では、ループの外側に条件があるのは自然なことです。だから私は

bool found = false;
size_t foundIndex;

そしてループで私は書きます

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

forループ

for (i = 0; i < something && ! found; ++i)

ループ内の条件をチェックすることは非常に自然です。 「break」ステートメントがあるかどうかは、回避したいループ内にさらに処理があるかどうかによって異なります。いくつかの処理が必要で他の処理が不要な場合、次のようなものがあるかもしれません

if (! found) { ... }

ループのどこかに。

0
gnasher729

breakを削除することは良いアイデアだと思いますが、コードの行を保存するためではありません。

breakなしでそれを書くことの利点は、ループの事後条件が明白で明白であることです。ループを終了してプログラムの次の行を実行すると、ループ条件i < array.length && !conditionはfalseであったに違いありません。したがって、iが配列の長さ以上であるか、conditionがtrueです。

break付きのバージョンでは、隠れた問題があります。ループ条件says有効な配列インデックスごとに他のコードを実行するとループが終了するが、実際にはbreakステートメントは、その前にループを終了させる可能性があり、ループのコードを注意深く確認しないと表示されません。また、最適化コンパイラーを含む自動化された静的アナライザーも、ループの事後条件が何であるかを推測するのに問題を抱えている可能性があります。

これは、エドガーダイクストラが「有害と見なされる後藤」と書いた元の理由でした。それはスタイルの問題ではありませんでした。ループから抜け出すと、プログラムの状態を正式に推論することが難しくなります。たくさん書きましたbreak;自分の人生の中で、大人になったときに、gotoを作成しました(パフォーマンスが重要な内部ループの複数のレベルから抜け出し、検索ツリーの別のブランチを続行するため)。ただし、私はreturnよりも、ループからの条件付きbreakステートメントを記述する可能性が高くなります(ループ後にコードが実行されることはないため、事後条件を調整できません)。 。

追記

実際、同じ動作をするようにコードをリファクタリングするには、実際にはさらに多くのコード行が必要になる場合があります。あなたのリファクタリングは、特定のいくつかの他のコードに対して有効であるか、またはconditionがfalseで始まることが証明できる場合。しかし、あなたの例は一般的なケースでは以下と同等です:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

他のコードがワンライナーではない場合、自分自身を繰り返さないようにそれを関数に移動し、ループをほぼリファクタリングすることができます末尾再帰関数。

これはあなたの質問の主要なポイントではなかったと私は認識しています。そしてあなたはそれをシンプルに保っていました。

0
Davislor