IFステートメントの反転


39

それで、私は数年前からプログラミングをしていて、最近ReSharperをもっと使い始めました。ReSharperが私に常に示唆していることの1つは、「 'if'ステートメントを逆にして入れ子を減らす」ことです。

このコードがあるとしましょう:

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
    }
}

そして、ReSharperは私がこれを行うことを提案します:

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;
}

ReSharperは、ネストがどれだけ進んでいるかに関係なく、IFを反転させることを常に提案するようです。この問題は、少なくともいくつかの状況でネストするのが好きだということです。私にとっては、特定の場所で何が起こっているのかを読み、理解するのが簡単に思えます。これは常にそうではありませんが、私は時々より快適なネストを感じます。

私の質問は、個人的な好みや読みやすさ以外に、ネストを減らす理由はありますか?パフォーマンスの違いや、私が気付いていない他の何かがありますか?



24
ここで重要なのは「Resharper Suggests」です。提案を見てください。コードが読みやすく、一貫性がある場合は、それを使用してください。for / for各ループで同じことを行います。
ジョンレイナー

私は通常、これらの再削りのヒントを降格します。常に従うわけではないので、それらはもうサイドバーに表示されません。
CodesInChaos

1
ReSharperはネガを削除しましたが、これは通常良いことです。ただし、ブロックが本当にあなたの例と同じくらい単純な場合、ここにうまく適合することができる三項条件は示唆しませんでした。
17

2
ReSharperは、条件をクエリに移動することも提案していませんか?foreach(someObjectList.Where(object => object!= null)のsomeObject){...}
ローマンライナー

回答:


63

場合によります。あなたの例では、非反転ifステートメントがあることは問題ではありません。なぜなら、の本文ifは非常に短いからです。ただし、通常は、ボディが大きくなったときにステートメントを反転させます。

私たちは人間であり、頭の中に一度に複数のものを保持することは困難です。

ステートメントの本文が大きい場合、ステートメントを反転すると、ネストが減少するだけでなく、そのステートメントの上のすべてを忘れて残りのみに集中できることを開発者に伝えます。逆のステートメントを使用して、できるだけ早く間違った値を取り除く可能性が非常に高くなります。これらがゲーム外にあることがわかったら、残っているのは実際に処理できる値だけです。


64
開発者の意見の流れがこのようになるのを見るのが大好きです。コード内に非常に多くの入れ子があります。というのは、単にbreakcontinueと複数returnが構造化されていないという非常に人気のある妄想があるからです。
ジミージェームズ

6
問題はブレークなどです。頭の中に保持しなければならない制御フローはまだあります。「これはxにならないか、先頭でループを終了するでしょう」とにかく例外をスローするnullチェックの場合、これ以上考える必要はないかもしれません。しかし、そのないので、良いときに、より微妙な「木曜日に、この状態の場合のみ実行さ半分、このコード」
ユアン・

2
@Ewan:「これはxにならない、またはループの一番上で終了する」と「これはxにならない、またはこのブロックに入らない」との違いは何ですか?
コリン

重要なxの複雑さと意味は何もない
ユアン

4
@ユアン:私はbreak/ continue/ ブロックreturnよりも本当の利点があると思いますelse。アーリーアウトには、2つのブロックがあります:アウトアウトブロックとステイゼアブロック。とelse3つのブロックがあります:if、else、およびその後に続くもの(ブランチが取ったものは何でも使用されます)。ブロックが少ないほうがいいです。
マチューM.17年

33

ネガを避けないでください。CPUはそれらを愛していても、人間はそれらを解析することを嫌います。

ただし、イディオムを尊重します。私たち人間は習慣の生き物ですので、雑草に満ちた道を進むよりも、すり切れた道を好むのです。

foo != null悲しいことに、イディオムになりました。もう別々の部分にほとんど気づかない。私はそれを見て、「ああ、foo今から脱落しても安全だ」と思うだけです。

あなたがそれを覆したいなら(ああ、いつかお願いします)、あなたは本当に強力なケースが必要です。あなたは私の目を楽に滑らせて、すぐにそれを理解できるものが必要です。

しかし、あなたが私たちに与えたのはこれでした:

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;
}

構造的にも同じではありません!

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;

    if(someGlobalObject == null) continue;
    someSideEffectObject = someGlobalObject.SomeProperty;
}

それは私の怠brainな脳が期待していたことをしていません。独立したコードを追加し続けることができると思ったが、できない。これにより、今は考えたくないことについて考えるようになります。あなたは私のイディオムを破ったが、私にもっと良いものを与えなかった。なぜなら、あなたは私を魂に焼き付けたネガティブなものから私を守りたかったからです。

申し訳ありませんが、ReSharperはこの90%の時間を提案するのが正しいかもしれませんが、nullチェックでは、無視するのが最適なコーナーケースを見つけたと思います。ツールは、読み取り可能なコードが何であるかを教えるのが苦手です。人間に聞いてください。査読を行います。しかし、盲目的にツールを追跡しないでください。


