ifステートメント-短絡評価と読みやすさ


90

場合によっては、ifステートメントがかなり複雑になったり長くなったりする可能性があるため、読みやすくするために、の前に複雑な呼び出しを抽出することをお勧めしifます。

例えばこれ:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

これに

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(提供された例はないことを複数の引数などで他の呼び出しを想像し...、それだけで説明のために悪いです)

しかし、この抽出により、短絡評価(SCE)が失われました。

  1. 毎回本当にSCEを失うのですか?コンパイラーが「最適化」してもSCEを提供できるシナリオはありますか?
  2. SCEを失うことなく2番目のスニペットの読みやすさを向上させる方法はありますか?

20
練習では、ここまたは他の場所で表示されるパフォーマンスに関するほとんどの回答がほとんどの場合間違っていることを示しています(4つが間違っている1つが正しい)。私のアドバイスは、常にプロファイリングを行い、自分でチェックすることです。「時期尚早の最適化」を回避し、新しいことを学ぶことができます。
Marek R

25
@MarekRは...それはOtherCunctionCallにおける副作用の可能性についてですだけではなく、パフォーマンスについてです
relaxxx

3
@David他のサイトを参照するとき、クロス投稿が嫌われる
gnat

7
読みやすさが主な懸念事項である場合は、if条件内で副作用のある関数を呼び出さないでください
Morgen

3
潜在的な近い有権者:もう一度質問を読んでください。パート(1)は意見ベースではありませんが、パート(2)は、私がやろうとしているように、想定される「ベストプラクティス」への参照を削除する編集によって、意見ベースになることを簡単にやめることができます。
2016年

回答:


119

1つの自然な解決策は次のようになります。

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

これには、理解しやすく、すべてのケースに適用でき、短絡動作をするという利点があります。


これが私の最初の解決策でした。メソッド呼び出しとforループ本体の適切なパターンは次のとおりです。

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

ショートサーキット評価と同じ優れたパフォーマンス上の利点が得られますが、コードは読みやすくなります。


4
@relaxxx:わかりましたが、「の後に実行する必要があることif」も、関数またはメソッドが大きすぎることを示しており、小さいものに分割する必要があります。それは常に最良の方法であるとは限りませんが、非常に頻繁にそうなります!
nperson325681

2
これはホワイトリストの原則に違反しています
JoulinRouge 2016年

13
@JoulinRouge:興味深いことに、私はこの原則について聞いたことがありませんでした。私自身、この「短絡」アプローチを読みやすさの利点のために好んでいます。それはインデントを減らし、インデントされたブロックの後に何かが発生する可能性を排除します。
Matthieu M.

2
より読みやすいですか?b2適切に名前を付けるとsomeConditionAndSomeotherConditionIsTrue、非常に意味のあるものではなく、また、私はこの演習の間、メンタルスタックに一連の変数を保持する必要があります(このスコープでの作業を停止するまではtbhです)。私はSJuan76第2の解決策を採用するか、全体を関数に入れます。
Nathan Cooper

2
私はすべてのコメントを読んだわけではありませんが、高速検索の後、最初のコードスニペット、つまりデバッグの大きな利点を見つけられませんでした。事前に変数に代入するのではなく、直接ifステートメントに要素を配置してから変数を使用すると、デバッグが必要以上に難しくなります。変数を使用すると、値を意味的にグループ化して読みやすくすることもできます。
rbaleksandar

31

条件を複数の行に分割する傾向があります。

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

複数の演算子(&&)を扱う場合でも、ブラケットの各ペアでインデントを進める必要があります。SCEはまだ機能しています。変数を使用する必要はありません。この方法でコードを記述したことで、何年も前からずっと読みやすくなりました。より複雑な例:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {

28

条件のチェーンが長く、短絡の一部を維持する必要がある場合は、一時変数を使用して複数の条件を組み合わせることができます。あなたの例をとると、それを行うことが可能になるでしょう

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

C ++ 11対応のコンパイラを使用している場合は、ラムダ式を使用して、上記と同様に式を関数に結合できます。

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }

21

1)はい、SCEはもうありません。そうでなければ、あなたはそれを持っているでしょう

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if後でステートメントがあるかどうかに応じて、いずれかの方法で機能します。複雑すぎる。

2)これは意見に基づくものですが、かなり複雑な表現の場合は次のことができます。

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

