他のブロックはコードの複雑さを増しますか?[閉まっている]


18

これは非常に単純化された例です。これは必ずしも言語固有の質問ではありません。関数を作成できる他の多くの方法、およびそれに加えられる変更を無視してください。。色はユニークなタイプです

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

私が出会った多くの人々、ReSharper、およびこの男(コメントから、しばらくの間これを尋ねようとしていることを思い出した)は、コードをリファクタリングして、elseこれを残すブロックを削除することをお勧めします。

(私は大多数が言ったことを思い出せない、そうでなければ尋ねなかったかもしれない)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

質問:elseブロックを含めないことで複雑さが増しますか?

私はelse、両方のブロックのコードが直接関係しているという事実を述べることにより、意図がより直接的に述べられているという印象を受けています。

さらに、特に後日コードを変更した後は、ロジックの微妙な間違いを防ぐことができます。

私の単純化された例のこのバリエーションを取り上げます(orこれは意図的に単純化された例であるため、演算子を無視します)。

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

誰かがif、最初の条件が自分の条件に制約を設定していることをすぐに正しく認識せずに、最初の例の後に条件に基づいて新しいブロックを追加できるようになりました。

elseブロックが存在する場合、新しい条件を追加した人はelseブロックのコンテンツを移動するように強制されます(そして、何らかの理由でそれらをブロックすると、ヒューリスティックによりコードが到達不能であることが示されifます。 。

もちろん、特定の例がとにかく定義されるべき他の方法があり、そのすべてがその状況を防ぎますが、それは単なる例です。

私が与えた例の長さは、これの視覚的側面を歪めるかもしれないので、括弧までのスペースはメソッドの残りの部分にとって比較的重要でないと仮定します。

elseブロックの省略に同意し、ifブロックを使用して、ヌルチェック(またはその他のガード)などの後続のすべてのコードに対して論理的に満たす必要がある制約を適用する場合について言及するのを忘れました。


実際には、「戻り」を伴う「if」ステートメントがある場合、すべてのケースの50%を超える「ガードブロック」と見なすことができます。だから私はあなたの誤解は「ガードブロック」は例外的なケースであり、あなたの例は通常のケースだと思います。
Doc Brown 14

2
@AssortedTrailmix:上記のようなケースは、else句を書くときに読みやすいかもしれないことに同意します(私の好みでは、不必要な括弧を省くとさらに読みやすくなります。しかし、上記の場合return sky.Color == Color.Blue(if / elseなし)
Doc Brown

1
上記のコメントで指摘したように、推奨される例は単純すぎて、投機的な答えを招いています。「誰かが新しいifブロックを追加できるようになりました...」で始まる質問の部分については、以前に何度も質問と回答がありました。たとえば、複数の条件をチェックする方法を参照してください複数の質問がリンクされています。
ブヨ

1
他のブロックがDRY原則に関係していることを確認できません。DRYは、抽象化と、関数、メソッド、およびオブジェクトの形式で一般的に使用されるコードへの参照に関係しています。あなたの質問はコードの読みやすさと、おそらく慣習やイディオムに関連しています。
シャシャンクグプタ14

2
再開の投票。この質問に対する適切な回答は、特に意見に基づくものではありません。質問の与えられた例が不十分な場合、編集することでそれを改善するのは合理的なことであり、実際には真実ではない理由で閉じることを投票することではありません。
マイケルショー14

回答:


27

私の見解では、明示的なelseブロックが望ましいです。これを見たら:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

私は相互に排他的なオプションを扱っていることを知っています。ifブロック内の内容を読む必要はありません。

これを見たら:

if (sky.Color != Blue) {
   ...
} 
return false;

一見すると、それは関係なくfalseを返し、空が青くないオプションの副作用があるように見えます。

ご覧のとおり、2番目のコードの構造は、コードが実際に行うことを反映していません。それは良くないね。代わりに、論理構造を反映する最初のオプションを選択します。論理フローを知るために、各ブロックのリターン/ブレーク/継続ロジックをチェックする必要はありません。

なぜ他の人が他の人を好むのかは私には謎です。うまくいけば、誰かが反対の立場を取ることで答えが得られることを願っています。

私はelseブロックの省略に同意し、ifブロックを使用して、nullチェック(またはその他のガード)などのすべての後続コードに対して論理的に満たす必要がある制約を適用する場合について言及するのを忘れました)。

あなたが幸福な道を去った場合、警備条件は大丈夫だと思います。エラーまたは障害が発生したため、通常の実行を続行できません。これが発生した場合、例外をスローするか、エラーコードを返すか、言語に適したものを返してください。

この回答の元のバージョンの直後に、私のプロジェクトで次のようなコードが発見されました。

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

returnをelseに入れないことにより、returnステートメントが欠落しているという事実が見落とされていました。他に採用されていた場合、コードはコンパイルされませんでした。(もちろん、私が持っているはるかに大きな懸念は、このコードで書かれたテストがこの問題を検出しないためにかなり悪かったということです。)


それについての私の考えをまとめると、このように考えるのは私だけではないことがうれしいです。私は、elseブロックの削除を好むプログラマー、ツール、およびIDEで作業します(コードベースの規則に沿ってそれを行うことを強いられると面倒になる場合があります)。私もここで反論点を見ることに興味があります。
セラリアドボール14

1
私はこれを変えます。OPが言及する微妙な論理エラーを引き起こす可能性は低いため、明示的なelseがあまり役に立たない場合があります-それは単なるノイズです。しかし、私はほとんど同意します、そして、時々、私は} // else他のものが通常2つの間の妥協として行くところのような何かをします。
テラスティン14