1
ループ内にさらにコードがある場合、それがどのように機能するかは、すべてがどのように組み合わされるかに依存します。独立したコードではありません。質問のリファクタリングされたコードはこの例では正しいものでしたが、孤立していない場合は正しくない可能性があります-それが目的だったのですか?(ただし、否定を避けないための+1)
Baldrickk

@Baldrickkをif (foo != null){ foo.something() }使用すると、foonullの場合、気にせずにコードを追加し続けることができます。これはしません。今でもそれをする必要がないとしても、「必要ならリファクタリングするだけでいい」と言って未来を台無しにしようという動機は感じません。うん。ソフトウェアは、変更が簡単な場合にのみソフトです。
candied_orange

4
コードは関連しなければならないのいずれかを「思いやりのあるなしにコードを追加し続ける」(そうでない場合、それはおそらく別々でなければなりません)、または追加として、あなたが変更しているブロック/関数の機能を理解するために取らケアがなければならない任意のコードが変化挙動への可能性を秘めています意図したものを超えています。このような単純なケースでは、どちらの方法でも重要だとは思いません。より複雑なケースでは、コードの理解が優先されることを期待するため、最も明確だと思う方を選択します。どちらのスタイルでもかまいません。
Baldrickk

関連するものと絡み合うものは同じものではありません。
candied_orange

1
否定を避けないでください:D
VitalyB

20

それは、ブレークがネストよりも悪いかどうかについての古い議論です。

人々はパラメータチェックの早期返還を受け入れているように見えますが、メソッドの大部分で嫌われています。

個人的には、それがより多くのネストを意味する場合でも、続行、中断、後藤などを避けます。ただし、ほとんどの場合、両方を回避できます。

foreach (someObject in someObjectList.where(i=>i != null))
{
    someOtherObject = someObject.SomeProperty;
}

明らかに、多くのレイヤーがある場合、入れ子にすると追跡が難しくなります

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
        if(someObject.x > 5) 
        {
            x ++;
            if(someObject.y > 5) 
            {
                x ++;
            }
        }
    }
}

最悪なのは両方を持っているときです

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
        if(someObject.x > 5) 
        {
            x ++;
            if(someObject.y > 5) 
            {
                x ++;
                return;
            }
            continue;
        }
        someObject.z --;
        if(myFunctionWithSideEffects(someObject) > 6) break;
    }
}

11
この行が大好きです:foreach (subObject in someObjectList.where(i=>i != null))なぜそんなことを考えたことがないのかわかりません。
バリーフランクリン

@BarryFranklin ReSharperは、foreachに緑色の点線の下線を付けて、「LINQ式に変換」を提案します。操作に応じて、SumやMaxなどの他のlinqメソッドも実行します。
ケビン料金

@BarryFranklinこれはおそらく、低レベルに設定し、自由裁量でのみ使用することをお勧めします。この例のような些細なケースではうまくいきますが、より複雑なコードでは、より複雑な条件部分をLinqifiableなビットに選択し、残りのボディはオリジナルよりもはるかに理解しにくいものを作成する傾向があります。少なくともループ全体をLinqに変換できる場合、結果は合理的であることが多いため、少なくとも提案を試してみます。しかし、結果の半分と半分はIい高速IMOを取得する傾向があります。
ダン・ニーリー

皮肉なことに、resharperによる編集は、ifの後にワンライナーが続くため、まったく同じレベルのネストを持っています。
ピーター

へえ、しかし中括弧なし!これは明らかに許可されています:/
Ewan

6

問題continueは、コードにさらに行を追加すると、ロジックが複雑になり、バグが含まれる可能性があることです。例えば

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;

    ...  imagine that there is some code here ...

    // added later by a Junior Developer
    doSomeOtherFunction(someObject);
}

あなたが電話をかけたいですかdoSomeOtherFunction()ために、すべての SomeObjectの、または唯一の非nullの場合は?元のコードにはオプションがあり、より明確です。continue一つの「ああ、うん、そこに戻って私たちはnullをスキップ」忘れるかもしれない、あなたは可能か正しくない可能性が方法の開始時にその呼び出しを入れない限り、あなたも、すべてのsomeObjectsのためにそれを呼び出すためのオプションを持っていません他の理由で。

はい、多くのネストはくて混乱を招く可能性がありますが、この場合は従来のスタイルを使用します。

おまけの質問: そもそもそのリストにヌルがあるのはなぜですか?そもそもnullを追加しない方が良いかもしれません。その場合、処理ロジックははるかに単純です。


12
ループ内には、スクロールせずにループの上部にnullチェックが表示されないほどのコードはないはずです。
ブライアンベッチャー

@Bryan Boettcherは同意しましたが、すべてのコードがそれほどうまく書かれているわけではありません。そして、数行の複雑なコードでさえ、継続を忘れる(または誤解する)可能性があります。実際に私はOKだreturn彼らは「クリーンブレーク」を形成するが、IMO原因S breakcontinue混乱を招くと、ほとんどの場合には、最高のを回避しています。
user949300

4
@ user949300 10年にわたる開発の後、私は壊れたり混乱を引き起こしたりすることは一度もありませんでした。私は彼らを混乱に巻き込んだことがありますが、彼らは決して原因ではありませんでした。通常、開発者に対する不適切な決定が原因であり、使用する武器に関係なく混乱を引き起こしていました。
corsiKa

