web-dev-qa-db-ja.com

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

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

例えばこの:

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

これに

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

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

(提供されている例はthat badではなく、単に説明のためです...複数の引数を持つ他の呼び出しを想像してください。)

しかし、この抽出では、短絡評価(SCE)を失いました。

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

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

短絡評価と同じ素晴らしいパフォーマンスの利点が得られますが、コードは読みやすくなります。

118
Horia Coman

私は条件を複数の行に分解する傾向があります、すなわち:

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

複数の演算子(&&)を扱う場合でも、括弧の各ペアでインデントを進める必要があります。 SCEはまだ有効です-変数を使用する必要はありません。この方法でコードを記述すると、すでに何年もずっと読みやすくなりました。より複雑な例:

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

条件の長いチェーンといくつかの短絡を維持するものがある場合、一時変数を使用して複数の条件を組み合わせることができます。あなたの例を挙げると、例えば.

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

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

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

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

以下も使用できます。

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()
   )
21
KIIV

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

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

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

2)これは意見に基づいていますが、合理的に複雑な表現では次のことができます。

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

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

21
SJuan76

1)毎回本当にSCEを失いますか?コンパイラは、「最適化する」ことを許可されたシナリオであり、それでもSCEを提供しますか?

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

2)そのような状況でのベストプラクティスは何ですか? (SCEが必要なときに)必要なものすべてをifと "できるだけ読みやすくなるようにフォーマットする"ことだけが可能です。

わかりやすい名前の1つの関数または1つの変数にリファクタリングすることを好みます。これにより、短絡評価と可読性の両方が維持されます。

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

...

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

そして、getSomeResult()SomeComplicatedFunctionCall()に基づいてOtherComplicatedFunctionCall()を実装するとき、それらがまだ複雑な場合は再帰的に分解できます。

14
songyuanyao

1)毎回本当にSCEを失いますか?コンパイラは、「最適化する」ことを許可されたシナリオであり、それでもSCEを提供しますか?

いいえ、そうではありませんが、適用方法が異なります。

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

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

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

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

ここでは、両方の関数willは、b1およびb2に格納する必要があるため実行されます。 Ff b1 == trueその後b2は評価されません(SCE)。ただし、OtherComplicatedFunctionCall()はすでに実行されています。

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

2)そのような状況でのベストプラクティスは何ですか? (SCEが必要な場合)必要なものすべてをifと "できるだけ読みやすくなるようにフォーマットする"ことだけが可能です。

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

9

短絡し、一箇所に条件がある別の可能性:

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

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

8
levilime

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

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) {
...
}
4
Dominique