3
@Telastyn、しかし、あなたは他をスキップすることから何を得ますか?私は、多くの状況で利益が非常にわずかであることに同意しますが、わずかな利益であっても、それを行う価値があると思います。確かに、それがコードである可能性があるときにコメントに何かを入れるのは私にとって奇妙なようです。
ウィンストンイーバート14

2
私はあなたがOPと同じ誤解を持っていると思う-「if」と「return」はほとんどガードブロックとして見ることができるので、ここでは例外的なケースについて議論していますが、ガードブロックのより頻繁なケースは"そうしないと"。
Doc Brown 14

...あなたが書いたものは間違っていませんが、私見はあなたが得た多くの賛成に値しません。
ドックブラウン14

23

else私が見つけたブロックを削除する主な理由は、過剰なインデントです。elseブロックの短絡により、よりフラットなコード構造が可能になります。

多くのifステートメントは本質的にガードです。これらは、nullポインターおよび他の「これをしないでください!」の逆参照を防止しています。エラー。そして、それらは現在の機能の迅速な終了につながります。例えば:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

ここで、else句は非常に合理的であるように見えるかもしれません:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

確かに、それがあなたがしているすべてである場合、それは上記の例よりもはるかにインデントされていません。しかし、これが実際のマルチレベルデータ構造(JSON、HTML、XMLなどの一般的な形式を処理するときに常に発生する状態)で行うことはほとんどありません。したがって、特定のHTMLノードのすべての子のテキストを変更する場合は、次のように言います。

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

ガードは、コードのインデントを増やしません。else句、実際の作業のすべてが右にオーバー移動を開始します:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

これは重要な右方向への動きを始めており、これは非常に簡単な例であり、ガード条件は2つだけです。ガードが追加されるたびに、別のインデントレベルが追加されます。XMLを処理したり、詳細なJSONフィードを使用したりした実際のコードは、3、4、またはそれ以上のガード条件を簡単に積み重ねることができます。常にを使用するとelse、最終的にインデントされた4、5、または6レベルになります。そして、それは間違いなくコードの複雑さの感覚に寄与し、何が何に並ぶかを理解するのにより多くの時間を費やしました。迅速でフラットな早期終了スタイルは、その「余分な構造」の一部を排除し、よりシンプルに見えます。

この回答に関する補遺のコメントは、ガード条件の唯一の理由がNULL /空の処理ではないことは明らかではないかもしれないことを実感しました。ほとんどない!この単純な例はNULL /空の処理に焦点を当てており、他の理由は含まれていませんが、たとえば、XMLやASTなどの「関連する」コンテンツの深い構造を検索すると、多くの場合、無関係なノードや興味のないノードを取り除くための一連の特定のテストが行​​われますケース。一連の「このノードが関係ない場合、リターン」テストは、NULL /空のガードと同じ種類の右方向のドリフトを引き起こします。実際には、後続の関連性テストは、検索された対応するより深いデータ構造のNULL /空のガードと頻繁に結合されます。


2
これは詳細かつ有効な答えですが、これを質問に追加します(最初は頭を悩ませました)。ブロックをelse使用して、ヌルチェック(またはその他のガード)など、後続のすべてのコードで論理的に満たす必要のifある制約を適用するときに、常に余分なブロックを見つける「例外」があります。
セラリアドボール14

@AssortedTrailmix定期的にthen節内で早期終了を行うことができる場合(連続するガードステートメントなど)を除外する場合、特定のelse節を回避するための特定の値はないと思います。確かに、それは明快さを低下させ、潜在的に危険です(単に、そこにある/あるべき代替構造を述べないことによって)。
ジョナサンユニス14

幸福な道を離れたら、ガード条件を使用して排出するのが妥当だと思います。ただし、XML / JSONを処理する場合、最初にスキーマに対して入力を検証すると、より良いエラーメッセージとよりクリーンなコードが得られることもわかりました。
ウィンストンユワート14

これは、他の状況が回避される場合の完全な説明です。
クリスシッフハウアー14

2
代わりに、愚かなNULLを生成しないようにAPIをラップしていました。
ウィンストンイーバート14

4

これを見たら:

if(something){
    return lamp;
}
return table;

よくあるイディオムがあります。私の頭の中では次のように変換される一般的なパターン

if(something){
    return lamp;
}
else return table;

これはプログラミングの非常に一般的なイディオムであるため、この種のコードを見ることに慣れているプログラマーは、それを見るたびに頭の中に暗黙の「翻訳」を持ち、適切な意味に「変換」します。

明示的なelseパターンは必要ありません。頭全体がパターン認識しているので、頭が「そこに置いてあります」。共通パターン全体の意味を理解しているので、明確にするために細かい詳細は必要ありません。

たとえば、次のテキストを読んでください。

ここに画像の説明を入力してください

これを読みましたか?

春のパリが大好き

もしそうなら、あなたは間違っています。文章をもう一度読んでください。実際に書かれているのは

私はパリを愛し春

テキストには2つの「the」単語があります。では、なぜ2つのうちの1つを見逃したのですか?

