通常のパスに従うか、早期に失敗する必要がありますか?


73

コードコンプリートブック以下の引用が来ます:

「通常の場合は、後ではifなく後に入力しますelse

つまり、標準パスからの例外/偏差をelseケースに入れる必要があります。

しかし、プラグマティックプログラマーは「早くクラッシュする」ことを教えています(p。120)。

どのルールに従うべきですか?


15
重複しない@gnat
BЈовић

16
2つは相互に排他的ではなく、あいまいさはありません。
ジュウェンティング

6
コード補完の引用がこのような素晴らしいアドバイスであるかどうかはわかりません。おそらく読みやすさを高めるための試みですが、珍しいケースを最初にテキストにすることを望む状況は確かにあります。これは答えではないことに注意してください。既存の回答は、あなたの質問がうまく起因する混乱をすでに説明しています。
イーモンネルボンヌ

14
早くクラッシュ、激しくクラッシュ!ifブランチの1つが戻ってきたら、最初にそれを使用します。そしてelse、残りのコードを回避します。前提条件が失敗した場合は、すでに返されています。コードは、より少ないインデントを読むために簡単です...
CodeAngry

5
これは可読性とは何の関係もないと思うのは私だけなのでしょうが、代わりに静的分岐予測の最適化の見当違いの試みだったのでしょうか?
Mehrdad

回答:


189

「早期にクラッシュする」とは、どのコード行がテキスト的に先に来るかではありません。処理のできるだけ早い段階でエラーを検出するように指示するため、すでに障害のある状態に基づいて誤って判断や計算を行うことはありません。

if/ elseどちらも「以前の」または「後」ステップを構成すると言わないことができるように構築ブロックの一方のみが実行されます。したがって、それらをどのように注文するかは読みやすさの問題であり、「早期失敗」は決定に含まれません。


2
あなたの一般的なポイントは素晴らしいですが、1つの問題があります。(if / else if / else)がある場合、「else if」で評価された式は、「if」ステートメントの式の後に評価されます。あなたが指摘するように、重要な部分は、読みやすさと処理の概念単位です。1つのコードブロックのみが実行されますが、複数の条件を評価できます。
エンカイター

7
@Encaitar、その粒度のレベルは、「早期失敗」というフレーズを使用した場合に一般的に意図されているレベルよりもはるかに小さいです。
riwalk

2
@Encaitarそれがプログラミングの芸術です。複数のチェックに直面したとき、アイデアは最初に本当である可能性が最も高いものを試すことです。ただし、その情報は、設計時ではなく最適化段階で知られていない場合がありますが、時期尚早な最適化に注意してください。
BPugh

公正なコメント。これは良い答えであり、将来の参考のためにそれを改善するために役立つことを試みただけです
エンカイター

JavaScript、Python、perl、PHP、bashなどのスクリプト言語は、直線的に解釈されるため例外です。小さいif/else構成では、おそらく重要ではありません。しかし、ループで呼び出されるものや、各ブロックに多数のステートメントがあるものは、最も一般的な条件で最初に実行する方が高速です。
DocSalvager 14年

116

else文に失敗コードのみが含まれている場合、ほとんどの場合、そこにあるべきではありません。

これを行う代わりに:

if file.exists() :
  if validate(file) :
    # do stuff with file...
  else :
    throw foodAtMummy
else :
  throw toysOutOfPram

これを行う

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

エラーチェックを含めるためだけにコードを深くネストしたくありません。

そして、他の誰もがすでに述べているように、2つのアドバイスは矛盾していません。1つは実行順序に関するもので、もう1つはコード順序に関するものです


4
後のブロックに通常のフローを配置し、後のブロックにif例外的なフローを配置するというアドバイスelseは、else!このようなGuardステートメントは、ほとんどのコーディングスタイルでエラー状態を処理するための推奨形式です。
ジュール

+1これは良い点であり、実際にエラー状態のあるものを注文する方法の本当の質問に対する答えを与えます。
ashes999

間違いなく明確で保守しやすい。これは私がそれをやりたい方法です。
ジョン

27

両方に従う必要があります。

「早期にクラッシュする」/「早期に失敗する」というアドバイスは、可能な限り早く入力の可能性のあるエラーをテストすることを意味します。
たとえば、メソッドが正(> 0)と想定されるサイズまたはカウントを受け入れる場合、早期失敗アドバイスは、アルゴリズムが意味のないものを生成するのを待つのではなく、メソッドの最初にその条件をテストすることを意味します結果。

通常のケースを最初に置くためのアドバイスは、条件をテストする場合、最も可能性の高いパスが最初に来ることを意味します。これは、通常の場合に関数が何をしているかを理解しようとするときにコードのブロックをスキップする必要がないため、パフォーマンス(プロセッサの分岐予測がより頻繁に行われるため)と可読性に役立ちます。コードの読み取り中にスキップするエラー処理がない
ため、前提条件をテストしてすぐに(アサートまたはif (!precondition) throw構造を使用して)救済する場合、このアドバイスは実際には適用されません。