1
@corsiKa Apple SSLのバグは実際にはgotoのバグですが、簡単に中断または継続バグになる可能性があります。しかし、コードは恐ろしいです...
user949300

3
@BryanBoettcher問題は、継続や複数のリターンを考える同じ開発者も大丈夫だと思っているようです。そのため、継続/複数のリターンに関するすべての経験は、膨大な量のコードに含まれています。
アンディ

3

アイデアは早期復帰です。初期のreturnループでは早くなりcontinue

この質問に対する多くの良い答え:関数から早く戻るか、ifステートメントを使用する必要がありますか?

質問は意見に基づいて閉じられますが、430以上の票が早期返還に有利であり、50票が「それは依存」であり、8は早期返還に反対していることに注意してください。そのため、非常に強力なコンセンサスがあります(それか、私はカウントが苦手です。もっともらしい)。

個人的には、ループ本体がアーリーコンティニューの対象であり、それが問題になるほど長い場合(3-4行)、アーリーコンティニューではなくアーリーリターンを使用する関数にループボディーを抽出する傾向があります。


2

この手法は、メソッドの大部分が条件が満たされたときに発生する場合に、前提条件を認証するのに役立ちます。

私たちは頻繁ifsに自分の仕事をひっくり返し、他のファントムを採用しています。PRをレビューしているときに、同僚が3つのレベルのネストを削除する方法を指摘できることがよくありました。ifsを展開しているため、発生頻度は低くなります。

これはロールアウトできるもののおもちゃの例です

function foobar(x, y, z) {
    if (x > 0) {
        if (z != null && isGamma(y)) {
            // ~10 lines of code
            // return something
        }
        else {
           return y
        }
    }
    else {
      return y + z
    }
}

このようなコードは、興味深いケースが1つ(残りは単に戻り値または小さなステートメントブロックである)一般的です。上記を次のように書き換えることは簡単です

function foobar(x, y, z) {
    if (x <=0) {
        return y + z
    }
    if (z == null || !isGamma(y) {
       return y
    }
    // ~ 10 lines of code
    // return something
}

ここでは、重要なコードであるインクルードされていない10行は、関数内でトリプルインデントされていません。最初のスタイルがある場合、長いブロックをメソッドにリファクタリングするか、インデントを許容するように誘惑されます。これは節約が始まる場所です。ある場所では、これは些細な節約ですが、多くのクラスの多くのメソッドで、コードをきれいにするために余分な関数を生成する必要がなく、それほど恐ろしいマルチレベルはありませんインデント


OPのコードサンプルに対応して、私はそれが過度に熱心な崩壊であることに同意します。特に、私が使用しているスタイルである1TBでは、反転によりコードのサイズが大きくなります。


0

研ぎ直しの提案はしばしば有用ですが、常に批判的に評価されるべきです。事前定義されたコードパターンを探して変換を提案しますが、人間がコードを多少読みやすくする要因をすべて考慮することはできません。

他のすべての条件が同じであれば、ネスティングは少ない方が望ましいため、ReSharperはネスティングを削減する変換を提案します。しかし、他のすべてのものは等しくありません。この場合、変換にはaの導入が必要continueです。つまり、結果のコードを実際に追跡するのが難しくなります。

では、いくつかの例、のネストのレベルを交換することcontinue ができ、実際に改善することが、これはReSharpers機械的ルールの理解を超えている問題のコードのセマンティクスに依存します。

あなたの場合、ReSharperの提案はコードを悪化させるでしょう。無視してください。またはそれ以上:提案された変換を使用しないでください。ただし、コードをどのように改善できるかについて少し考えてください。同じ変数を複数回割り当てている可能性がありますが、前の割り当てが上書きされるため、最後の割り当てのみが有効であることに注意してください。これバグのように見えます。ReSharpersの提案はバグを修正しませんが、疑わしいロジックが進行していることをもう少し明らかにします。


0

ここでのより大きなポイントは、ループの目的がループ内のフローを整理するための最良の方法を決定するということだと思います。残りの要素をループする前にいくつかの要素をcontinue除外する場合は、早期に削除するのが理にかなっています。ただし、ループ内でさまざまな種類の処理を実行している場合は、次のように、それをif実際に明らかにする方が理にかなっている場合があります。

for(obj in objectList) {
    if(obj == null) continue;
    if(obj.getColour().equals(Color.BLUE)) {
        // Handle blue objects
    } else {
        // Handle non-blue objects
    }
}

Java Streamsまたは他の機能的なイディオムでは、以下を使用して、フィルタリングをループから分離できることに注意してください。

items.stream()
    .filter(i -> (i != null))
    .filter(i -> i.getColour().equals(Color.BLUE))
    .forEach(i -> {
        // Process blue objects
    });

ちなみに、これは、Perlのインバートされたif(unless)が単一のステートメントに適用されることで気に入っているものの1つです。

foreach my $item (@items) {
    next unless defined $item;
    carp "Only blue items are supported! Failed on item $item" 
       unless ($item->get_colour() eq 'blue');
    ...
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.