それはあなたの脳が共通のパターンを見て、すぐにそれを既知の意味に翻訳したからです。パターンを見るとすでにパターンを認識しているため、意味を理解するためにすべてを詳細に読む必要はありませんでした。これが、余分な「the」を無視した理由です。

上記のイディオムと同じこと。多くのコードを読んだプログラマーは、このパターンを見るのに慣れていif(something) {return x;} return y;ます。彼らのelse脳は「彼らのためにそこにそれを置く」ので、彼らはその意味を理解するためにexplciit を必要としません。彼らは、それを既知の意味を持つパターンとして認識します:「閉じ括弧の後elseは、前のものの暗黙的なものとしてのみ実行されifます」。

私の意見では、これelseは不要です。しかし、より読みやすいと思われるものを使用してください。


2

コードに視覚的な混乱を追加するため、はい、複雑さを追加する可能性があります。

ただし、逆も当てはまります。コード長を減らすための過剰なリファクタリングも複雑さを増す可能性があります(もちろんこの単純なケースではありません)。

elseブロックが意図を述べているという声明に同意します。しかし、これを達成するためにコードに複雑さを追加しているため、私の結論は異なります。

論理の微妙な間違いを防ぐことができるというあなたの声明には同意しません。

例を見てみましょう。空が青で湿度が高い場合、湿度が高くない場合、または空が赤の場合にのみtrueを返すようになりました。しかし、その後、1つのブレースを間違えて間違った場所に配置しますelse

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

実稼働コードでこの種のばかげたミスをすべて確認しましたが、検出は非常に困難です。

次のことをお勧めします。

デフォルトがであることが一目でわかるので、これは読みやすいと思いますfalse

また、ブロックの内容を強制的に移動して新しい句を挿入するという考え方は、エラーを起こしやすいです。これはどういうわけか、オープン/クローズの原則に関連しています(コードは拡張のために開かれ、修正のために閉じられるべきです)。既存のコードの変更を強制しています(たとえば、コーダーが中括弧のバランスにエラーを導入する可能性があります)。