1
分岐予測の部分について詳しく説明してください。ifケースに行く可能性が高いコードが、elseケースに行く可能性が高いコードよりも速く実行されるとは思わないでしょう。つまり、これが分岐予測のポイントですよね。
ローマンライナー14年

@ user136712:最新の(高速)プロセッサでは、前の命令の処理が完了する前に命令がフェッチされます。分岐予測は、条件付き分岐の実行中にフェッチされた命令が実行する正しい命令である可能性を高めるために使用されます。
バートヴァンインゲンシェナウ14年

2
分岐予測とは何かを知っています。あなたの投稿を正しく読んだら、分岐予測if(cond){/*more likely code*/}else{/*less likely code*/}よりも高速に実行されると言いますif(!cond){/*less likely code*/}else{/*more likely code*/}。分岐予測はifelseステートメントまたはステートメントのどちらにも偏っておらず、履歴のみを考慮していると思います。したがって、else発生する可能性が高い場合は、それを同じように予測できるはずです。この仮定は間違っていますか?
ローマンライナー14年

18

@JackAidley はその要点をすでに述べていると思いますが、次のように定式化させてください。

例外なし(Cなど)

通常のコードフローには、次のものがあります。

if (condition) {
    statement;
} else if (less_likely_condition) {
    less_likely_statement;
} else {
    least_likely_statement;
}
more_statements;

「早期エラーアウト」の場合、コードは突然読み取ります。

/* demonstration example, do NOT code like this */
if (condition) {
    statement;
} else {
    error_handling;
    return;
}

あなたはこのパターンを見つけた場合- returnelse(あるいはif問題のコードがないので)ブロック、すぐにそれを手直ししませ持つelseブロックを:

/* only code like this at University, to please structured programming professors */
function foo {
    if (condition) {
        lots_of_statements;
    }
    return;
}

現実世界では…

/* code like this instead */
if (!condition) {
    error_handling;
    return;
}
lots_of_statements;

これは、あまりにも深くネスト回避し、「早期抜け出す」の場合を満たして(心を維持するのに役立ちます-と、コードの流れ-クリーン)「に可能性が高いものを入れ違反しないifだけで何も存在しないため、一部」elseの部分は、 。

C そしてクリーンアップ

同様の質問(これは間違っていました)の答えに触発され、Cでクリーンアップを行う方法を次に示します。1つまたは2つの出口ポイントを使用できます。2つの出口ポイントに1つあります。

struct foo *
alloc_and_init(size_t arg1, int arg2)
{
    struct foo *res;

    if (!(res = calloc(sizeof(struct foo), 1)))
        return (NULL);

    if (foo_init1(res, arg1))
        goto err;
    res.arg1_inited = true;
    if (foo_init2(&(res->blah), arg2))
        goto err;
    foo_init_complete(res);
    return (res);

 err:
    /* safe because we use calloc and false == 0 */
    if (res.arg1_inited)
        foo_dispose1(res);
    free(res);
    return (NULL);
}

実行するクリーンアップが少ない場合は、それらを1つの終了ポイントにまとめることができます。

char *
NULL_safe_strdup(const char *arg)
{
    char *res = NULL;

    if (arg == NULL)
        goto out;

    /* imagine more lines here */
    res = strdup(arg);

 out:
    return (res);
}

gotoあなたがそれに対処することができれば、この使用はまったく問題ありません。使用を中止するためのアドバイスgotoは、使用が良い、許容できる、悪い、スパゲッティコード、または他の何かであるかどうかをまだ自分で決定できない人々に向けられています。

例外

上記は例外のない言語について語っていますが、私はそれを非常に好みます(明示的なエラー処理をはるかに優れた方法で使用でき、驚きも少​​なくなります)。igliを引用するには:

<igli> exceptions: a truly awful implementation of quite a nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)

しかし、例外のある言語でそれをうまく行う方法と、それらをうまく使用したい場合の提案があります:

例外が発生した場合のエラー戻り

Earlyのほとんどをreturn例外をスローするように置き換えることができます。しかし、あなたの通常のプログラムの流れは、つまりプログラムが遭遇しなかったで任意のコードの流れ、よく、例外...エラー条件やsomesuch、してはならない任意の例外を発生させます。

この意味は…

# this page is only available to logged-in users
if not isLoggedIn():
    # this is Python 2.5 style; insert your favourite raise/throw here
    raise "eh?"

…大丈夫ですが、…

/* do not code like this! */
try {
    openFile(xyz, "rw");
} catch (LockedException e) {
    return "file is locked";
}
closeFile(xyz);
return "file is not locked";