複雑すぎる場合、明白な解決策は、式を評価してそれを呼び出す関数を作成することです。


21

次のものも使用できます。

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

SCEが機能します。

しかし、それは例えばよりも読みやすくはありません:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

3
ブール値をビットごとの演算子と組み合わせることにあまり熱心ではありません。一般に、非常に低いレベルで作業していて、プロセッササイクル数を除いて、最も読みやすいものを使用します。
Ant

3
私は具体的に使用しており b = b || otherComplicatedStuff();、@ SargeBorschはSCEを削除するために編集を行います。@Antの変更についてお知らせいただきありがとうございます。
KIIV 2016年

14

1)毎回本当にSCEを失うのですか?コンパイラーは、「最適化」してもSCEを提供できるシナリオですか?

そのような最適化は許可されていないと思います。特にOtherComplicatedFunctionCall()いくつかの副作用があるかもしれません。

2)そのような状況でのベストプラクティスは何ですか?(SCEが必要なときに)必要なすべてを直接内部に配置し、「できるだけ読みやすいようにフォーマットする」ことが唯一の可能性ですか?

私はそれを1つの関数または説明的な名前の1つの変数にリファクタリングすることを好みます。短絡評価と読みやすさの両方を維持します:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

また、およびにgetSomeResult()基づいて実装する場合、まだ複雑な場合は再帰的に分解できます。SomeComplicatedFunctionCall()OtherComplicatedFunctionCall()


2
このように私はあなたが本当に価値のあるものを追加していない、(おそらくないgetSomeResultいえ)ラッパー関数にあまりにも多くの他の回答を説明的な名前を与えることによって、いくつかの読みやすさを得ることができますので、
aw04

9

1)毎回本当にSCEを失うのですか?コンパイラーは、「最適化」してもSCEを提供できるシナリオですか?

いいえ、違いますが、適用方法は異なります。

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

ここでは、コンパイラでも実行されませんOtherComplicatedFunctionCall()場合はSomeComplicatedFunctionCall()trueを返します。

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

ここでは、両方の機能がします彼らはに格納する必要があるために実行b1してb2。Ff b1 == trueb2評価されません(SCE)。しかし、OtherComplicatedFunctionCall()すでに実行されています。

if b2が他の場所で使用されていない場合、コンパイラー、関数に観察可能な副作用がない場合にif内の関数呼び出しをインライン化するのに十分スマートである可能性あります。

2)そのような状況でのベストプラクティスは何ですか?(SCEが必要なときに)必要なすべてを直接内部に配置し、「できるだけ読みやすいようにフォーマットする」ことが唯一の可能性ですか?

場合によります。副作用のために実行する必要 OtherComplicatedFunctionCall()があるか、または関数のパフォーマンスへの影響が最小限である場合、読みやすさのために2番目のアプローチを使用する必要があります。それ以外の場合は、最初のアプローチでSCEに固執します。


8

短絡し、1つの場所に条件がある別の可能性:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

ループを関数に入れ、関数に条件のリストを受け入れてブール値を出力させることができます。


1
@Erburethいいえ、そうではありません。配列の要素は関数ポインタです。関数がループで呼び出されるまで、yyは実行されません。
Barmar

Barmarに感謝しますが、私は編集を行いました。編集の前はErburethが正しかったです(私の編集は視覚的に直接伝播すると思いました)。
levilime 2016年

4

非常に奇妙です:コード内のコメントの使用法について誰も言及していないとき、あなたは読みやすさについて話しています:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

その上で、私は常に関数の前に、関数自体について、その入力と出力についてのいくつかのコメントを付けています。

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

明らかに、コメントに使用するフォーマットは開発環境(Visual Studio、EclipseでのJavaDocなど)によって異なる場合があります。

SCEに関する限り、これは次のことを意味します。

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}

-7

あなたが会社で働いていて、あなたのコードが他の誰かに読まれるなら、読みやすさが必要です。自分でプログラムを作成する場合、わかりやすいコードのためにパフォーマンスを犠牲にしたいかどうかはあなた次第です。


23
「6か月後のあなた」は間違いなく「他の誰か」であり、「あなたの明日」は時々そうである可能性があることを覚えておいてください。パフォーマンスの問題があったという確かな証拠が得られるまで、パフォーマンスの読みやすさを犠牲にすることはありません。
Martin Bonnerがモニカをサポートする
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.