最後に、あなたは、あなたが正しいと思う事前に決められた方法で答えるように人々を導いています。インスタンスでは、非常に単純な例を提示しますが、その後、人々はそれを考慮に入れるべきではないと述べます。ただし、例の単純さは質問全体に影響します。

  • ケースが提示されたものと同じくらい単純な場合、私の答えはすべてのif else節を省略することです。

    return(sky.Color == Color.Blue);

  • ケースが少し複雑で、さまざまな条項がある場合、私は答えます:

    bool CanLeaveWithoutUmbrella(){

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

  • ケースが複雑で、すべての句がtrueを返すわけではなく(おそらくdoubleを返す)、ブレークポイントまたはlogginコマンドを導入したい場合は、次のようなものを検討します。

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • 何かを返すメソッドではなく、変数を設定したり、メソッドを呼び出すif-elseブロックについて話している場合は、yes、final else句を含めます。

したがって、例の単純さも考慮すべき要素です。


あなたは他の人が犯したのと同じ間違いを犯し、サンプルを少し変形しすぎて新しい問題を調べているように感じますが、少し時間を取ってあなたのサンプルに到達するプロセスを調べる必要があるので、今のところは私に有効。補足説明として、これはオープン/クローズの原則とは関係ありません。このイディオムを適用するには範囲が狭すぎます。しかし、より高いレベルでの応用が(の必要性除去することにより、この問題の全体の様相を取り除くことになる注意することは興味深いことである任意の機能の変更を)。
セラリアドボール14

1
ただし、この過度に簡略化された例の追加句を変更したり、記述方法を変更したり、変更を加えたりすることができない場合は、この質問はコードの正確な行のみに関連し、もはや一般的な質問ではないことを考慮する必要があります。その場合、あなたは完全に正しいです。そもそも複雑さはなかったので、else節に複雑さを追加することも削除することもありません。ただし、新しい節が追加される可能性があるという考えを提案しているので、あなたは公平ではありません。
フランモビンケル14

そして、私が脇に注意するように、私はこれがよく知られているオープン/クローズドの原則に関連しているとは言いません。あなたが提案するようにコードを修正するように人々を導くという考えは、エラーを起こしやすいと思います。私はその原理からこの考えを取り入れています。
フランモビンケル14

編集上のホールド、あなたが何か良いのにしているが、私は今、コメントを入力してよ
Selali Adobor

1

説明されているif-elseのケースはより複雑ですが、ほとんどの(すべてではありませんが)実際的な状況ではそれほど重要ではありません。

数年前、航空業界で使用されていたソフトウェアに取り組んでいたとき(認証プロセスを経る必要がありました)、あなたの質問が浮かびました。コードの複雑さを増すと、「その他」のステートメントに金銭的コストがかかることが判明しました。

その理由は次のとおりです。

「else」の存在は、評価する必要のある暗黙の3番目のケースを作成しました。「if」も「else」も取られていません。あなたは(私が当時のように)WTFを考えているのでしょうか?

説明はこんな感じで......

論理的な観点からは、「if」と「else」の2つのオプションしかありません。しかし、私たちが証明していたのは、コンパイラが生成していたオブジェクトファイルです。したがって、「その他」がある場合、生成された分岐コードが正しいこと、および不可解な3番目のパスが表示されないことを確認するために、分析を行う必要がありました。

これを「if」のみで「else」がない場合と比較してください。その場合、「if」パスと「non-if」パスの2つのオプションしかありません。評価する2つのケースは3つよりも複雑ではありません。

お役に立てれば。


オブジェクトファイルでは、どちらの場合も同じjmpとしてレンダリングされませんか?
ウィンストンイーバート14

@WinstonEwert-それらが同じであることが期待されます。ただし、レンダリングされるものとレンダリングされるものは、それらがどの程度重複しているかに関係なく、2つの異なるものです。
スパーキー14

(SEが私のコメントを食べたと思います...)これは非常に興味深い答えです。私はそれが公開されるケースがややニッチであることを言うだろうが、それはいくつかのツールは、私が見流れ、循環的複雑度に基づいて2つ(も、最も一般的なコードのメトリックの違いを見つけるだろうことを示し、有意差を測定しないでしょうん。
Selaliアドボール14

1

コードの最も重要な目標は、コミット済みとして理解できるようにすることです(簡単にリファクタリングするのではなく、有用ですが重要度は低くなります)。これを説明するために、もう少し複雑なPythonの例を使用できます。

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

これは非常に明確です-これは、caseステートメントまたはディクショナリルックアップに相当しますreturn foo == bar

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

トークンの数が多くても(キーと値のペアが2つある元のコードでは32対24)、関数のセマンティクスは明確になりました。これは単なるルックアップであるためです。

(これはリファクタリングに結果をもたらします-もし、もしあなたが副作用を持ちたいならkey == NIK、3つの選択肢があります:

  1. if/ elif/ elseスタイルに戻し、key == NIKケースに1行挿入します。
  2. ルックアップ結果を値に保存し、戻る前に特別な場合のifステートメントを追加しvalueます。
  3. 呼び出し元にifステートメントを入れます。

検索機能は、強力な単純化である、なぜ今、私たちは以下を参照してください。これは、最初のオプションよりも小さな差が生じるに加えているので、明らかに第三の選択肢最も簡単な解決策を作ることがことを確認するだけだvalue唯一のことを行います。

OPのコードに戻ると、これはelseステートメントの複雑さのガイドとして役立つと思います。明示的なelseステートメントを含むコードは客観的にはより複雑ですが、より重要なのは、コードのセマンティクスを明確かつ単純にするかどうかです。また、コードの各部分には異なる意味があるため、ケースごとに回答する必要があります。


単純なスイッチケースのルックアップテーブルの書き換えが気に入っています。そして、私はそれが好まれるPythonのイディオムであることを知っています。ただし、実際には、多方向の条件(別名caseまたはswitchステートメント)の均質性が低いことがよくあります。いくつかのオプションは単なる値を返すだけですが、1つまたは2つのケースでは追加の処理が必要です。そして、ルックアップ戦略が適していないことを発見したら、まったく異なるif elif... else構文で書き直す必要があります。
ジョナサンユニス14

そのため、ルックアップは通常、1行または関数である必要があります。そのため、ルックアップ結果のロジックは、最初にルックアップするロジックから分離されます。
l0b0 14

この質問は、必ず言及して留意すべき質問に高レベルの見解をもたらしますが、自分のスタンスを自由に拡大できるように、十分な制約がケースに置かれていると感じています
Selali Adob​​or

1

どのようなifステートメントを使用しているかに依存すると思います。一部のifステートメントは式として見ることができ、他のステートメントは制御フローステートメントとしてのみ見ることができます。

あなたの例は私には表現のように見えます。Cのような言語では、三項演算子?:if文に非常に似ていますが、式であり、elseの部分は省略できません。式には常に値が必要であるため、elseブランチを式で省略することはできません。

三項演算子を使用すると、例は次のようになります。

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

ただし、ブール式であるため、次のように単純化する必要があります。

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

明示的なelse句を含めると、意図が明確になります。ガードは状況によっては意味をなす場合がありますが、例外でも異常でもない2つの可能な値から選択する場合は役に立ちません。


+ 1、OPは、上記の方法で記述した方がよい病理学的なケースについて議論しているIMHOです。「戻る」と「IF」のためにはるかに頻繁にケースがある私の経験に「ガード」の場合。
Doc Brown 14

なぜこれが起こり続けるのかわかりませんが、人々は質問の最初の段落を無視し続けます。実際、あなたはすべて、私が明示的に使用しないと言っているコード行を使用しています。I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
セラリアドボール14

最後の質問を読み直すと、質問の意図を誤解しているように見えます。
セラリアドボール14

単純化するために実際に物beいをしている例を使用しました。私はあなたの最初の段落を読んで注意を払いました。そのため、その特定の例を実行するための最良の方法に直接スキップするのではなく、最初のコードブロックが存在するのです。私(または他の誰か)があなたの質問の意図を誤解していると思われる場合は、おそらくあなたの意図を明確にするためにそれを編集する必要があります。
マイケルショー14

私はそれを編集しますが、あなたがするコードの正確な行を使用して「これをしないでください」と言うので、それをそれ以上明確にする方法を知りません。あなたがコードを書き換えると、質問を削除することができ、無限の方法は...私はまだ非常に多くの人が私の文を理解し、それを尊重している、それらをすべてカバーすることはできません、あります
Selali Adobor

1

この議論で考えるべきもう1つのことは、各アプローチが促進する習慣です。

  • if / elseは、ブランチに関してコーディングサンプルを再フレーム化します。あなたが言うように、時々この明示性は良いです。実行パスには2つの可能性があり、それを強調したいと思います。

  • 他の場合では、実行パスが1つだけで、プログラマが心配しなければならない例外的なものがすべてあります。あなたが述べたように、ガード句はこれに最適です。

  • もちろん、3つ以上のケース(n)があります。通常、if / elseチェーンを放棄し、case / switchステートメントまたはポリモーフィズムを使用することをお勧めします。

ここで私が心に留めておく指導原則は、メソッドまたは関数は1つの目的のみを持つべきであるということです。if / elseまたはswitchステートメントを含むコードは、分岐専用です。したがって、それはばかげて短く(4行)、正しいメソッドに委任するか、明らかな結果(例のように)を生成するだけです。あなたのコードがそんなに短いとき、それらの複数のリターンを見逃すのは難しいです:)「gotoは有害だ」と同様に、分岐文は悪用される可能性があります。既に述べたように、elseブロックを用意するだけで、コードをまとめる場所が提供されます。あなたとあなたのチームは、あなたがそれについてどう感じるかを決める必要があります。

ツールが本当に不満を持っているのは、ifブロック内にreturn / break / throwがあるという事実です。ですから、それに関する限り、あなたはガード条項を書きましたが、それを台無しにしました。ツールを幸せにすることも、受け入れずに提案を「楽しませる」こともできます。


私はすぐにそれはおそらくケースですいずれかのパターン、に収まるようようにガード条項をする場合、ツールが考慮されるかもしれないという事実について考えたことはありません、それはそれの欠如のために一つの「支持者のグループ」のための推論を説明します
セラリアドボール14

@AssortedTrailmixそうですね、elseセマンティックについて考えると、コード分析ツールは通常、制御フローの誤解を強調するため、無関係な命令を探します。else後にifその戻りビットを浪費されます。 if(1 == 1)多くの場合、同じ種類の警告がトリガーされます。
スティーブジャクソン14

1

私は答えはそれが依存することだと思います。コードサンプル(間接的に指摘するとおり)は、条件の評価を返すだけであり、ご存知のように単一の式として表現するのが最適です。

else条件を追加することでコードが明確になるかわかりにくくなるかを判断するには、IF / ELSEが何を表すかを判断する必要があります。それは(あなたの例のように)式ですか?その場合、その式を抽出して使用する必要があります。ガード状態ですか?その場合、2つのブランチが同等に見えるため、ELSEは不要で誤解を招く可能性があります。それは手続き型多型の例ですか?それを抽出します。前提条件を設定していますか?それからそれは不可欠です。

ELSEを含めるか制限するかを決定する前に、ELSEが何を表すかを決定します。これにより、ELSEが存在するかどうかがわかります。


0

答えは少し主観的です。elseブロックは、1つのコードブロックが両方のブロックを読んでする必要がなく、コードの他のブロックに排他的に実行されることを確立することにより、可読性を支援することができます。これは、たとえば、両方のブロックが比較的長い場合に役立ちます。場合によっては、ifsのないelsesを使用すると読みやすくなる場合があります。次のコードをいくつかのif/elseブロックと比較します。

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

で:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

コードの2番目のブロックは、ネストされたifステートメントをガード句に変更します。これにより、インデントが減少し、戻り条件の一部が明確になります(「if a != b、then return 0!」)


私の例にはいくつかの穴があるかもしれないとコメントで指摘されているので、もう少し肉付けします。

コードを読みやすくするために、最初のコードブロックをここに書き出すこともできます。

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

このコードは上記の両方のブロックと同等ですが、いくつかの課題があります。elseここの節は、これらのifステートメントを結び付けます。上記のguard節のバージョンでは、guard節は互いに独立しています。新しいものを追加するということは、ロジックのif最後ifと残りの間に新しいものを書くだけです。ここでも、認知負荷を少しだけ増やして、それを行うことができます。ただし、その違いは、新しいガード句の前にいくつかの追加ロジックを発生させる必要がある場合です。ステートメントが独立している場合、次のことができます。

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

接続されたif/elseチェーンでは、これは簡単ではありません。実際のところ、最も簡単な方法は、チェーンを分割してガード句バージョンのように見せることです。

しかし、本当に、これは理にかなっています。if/else弱く使用すると、部品間の関係が暗示されます。連鎖バージョンでa != bは何らかの形で関連していると推測できます。ガード条項のバージョンから、それらが実際には関連していないことがわかるように、その仮定は誤解を招くでしょう。つまり、独立した句を再配置するなど、独立した句でより多くのことができることを意味します(クエリのパフォーマンスを最適化する、または論理フローを改善するためなど)。また、それらを通過した後、認知的に無視することもできます。ではチェーン、私は間の等価関係することをあまり確信しているとは、後で戻ってポップアップしません。c > dif/elseif/elseab

暗示される関係elseは、深くネストされたサンプルコードでも明らかになりますが、方法は異なります。elseステートメントは、それらが結合している条件から分離され、そしてそれらがに関連している逆の条件文の順序(最初else、最後に取り付けられif、最後にはelse最初に取り付けられていますif)。これは認知的不協和音を生み出し、そこではコードをさかのぼって、脳のくぼみを整列させなければなりません。それは括弧の束を一致させようとすることに少し似ています。インデントが間違っているとさらに悪化します。オプションの波括弧も役に立たない。

私はelseブロックの省略に同意し、ifブロックを使用して、nullチェック(またはその他のガード)などのすべての後続コードに対して論理的に満たす必要がある制約を適用する場合について言及するのを忘れました)。

これは良い譲歩ですが、実際には回答を無効にしません。あなたのコード例でそれを言うことができます:

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

このようなコードを記述するための有効な解釈は、「空が青くない場合にのみ傘を持って立ち去らなければならない」ということです。次のコードを実行するには、空が青でなければならないという制約があります。私はあなたの譲歩の定義を満たしました。

空の色が黒なら晴れた夜だから、傘は要らないと言ったらどうでしょう。(これを書く簡単な方法がありますが、全体の例を書く簡単な方法があります。)

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

上記の大きな例のように、必要なことをあまり考えずに新しい句を追加するだけです。でif/else、特にロジックがより複雑な場合、私は何かを台無しにしないことを確信できません。

また、単純な例もそれほど単純ではないことを指摘します。非バイナリ値のテストは、結果が反転したテストの逆と同じではありません。


1
質問の私の編集はこのケースに対処します。ヌルチェック(またはその他のガード)など、後続のすべてのコードで論理的に満たす必要がある制約がelse、読みやすさのためにブロックの省略に不可欠であることに同意します。
セラリアドボール14

1
最初のバージョンを理解するのは難しいですが、if / elseを使用することでそれが必要になるとは思いません。:私はそれを書くと思いますどのように参照してくださいgist.github.com/winstonewert/c9e0955bc22ce44930fbを
ウィンストンユワート14

@WinstonEwertコメントへの応答については、編集を参照してください。
cbojar 14

編集により、いくつかの有効なポイントが作成されます。そして、私は確かにガード条項には場所があると思います。しかし、一般的なケースでは、私はelse節に賛成です。
ウィンストンイーバート14

0

あなたの例を単純化しすぎています。 より大きな状況に応じて、どちらの選択肢も読みやすい場合があります。具体的には、条件の2つの分岐のいずれかが異常かどうかによって異なります。いずれかのブランチが同様に発生する可能性がある場合、...

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

...は...より読みやすい

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

... 1つ目は並列構造を示し、2つ目はそうではないためです。しかし、関数の大部分が1つのタスクに充てられ、最初にいくつかの前提条件を確認するだけでよい場合、...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

...は...より読みやすい

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

...意図的に並列構造を設定しないことは、ここで「本当にタイプ2」である入力を取得することが異常であるという手がかりを将来の読者に提供するためです。(実際にProcessInputDefinitelyOfTypeOneは、独立した関数ではないため、違いはさらに大きくなります。)


2番目の場合には、より具体的な例を選択する方が良いかもしれません。それに対する私の即座の反応は、「先に行ってとにかくそれを処理した場合、それほど異常なことはありえない」ということです。
ウィンストンユワート14

@WinstonEwertの異常は、おそらく間違った用語です。しかし、デフォルト(case1)と1つの例外がある場合、Zackには同意します。それは、if早めの戦略として» -> Doを実行し、例外を残す」と書く必要があります。デフォルトではより多くのチェックが含まれるコードを考えてください。最初に例外ケースを処理する方が高速です。
トーマスジャンク14

これは私が話し合っていたケースに陥っているようwhen using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards)です。論理的には、すべての作業ProcessInputOfTypeOneはに依存する!InputIsReallyTypeTwo(input)ため、通常の処理以外でそれをキャッチする必要があります。通常ProcessInputOfTypeTwo(input);throw ICan'tProccessThatException、類似性をより明確にする実際にそうなります。
セラリアドボール14

