短絡評価、それは悪い習慣ですか?


88

私がしばらく知っているが、考慮されていないことは、ほとんどの言語では、順序に基づいてifステートメントで演算子を優先することができるということです。私はよく、これをヌル参照例外を防ぐ方法として使用します。例えば:

if (smartphone != null && smartphone.GetSignal() > 50)
{
   // Do stuff
}

この場合、コードは最初にオブジェクトがnullでないことを確認し、次にこのオブジェクトが存在することを知って使用します。言語が賢いのは、最初のステートメントがfalseの場合、2番目のステートメントを評価しても意味がないため、null参照例外がスローされないことがわかっているためです。これはandand or演算子でも同じように機能します。

これは、インデックスが配列の境界内にあることを確認するなど、Java、C#、C ++、Python、Matlabなどのさまざまな言語でこのような手法を実行できるなど、他の状況でも役立ちます。

私の質問は次のとおりです。この種のコードは悪い習慣を表していますか?この悪い習慣は、隠れた技術的な問題から生じますか(つまり、最終的にエラーになる可能性があります)、または他のプログラマーにとって読みやすさの問題につながりますか?紛らわしいですか?


69
専門用語は短絡評価です。これはよく知られた実装手法であり、言語標準がそれを保証している場合、それに依存するのは良いことです。(そうでない場合、それはあまり言語ではありません、IMO。)
Kilian Foth

3
ほとんどの言語では、必要な動作を選択できます。オペレータC#で||短絡、オペレータ|(単管)がない(&&&同じように動作します)。短絡演算子ははるかに頻繁に使用されるため、非短絡演算子の使用法にコメントを付けて、その動作が必要な理由を説明することをお勧めします(ほとんどの場合、メソッド呼び出しなどが発生する必要があります)。
ライナック

14
@linacは、何かを右側に配置し&たり|、副作用が発生することを確認したりすることは、間違いなく悪い習慣だと言います。そのメソッド呼び出しを独自の行に配置するのに費用はかかりません。
ジョンハンナ

14
@linac:CとC ++では、&&ショートサーキットであり、ショートサーキットで&はありませんが、それらは非常に異なる演算子です。&&論理的な「and」です。&ビット単位の「and」です。falseのx && y場合にtrueになる可能性x & yがあります。
キーストンプソン

3
あなたの特定の例は、別の理由で悪い習慣になる可能性があります:それ nullの場合どうなりますか?ここでnullにするのは妥当ですか?なぜヌルですか?このブロック以外の関数の残りの部分がnullの場合、すべてを実行する必要がありますか、それともプログラムを停止する必要がありますか?すべてのアクセスで「nullの場合」を記録する(おそらく、null参照の例外が原因で駆除が急がれたため)ことは、電話オブジェクトの初期化がねじ込まれたことに気付かずに、プログラムの残りの部分にかなり入り込むことができることを意味しますアップ。そして最終的に、最終的にそうでないコードに到達したとき
-Random832

回答:


125

いいえ、これは悪い習慣ではありません。条件式の短絡に依存することは、この動作を保証する言語(現代言語の大部分を含む)を使用している限り、広く受け入れられている有用な手法です。

あなたのコード例は非常に明確であり、実際、それは多くの場合それを書くための最良の方法です。代替(ネストされたifステートメントなど)は、面倒で複雑で、理解しにくいものになります。

いくつかの注意事項:

複雑さと読みやすさに関する常識を使用する

以下はあまり良い考えではありません:

if ((text_string != null && replace(text_string,"$")) || (text_string = get_new_string()) != null)

コード内の行を削減する多くの「賢い」方法と同様に、この手法に頼りすぎると、理解しづらく、エラーが発生しやすいコードになります。

エラーを処理する必要がある場合、多くの場合、より良い方法があります

あなたの例は、スマートフォンがヌルであることが通常のプログラム状態である限り問題ありません。ただし、これがエラーの場合は、よりスマートなエラー処理を組み込む必要があります。

同じ条件の繰り返しチェックを避ける

ニールが指摘しているように、異なる条件で同じ変数を何度も繰り返しチェックすることは、設計上の欠陥の証拠である可能性が最も高いです。あなたがいる場合に実行すべきではない、あなたのコードを振りかけた10種類の書類がある場合smartphoneであるがnull、変数を毎回チェックするよりも、これを処理するためのより良い方法があるかどうかを検討してください。