…ではありません。基本的に、例外は制御フロー要素ではありません。これにより、Operationsが奇妙に見え(「Java™プログラマーは常にこれらの例外は正常であると言う」)、デバッグを妨げる可能性があります(たとえば、例外を発生させるだけでIDEに指示する)。多くの場合、例外を発生させるには、ランタイム環境でスタックをアンワインドしてトレースバックなどを生成する必要があります。これにはおそらく、さらに多くの理由があります。

つまり、例外をサポートする言語では、既存のロジックとスタイルに一致し、自然に感じるものを使用します。ゼロから何かを書く場合は、早めに合意してください。ライブラリをゼロから作成する場合は、消費者について考えてください。(abort()ライブラリでも使用しないでください...)しかし、あなたがすることは何でも、原則として、操作がその後(通常)継続する場合、例外はスローされません。

一般的なアドバイス 例外

まず、開発チーム全体で合意された例外のすべてのプログラム内使用を取得してください。基本的に、それらを計画します。それらを豊富に使用しないでください。場合によっては、C ++、Java™、Pythonでさえ、エラーが返される方が良い場合があります。時々そうではありません。それらを考えて使用してください。


一般的に、早期のリターンはコードの匂いだと考えています。前提条件が満たされていないために次のコードが失敗する場合、代わりに例外をスローします。Just sayin '
ダンマン

1
@DanMan:私の記事はCを念頭に置いて書かれています...私は通常、例外を使用しません。しかし、この記事を(おっと、かなり長くなった)提案で拡張しました。例外; ちなみに、昨日、社内の
開発者

また、1行のifやforsでもブレースを使用します。goto fail;アイデンティティに別の人を隠したくありません。
ブルーノキム

1
@BrunoKim:これは、使用するプロジェクトのスタイル/コーディング規則に完全に依存します。私はBSDで働いていますが、そこでは眉をひそめています(より多くの視覚的な混乱と垂直方向のスペースの損失)。ただし、$ dayjobで合意したとおりに配置します(初心者の方が難しくなく、エラーの可能性が少ないなど)。
ミラビロス

3

私の意見では、「ガード条件」はコードを読みやすくするための最良かつ最も簡単な方法の1つです。ifメソッドの最初に表示され、elseコードが画面外にあるため表示されない場合、私は本当に嫌いです。見るだけで下にスクロールしなければならないthrow new Exception

コードを読んでいる人がそれを読むためにメソッド全体を飛び越える必要がなく、代わりに常に上から下にスキャンするように、チェックを最初に置きます。


2

(@mirabilosの答えは素晴らしいですが、同じ結論に到達するための質問の考え方は次のとおりです。)

私は自分(または他の誰か)が後で関数のコードを読むことを考えています。最初の行を読んだとき、入力について何も仮定できません(とにかくチェックしないものを除く)。だから私の考えは、「わかりました、私は自分の議論で物事をやろうとしていることを知っています。しかし最初に「それらを片付けましょう」-つまり、彼らが私の好みに合わない制御パスを殺します。」 、私は通常の場合を条件付けられたものとは見ていません;私はそれが正常であることを強調したいと思います。

int foo(int* bar, int baz) {

   if (bar == NULL) /* I don't like you, leave me alone */;
   if (baz < 0) /* go away */;

   /* there, now I can do the work I came into this function to do,
      and I can safely forget about those if's above and make all 
      the assumptions I like. */

   /* etc. */
}

-3

この種の条件の順序は、問題のコードセクションの重要度と、使用できるデフォルトがあるかどうかによって異なります。

言い換えると:

A.クリティカルセクションとデフォルトなし=>早期失敗

B.重要ではないセクションとデフォルト=> else部分でデフォルトを使用

C.中間ケース=>必要に応じてケースごとに決定する


これは単にあなたの意見ですか、それとも何らかの方法で説明/バックアップできますか?
ブヨ

各オプションが(実際には多くの言葉なしで)それが使用される理由を説明しているように、これはどの程度正確にバックアップされていませんか?
ニコスM.

私はこれを言いたくありませんが、(私の)この答えのダウン投票は文脈外です:)。これは質問のOPを使用すると、代わりの答えは全く別の問題がある持っているかどうか、尋ねている
ニコスM.

正直なところ、ここで説明を見ることはできません。たとえば、「クリティカルセクションでデフォルトなし=>早く失敗しない」など、別の意見を書いた場合、この回答は読者が2つの反対意見を選ぶのにどのように役立ちますか。回答方法のガイドラインに合わせて、より良い形状に編集することを検討してください。
ブヨ

[OK]を私は見、これは確かに別の説明かもしれないが、少なくともあなたは、単語「クリティカルセクション」と「いいえデフォルト」を理解しないことができますするための戦略を意味するもので、早期に失敗を最小限1はいえ、これは確かにexpanationある
ニコスM.を
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.