@WinstonEwertはい、それをもっとうまく表現できたかもしれません。トークナイザーを手作業でコーディングするときによく起こる状況を考えていました。たとえば、CSSでは、文字シーケンス " u+" は通常 "unicode-range"トークンを導入しますが、次の文字が16進数ではない場合、次に、代わりに「u」を識別子としてバックアップして処理する必要があります。したがって、メインスキャナーループは「ユニコード範囲トークンをスキャンする」ために多少不正確にディスパッチし、「...この特殊な特殊なケースにいる場合は、バックアップしてから代わりにscan-an-identifierサブルーチンを呼び出します」で始まります。
zwol

@AssortedTrailmix私が考えていた例が、例外が使用されないかもしれないレガシーコードベースからではなかったとしても、これはエラー状態ではなく、呼び出すコードProcessInputOfTypeOneは不正確だが高速のチェックを使用するだけです代わりにタイプ2をキャッチします。
zwol 14

0

はい、else節は冗長であるため、複雑さが増します。したがって、コードを無駄にせず、意味を持たないようにすることをお勧めします。冗長なthis修飾子(Java、C ++、C#)を省略したり、returnステートメントで括弧を省略したりするのと同じコードです。

優れたプログラマーは「何を追加できるのか」と考えています。優れたプログラマーは「何を削除できるのか」と考えています。


1
elseブロックの省略を見たとき、それがワンライナーで説明されている場合(これはあまり頻繁ではありません)、この種の推論であることが多いので、「デフォルト」引数を提示するために+1が表示されますが、私はしませんそれが十分に強力な推論であることがわかります。A good programmer things "what can I add?" while a great programmer things "what can I remove?".多くの人が細心の注意を払わずに行き過ぎているという一般的な格言のようです。
セラリアドボール14

ええ、あなたの質問を読んで、私はすぐにあなたがすでに決心したと感じましたが、確認を望んでいました。この答えは明らかにあなたが望んだものではありませんが、時には同意しない意見を考慮することが重要です。たぶんそれもありますか?
vidstige 2014

-1

このことに気がつき、気がつきました。

約1年前にこの質問をすると、次のような答えが得られました。

» メソッドにはentry1つしかありませんexit«これを念頭に置いて、コードを次のようにリファクタリングすることをお勧めします。

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

コミュニケーションの観点からは、何をすべきかは明らかです。If何かがそうであり、ある方法結果をelse 操作し、別の方法でそれを操作します。

しかし、これには不必要なもの が含まれelseます。else表現デフォルト・ケースを。したがって、2番目のステップでは、リファクタリングして

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

その後、疑問が発生します。なぜ一時変数を使用するのですか?リファクタリングの次のステップは、次のことにつながります。

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

したがって、これはそれが何をするのか明らかです。さらに一歩進めます。

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

またはのためのかすかな心を持ちました

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

次のように読むことができます。»CanLeaveWithoutUmbrellaはboolを返します。特別なことが起こらない場合、falseを返します «これは上から下への最初の読み取りです。

そして、条件が満たされた場合、「パース」の条件は»、それを返し、真のあなたは完全に可能性が« スキップ条件を、この機能はで何をするか、理解しているデフォルトの -case。あなたの目と心は、右側の部分を読まずに、左側のいくつかの文字をざっと読むだけです。

私は、両方のブロックのコードが直接関連しているという事実を述べることによって、他の人がより直接的に意図を述べているという印象を受けています。

はいそうです。しかし、その情報は冗長です。デフォルトのケースと、if-statementでカバーされる1つの「例外」があります。

より複雑な構造を扱うときは、別の戦略を選択し、iforい兄弟switchを省略します。


+1冗長性は、明らかに複雑さだけでなく混乱の増加を示しています。冗長性があるため、このように記述されたコードを常に二重チェックしなければならなかったことが常にわかっています。
dietbuddha

1
で始まるケースの後、ケースを削除しSo this is clear in what it does...て展開できますか?(前のケースも関連性が疑わしい)。私がこれを言うのは、それが私たちが調査するように求めている質問だからです。あなたは自分のスタンスを述べ、elseブロックを省略しました。実際には、より多くの説明が必要なスタンスです(そして、この質問をするように私を促したスタンスです)。質問から:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
セラリアドボール14

@AssortedTrailmix確かに!正確に何を詳しく説明する必要がありますか?最初の質問は「他のブロックはコードの複雑さを増す」というものだったので、私の答えは「はい」です。この場合、省略できます。
トーマスジャンク14

そして:いいえ!私は下票を理解していません。
トーマスジャンク14

-1

長い話を短くするために、この場合、elseキーワードを取り除くことは良いことです。ここでは完全に冗長であり、コードをどのようにフォーマットするかに応じて、コードに完全に無駄な行を1〜3行追加します。ifブロックの内容を検討してください。

return true;

したがって、ifブロックに入る場合、残りの関数全体は定義上、実行されません。しかし、それが入力されない場合、それ以上のifステートメントがないため、関数の残り全体が実行されます。returnステートメントがifブロックにない場合はまったく異なります。その場合、elseブロックの有無は影響しますが、ここでは影響はありません。

あなたの質問では、if条件を逆にしてを省略した後else、次のように述べました。

誰かが、最初の条件をすぐに正しく認識せずに、最初の例の後の条件に基づいて新しいifブロックを追加できるようになりました。

彼らは、コードをもう少し詳しく読む必要があります。これらの問題を軽減するために高レベルの言語と明確なコード編成を使用しますが、完全に冗長なelseブロックを追加すると、コードに「ノイズ」と「クラッター」が追加され、if条件を反転または再反転する必要はありません。違いがわからない場合、彼らは何をしているのかわかりません。彼らが違いを伝えることができれば、彼らは常に元のif条件自体を反転させることができます。

私はelseブロックの省略に同意し、ifブロックを使用して、nullチェック(またはその他のガード)などのすべての後続コードに対して論理的に満たす必要がある制約を適用する場合について言及するのを忘れました)。