これは、特にショートサーキットに関するものではありません。反復コードのより一般的な問題です。ただし、言及する価値はあります。なぜなら、例のステートメントのような多くの繰り返しステートメントを含むコードを見るのは非常に一般的だからです。


12
短絡評価でインスタンスがnullであるかどうかをチェックするブロックが繰り返される場合、そのチェックを1回だけ実行するように書き直す必要があることに注意してください。
ニール

1
@ニール、良い点; 答えを更新しました。

2
私が追加できると思う唯一のことは、それを処理する別のオプションの方法に注意することです。これは、複数の異なる条件で何かをチェックする場合に本当に好ましいです:if()で何かがnullであるかどうかを確認してから、リターン/スロー-それ以外の場合はそのまま続けます。これにより、ネストが排除され、コードをより明確かつ簡潔にすることができます。たとえば、「これはnullであってはならず、もしそうなら私はここから離れます」-コードのできるだけ早い段階で配置されます。メソッド内の1箇所以上で特定の条件をチェックする何かをコーディングするときはいつでも素晴らしい。
ブライアン

1
@ dan1111「条件には副作用(上記の変数割り当てなど)を含めることはできません。短絡評価はこれを変更しないため、サイドのショートハンドとして使用しないでください」 if / then関係をより適切に表すために、ifの本体に簡単に配置できる効果。」
アーロンLS

@AaronLS、「条件に副作用が含まれてはならない」という主張には強く反対します。条件は不必要に混乱させるべきではありませんが、明確なコードである限り、条件に副作用(または複数の副作用)があれば大丈夫です。

26

Cスタイル言語を使用せず&&、質問と同等のコードを実行する必要があるとします。

あなたのコードは次のようになります:

if(smartphone != null)
{
  if(smartphone.GetSignal() > 50)
  {
    // Do stuff
  }
}

このパターンは、多くの結果になります。

ここで、仮想言語が導入するバージョン2.0を想像してください&&。あなたはそれがいかにクールだと思いますか!

&&上記の例が行うことを実行するための認識された慣用的な手段です。それを使用することは悪い習慣ではありません。実際、上記のような場合に使用しないことは悪い習慣elseです。

言語が賢いのは、最初のステートメントがfalseの場合、2番目のステートメントを評価しても意味がないため、null参照例外がスローされないことがわかっているためです。

いいえ、あなたはあなたが最初の文が偽だった場合、その後も、第二の文を評価しない点がないことを知っていたので、賢いです。言語は岩の箱のように愚かであり、あなたがそれをするように言ったことをしました。(John McCarthyはさらに賢く、短絡評価は言語にとって有用なものであることを認識していました)。

あなたが賢いということと、言語が賢いということの違いは重要です。なぜなら、良い練習と悪い練習は、しばしばあなたが必要なだけ賢くて、それ以上賢くないということになるからです。

考慮してください:

if(smartphone != null && smartphone.GetSignal() > ((range = (range != null ? range : GetRange()) != null && range.Valid ? range.MinSignal : 50)

これにより、コードが拡張され、rangerangeがnullの場合、GetRange()失敗するrange可能性があるので、まだnullである可能性がありますが、呼び出しによって設定しようとします。この後に範囲がnullでない場合Valid、そのMinSignalプロパティが使用50されます。それ以外の場合は、デフォルトが使用されます。

これも依存します&&が、おそらく1行に入れるのは賢すぎるでしょう(私が100%正しいと確信しているわけではありません。また、その事実が私の主張を実証しているので、再確認しません)。

&&ただし、ここで問題になるわけではありませんが、1つの式に多くを使用する機能(良いこと)により、理解しにくい式を不必要に書く能力(悪いこと)が増加します。

また:

if(smartphone != null && (smartphone.GetSignal() == 2 || smartphone.GetSignal() == 5 || smartphone.GetSignal() == 8 || smartPhone.GetSignal() == 34))
{
  // do something
}

ここでは&&、特定の値のチェックとの使用を組み合わせています。これは電話信号の場合には現実的ではありませんが、他の場合には発生します。ここで、それは私が十分に賢くないことの例です。次のことをした場合:

if(smartphone != null)
{
  switch (smartphone.GetSignal())
  {
    case 2: case 5: case 8: case 34:
      // Do something
      break;
  }
}

私は読みやすさとパフォーマンスの両方を得ることができました(への複数の呼び出しGetSignal()はおそらく最適化されていなかったでしょう)。

ここでも問題は&&、その特定のハンマーを使用して、他のすべてを釘として見ることではありません。それを使用しないことで、使用するよりも良いことができます。

ベストプラクティスから離れる最後のケースは次のとおりです。

if(a && b)
{
  //do something
}

に比べ:

if(a & b)
{
  //do something
}

私たちが後者を好む理由に関する古典的な議論はb、私たちaが真実であるかどうかを望むことを評価する際に何らかの副作用があるということです。私はそのことに同意しません。その副作用が非常に重要な場合は、別のコード行でそれを実現してください。

ただし、効率の観点からは、この2つの方が優れている可能性があります。最初のコードは明らかに少ないコードを実行し(b1つのコードパスでまったく評価しない)、評価にかかる時間を節約できますb

ただし、最初のブランチにももう1つブランチがあります。仮想Cスタイル言語でnoを使用して書き換えるかどうかを検討します&&

if(a)
{
  if(b)
  {
    // Do something
  }
}

この余分な部分ifは、の使用には隠されていますが&&、まだ存在しています。そのため、分岐予測が発生し、そのため分岐予測ミスが発生する可能性がある場合です。

そのため、if(a & b)場合によってはコードをより効率的にすることができます。

ここでif(a && b)は、それが最初のベストプラクティスアプローチであると言います。より一般的なのは、場合によっては実行可能な唯一のもの(falseのb場合aはエラーになる)であり、より高速です。if(a & b)ただし、特定の場合には、それが便利な最適化であることが多いことに注意してください。


1
1が持っている場合はtry返すメソッドtrue成功の場合には、あなたはをどう思いますかif (TryThis || TryThat) { success } else { failure }?あまり一般的なイディオムではありませんが、コードの重複やフラグを追加せずに同じことを達成する方法がわかりません。フラグがある場合に到達可能なステートメントを含む1 -フラグに必要なメモリは、分析の観点から、非問題であろうが、有効フラグを追加すると、2つの平行なユニバースにコードを分割しtrue、それがだときに到達他方のステートメントをfalse。DRYの観点からは良いこともあるが、むかつく。
supercat

5
私は何も間違っているとは思わないif(TryThis || TryThat)
ジョンハンナ

3
くそー良い答え、先生。
ビショップ

FWIW、Bell LabsのKernighan、Plauger&Pikeなどの優秀なプログラマーは、同じ式を複数回評価することを意味する場合でも、明確にするために、などのif {} else if {} else if {} else {}ネストされた制御構造ではなく、浅い制御構造を使用することを推奨してif { if {} else {} } else {}います。Linus Torvaldsは同様のことを言った:「もしあなたが3レベル以上のインデントが必要なら、とにかくねじ込まれている」。それを短絡演算子への投票と考えましょう。
サムワトキンス

if(a && b)ステートメント; かなり簡単に交換できます。if(a && b && c && d)statement1; elseステートメント2; 本当に痛いです。
gnasher729

10

これをさらに進めることができます。一部の言語では、これが物事を行う慣用的な方法です。条件付きステートメントの外側で短絡評価を使用することもでき、それらは条件付きステートメント自体の形式になります。例えば、Perlでは、関数が失敗すると偽の(そして成功すると真実の)何かを返すのが慣用的です。

open($handle, "<", "filename.txt") or die "Couldn't open file!";

慣用的です。open成功した場合はゼロ以外の値を返し(die後の部分orは発生しないように)、失敗した場合は未定義になり、呼び出しdieが発生します(例外を発生させるPerlの方法です)。

それは誰もがそれを行う言語では問題ありません。


17
Perlの言語設計への最大の貢献は、Perlを使用しない方法の複数の例を提供することです。
ジャックエイドリー

@JackAidley:Perlのunless、および接尾辞の条件(send_mail($address) if defined $address)が恋しいです。
RemcoGerlich

6
@JackAidleyは、「or die」が悪い理由へのポインタを提供できますか?それは例外を処理するための単純な方法ですか?
ビッグストーン

Perlで多くの恐ろしいことを行うことは可能ですが、この例では問題が見当たりません。スクリプト言語でのエラー処理に必要なものは、単純で短く、明確です。

1
:Rubyはそのスタイルの一部を継承し@RemcoGerlichsend_mail(address) if address
bonh

6

私は一般にdan1111の答えに同意しますが、カバーしていない重要なケースが1つあります。それはsmartphone同時に使用されるケースです。そのシナリオでは、この種のパターンは、見つけにくいバグのよく知られたソースです。

問題は、短絡評価がアトミックではないことです。あなたのスレッドはそれをチェックすることsmartphoneができnull、別のスレッドが入ってそれを無効にすることができ、それからあなたのスレッドは即座にそれを試みてGetSignal()爆破します。

しかし、もちろん、それは星が整列し、スレッドのタイミングがちょうど良いときにのみ行われます。

したがって、この種のコードは非常に一般的で便利ですが、この厄介なバグを正確に防ぐことができるように、この警告について知ることが重要です。


3

最初に1つの条件をチェックし、最初の条件が成功した場合にのみ2番目の条件をチェックしたい状況がたくさんあります。時には効率のためだけに(最初の条件が既に失敗した場合は2番目の条件をチェックするポイントがないため)、それ以外の場合はプログラムがクラッシュする(最初にNULLのチェック)ため、そうでない場合は結果がひどいため (IF(ドキュメントを印刷したい)および(ドキュメントの印刷に失敗した))エラーメッセージを表示します-ドキュメントを印刷したくない場合、ドキュメントを印刷したくない場合は印刷が失敗したかどうかを確認します最初の場所!

そのため、論理式の短絡評価の保証が非常に必要でした。Pascal(非保証型の短絡評価)には存在しませんでしたが、これは大きな苦痛でした。私は最初にそれをエイダとCで見ました。

これが「悪い習慣」であると本当に思う場合は、中程度に複雑なコードを取り、短絡評価なしで正常に機能するように書き直して、戻ってあなたがそれを気に入った方法を教えてください。これは、呼吸は二酸化炭素を生成すると言われ、呼吸が悪い習慣かどうかを尋ねるようなものです。数分間試してみてください。


「短絡評価なしで正常に動作するように書き直し、戻ってきてどのように気に入ったか教えてください。」-はい、これでパスカルの思い出がよみがえります。いいえ、私はそれが好きではありませんでした。
トーマスパドロン-マッカーシー

2

他の回答には記載されていませんが、これらのチェックを行うと、Null Objectデザインパターンのリファクタリングの可能性を示唆する場合があります。例えば:

if (currentUser && currentUser.isAdministrator()) 
  doSomething();

次のように単純化できます。

if (currentUser.isAdministrator())
  doSomething ();

場合はcurrentUser、フォールバックの実装でいくつかの「匿名ユーザー」または「ヌルユーザにデフォルト設定され、ユーザーはログインしていない場合。

必ずしもコードの匂いではありません、考慮すべきことがあります。


-4

私は人気がなくなり、はい、それは悪い習慣だと言います。IFあなたのコードの機能は、条件付きのロジックを実装するためにそれに頼っています。

すなわち

 if(quickLogicA && slowLogicB) {doSomething();}

良いですが、

if(logicA && doSomething()) {andAlsoDoSomethingElse();}

悪いし、きっと誰も同意しないだろう!?

if(doSomething() || somethingElseIfItFailed() && anotherThingIfEitherWorked() & andAlwaysThisThing())
{/* do nothing */}

推論:

単純にロジックを実行しないのではなく、両側が評価されると、コードはエラーになります。これは問題ではないと主張されてきました。'and '演算子はc#で定義されているため、意味は明確です。しかし、私はこれは真実ではないと主張します。

理由1.歴史的

基本的に、コードの機能を提供するために、コンパイラーの最適化(本来の目的)に依存しています。

C#では、言語仕様によりブールの「and」演算子が短絡を行うことが保証されています。これは、すべての言語およびコンパイラに当てはまるわけではありません。

wikipedaにはさまざまな言語と、短絡に関するhttp://en.wikipedia.org/wiki/Short-circuit_evaluationの一覧があります。多くのアプローチがあります。そして、ここには触れないいくつかの余分な問題。

特に、FORTRAN、Pascal、およびDelphiには、この動作を切り替えるコンパイラフラグがあります。

したがって、リリース用にコンパイルされたときにコードが機能することがありますが、デバッグ用または代替コンパイラなどでは機能しないことがあります。

理由2.言語の違い

ウィキペディアからわかるように、ブール演算子がどのように処理するかを指定している場合でも、すべての言語が短絡に対して同じアプローチを採用しているわけではありません。

特に、VB.Netの「and」演算子は短絡せず、TSQLにはいくつかの特性があります。

最新の「ソリューション」にはjavascript、regex、xpathなどの多くの言語が含まれる可能性が高いことを考えると、コードの機能を明確にする際にこれを考慮する必要があります。

理由3.言語内の違い

たとえば、VBのインラインifは、ifとは異なる動作をします。繰り返しますが、最近のアプリケーションでは、たとえばコードビハインドとインラインWebフォームの間でコードが移動する可能性があるため、意味の違いに注意する必要があります。

理由4.関数のパラメーターをnullチェックする特定の例

これは非常に良い例です。それは誰もがすることですが、実際に考えると悪い習慣です。

コードの行を大幅に削減するため、この種のチェックを見るのは非常に一般的です。誰もがコードレビューでそれを持ち出すとは思わない。(そして明らかに、あなたはそれが人気があることを見ることができるコメントと評価から!)

ただし、複数の理由でベストプラクティスに失敗します!

  1. その非常に多数の供給源からの明確な、そのベストプラクティスは、あなたがそれらを使用する前に、有効性のために、あなたのパラメータをチェックするために、彼らが無効である場合に例外をスローすることです。コードスニペットは周囲のコードを表示しませんが、おそらく次のようなことをしているはずです。

    if (smartphone == null)
    {
        //throw an exception
    }
    // perform functionality
  2. 条件ステートメントから関数を呼び出しています。関数の名前は、単に値を計算することを意味しますが、副作用を隠すことができます。例えば

    Smartphone.GetSignal() { this.signal++; return this.signal; }
    
    else if (smartphone.GetSignal() < 1)
    {
        // Do stuff 
    }
    else if (smartphone.GetSignal() < 2)
    {
        // Do stuff 
    }

    ベストプラクティスは次のとおりです。

    var s = smartphone.GetSignal();
    if(s < 50)
    {
        //do something
    }

これらのベストプラクティスをコードに適用すると、非表示の条件を使用してコードを短くする機会がなくなることがわかります。ベストプラクティスは、スマートフォンがnullの場合を処理するために何かをすることです。

これは他の一般的な使用法にも当てはまると思います。私たちも持っていることを覚えておく必要があります:

インライン条件

var s = smartphone!=null ? smartphone.GetSignal() : 0;

およびヌル合体

var s = smartphoneConnectionStatus ?? "No Connection";

同様の短いコード長の機能をより明確に提供します

私のすべてのポイントをサポートする多数のリンク:

https://stackoverflow.com/questions/1158614/what-is-the-best-practice-in-case-one-argument-is-null

C#でのコンストラクターパラメーターの検証-ベストプラクティス

彼がヌルを期待していない場合、ヌルをチェックする必要がありますか?

https://msdn.microsoft.com/en-us/library/seyhszts%28v=vs.110%29.aspx

https://stackoverflow.com/questions/1445867/why-would-a-language-not-use-short-circuit-evaluation

http://orcharddojo.net/orchard-resources/Library/DevelopmentGuidelines/BestPractices/CSharp

短絡に影響するコンパイラフラグの例:

Fortran: "sce | nosceデフォルトでは、コンパイラーはXL Fortranルールを使用して、選択した論理式で短絡評価を実行します。sceを指定すると、コンパイラーは非XL Fortranルールを使用できます。デフォルトはnosceです。」

Pascal: "B-短絡評価を制御するフラグディレクティブ。短絡評価の詳細については、2項演算子のオペランドの評価順序を参照してください。詳細については、コマンドラインコンパイラの-scコンパイラオプションも参照してください(ユーザーマニュアルに記載されています)。」

Delphi:「Delphiはデフォルトで短絡評価をサポートしています。{$ BOOLEVAL OFF}コンパイラディレクティブを使用して無効にできます。」


コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
世界エンジニア
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.