それは基本的にここで起こっていることです。ifブロックから戻っているため、if評価する条件false 、後続のすべてのコードで論理的に満たす必要ある制約です。

elseブロックを含めないことで複雑さが増しますか?

いいえ、それはコードの複雑さの減少を構成し、特定の非常に簡単な最適化が行われるかどうかに応じて、コンパイルされたコードの減少を構成することさえあります。


2
「彼らはコードをもう少し詳しく読む必要があります。」コーディングスタイルが存在するため、プログラマーは、細かい点にあまり注意を払わず、実際に行っていることに注意を払うことができます。あなたのスタイルが不必要にそれらの詳細に注意を払わせるなら、それは悪いスタイルです。「...完全に役に立たない行...」それらは役に立たないわけではありません-この部分またはその部分が実行された場合に何が起こるかについて時間を無駄にすることなく、構造が一目でわかるようにします。
マイケルショー14

@MichaelShaw Aviv Cohnの答えは、私が話していることになると思います。
パンツァークライシス

-2

あなたが与えた特定の例では、そうします。

  • レビューアーにコードを再度読み取らせて、エラーでないことを確認します。
  • フローグラフに1つのノードが追加されるため、(不要な)循環的複雑度が追加されます。

私はこれをこれ以上簡単にすることはできなかったと思う:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

6
この非常に単純化された例は、ifステートメントを削除するために書き直すことができるという事実に特に対処します。実際、私はあなたが使用した正確なコードを使用して、さらなる単純化を明示的に推奨していません。さらに、循環的複雑度はelseブロックによって増加しません。
セラリアドボール14

-4

私は常に意図が可能な限り明確になるようにコードを書きます。あなたの例には文脈が欠けているので、少し修正します。

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

私たちの意図、空が青いときに傘なしで去ることができるようです。ただし、その単純な意図はコードの海で失われます。わかりやすくする方法はいくつかあります。

まず、elseブランチに関する質問があります。どちらの方法でも構いませんが、を除外する傾向がありelseます。私は明示的であるという議論を理解していますが、私の意見では、ifステートメントは「オプション」ブランチをラップする必要がありますreturn truereturn falseブランチはデフォルトとして機能しているため、ブランチを「オプション」とは呼びません。いずれにせよ、これは非常に小さな問題であり、以下の問題よりもはるかに重要ではないと考えています。

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

非常に多くの重要な問題は、あなたの枝はあまりやっているということです!ブランチは、あらゆるものと同じように、「できるだけシンプルだが、それ以上」ではないはずです。この場合if、実際に分岐する必要があるのは(値)だけであるときにコードブロック(ステートメントのコレクション)間で分岐するステートメントを使用しました。これは明らかです。a)コードブロックには単一のステートメントのみが含まれ、b)それらは同じステートメントであるためです()。三項演算子を使用して、分岐を異なる部分のみに絞り込むことができます。return?:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

ここで、結果がに等しいという明らかな冗長性に到達しthis.color == Color.Blueます。あなたの質問はこれを無視するように私たちに求めていることを知っていますが、これは非常に一次的な手続き上の考え方であると指摘することは本当に重要だと思います:入力値を分解し、いくつかの出力値を構築しています。それは問題ありませんが、可能であれば、入力と出力の間の関係変換の観点から考える方がはるかに強力です。この場合、それらの関係は明らかに同じですが、このような複雑な手続き型コードをよく見ますが、これは単純な関係を曖昧にします。先に進み、この単純化を行ってみましょう。

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

次に指摘するのは、APIに二重否定が含まれCanLeaveWithoutUmbrellaていることfalseです。もちろん、これはに簡略化さNeedUmbrellaれているtrueので、そうしましょう:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

これで、コーディングスタイルについて考え始めることができます。オブジェクト指向言語を使用しているように見えますがif、コードブロック間で分岐するステートメントを使用することにより、手続き型コードを作成することになります。

オブジェクト指向コードでは、すべてのコードブロックはメソッドであり、すべての分岐は動的なディスパッチを介して行われます。クラスまたはプロトタイプを使用します。それが物事を簡単にするかどうかは議論の余地がありますが、それはあなたのコードを他の言語とより一貫させるべきです:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

式間の分岐に三項演算子を使用することは、関数型プログラミングでは一般的であることに注意してください。


回答の最初の段落は質問への回答が順調に進んでいるように見えますが、この非常に単純な例では無視するよう明示的に求めている正確なトピックにすぐに分かれます。質問から:。実際、あなたは私が言及した正確なコード行を使用します。さらに、質問のこの部分はトピックから外れていますが、推測が行き過ぎだと思います。あなたはきた根本コードが何であるかを変更します。I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
セラリアドボール

@AssortedTrailmixこれは純粋にスタイルの問題です。スタイルを気にすることは理にかなっており、スタイルを無視することは理にかなっています(結果のみに焦点を合わせます)。私はそれが気に理にかなっているとは思わないreturn false;else { return false; }ながらありませんあなたがオブジェクト指向言語での手続きのコードを書いている場合は、根本的な変化が必要である等の分岐極性、動的ディスパッチ、三元系、気;)
Warbo

一般的にそのようなことを言うことができますが、明確に定義されたパラメーターで質問に答えているときは適用できません(特に、これらのパラメーターを尊重せずに質問全体を「抽象化」できる場合)。私はまた、最後の文がただ間違っていると言いたいです。OOPは「手続きの抽象化」であり、「手続きの削除」ではありません。あなたが1つのライナーから無限のクラス(各色に1つ)に行ったという事実が理由を示していると思います。さらに、私はまだ、動的ディスパッチがif/elseステートメントとどのような関係があるのか​​、ブランチの極性は何なのか疑問に思っています。
セラリアドボール14

オブジェクト指向ソリューションの方が優れているとは言いませんでした(実際、関数型プログラミングを好む)、私はそれが言語とより一貫していると言っただけです。「極性」とは、「真実」と「偽」を意味します(「NeedUmbrella」に切り替えました)。OO設計では、すべての制御フローは動的ディスパッチによって決定されます。例えば、Smalltalkのは、何か他のものは許可していませんen.wikipedia.org/wiki/Smalltalk#Control_structures(も参照c2.com/cgi/wiki?HeInventedTheTermを
Warbo

2
最後の段落(OOについて)に到達するまで、これを支持していましたが、代わりに支持を得ました!それはまさに、スパゲッティと同じくらい読めないラビオリのコードを生成するOO構造の考えられない使い過ぎです。
zwol
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.