関数から早く戻るか、ifステートメントを使用する必要がありますか?[閉まっている]


304

私はよくこの種の関数を両方の形式で記述しましたが、ある形式が別の形式よりも好まれるのか、そしてその理由は疑問でした。

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

または

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

それはコーディング中の私の脳の働きであるため、私は通常最初のものでコーディングしますが、エラー処理をすぐに処理し、読みやすいので2番目のものを好むと思います


9
私はこの議論に少し遅れているので、これを答えに入れません。また、2年前にこのことを考えました:lecterror.com/articles/view/code-formatting-and-readability 2番目の方が読みやすく、変更しやすく、保守しやすく、デバッグしやすいと思います。しかし、それは私だけかもしれません:)
ハンニバルレクター博士


4
今、この質問は意見ベースの質問の良い例です
ルドルフオラ

2
それで、1つまたは別の方向に絶対的な証拠がない場合はどうなりますか?十分な議論が1つの方向と別の方向に提供され、答えが正しければ投票が行われると、非常に有用になります。このような最後の質問は、このサイトの価値に有害であると思います。
gsf

9
そして、意見に基づく質問と回答が好きです。彼らは、多数派が好むものを教えてくれるので、他の人が読むためのコードを書くことができます。
ジギマンタス

回答:


402

私は2番目のスタイルが好きです。最初に無効なケースを取得し、必要に応じて単に終了するか例外を発生させ、そこに空白行を入れてから、メソッドの「実際の」本体を追加します。読みやすいと思います。


7
Smalltalkでは、これらを「保護条項」と呼びます。少なくともそれは、Kent BeckがSmalltalkベストプラクティスパターンでそれらを呼ぶものです。それが一般的な用語かどうかはわかりません。
フランクシェラー

154
早期に終了すると、限られたメンタルスタックからものをポップすることができます。:)
ジョレン

50
これは「バウンサーパターン」と呼ばれることを以前に聞いたことがあります。ドアに入る前に悪いケースを取り除きます。
RevBingo

14
私はこれまでのところ、これが間違いなく唯一の正しいことだと言っていました。
オリバーワイラー

38
さらに、最初に境界ケースを取り除くと、インデントを追加し続けません。
-doppelgreener

170

間違いなく後者。前者は今のところ悪く見えませんが、より複雑なコードを取得するとき、誰もこれを考えるとは想像できません:

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

より読みやすい

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

単一の出口点の利点を理解したことはありません。


20
単一の出口点の利点は...単一の出口点があることです!あなたの例では、戻ることができるいくつかのポイントがあります。より複雑な関数を使用すると、戻り値の形式が変更されると、それがハント出口ポイントに変わる可能性があります。もちろん、単一の終了ポイントを強制しても意味がない場合があります。
JohnL

71
@JohnLの大きな関数が問題であり、複数の出口点ではありません。あなたは追加の関数呼び出しが...もちろん、非常にあなたのコードを遅くするコンテキストで作業している場合を除き
ダンRosenstark

4
@Yar:本当ですが、ポイントは立っています。私は誰かを変えようとするつもりはありません。出口ポイントをできるだけ少なくすることの利点を指摘していました(そして、ジェイソンの例はとにかくストローマンのようなものでした)。次回、SomeFunctionが奇数の値を返す理由を見つけようとして髪を引っ張っているとき、戻りの直前にロギングコールを追加したいと思うかもしれません。バグが1つしかない場合は、デバッグがはるかに簡単になります。:)
JohnL

76
@Jason Viersまるで1人でもreturn value;助けられるように!?!次にvalue = ...、この割り当てと最終リターンの間で値が変更されないことを決して確信できないという不利な点で、半ダースを狩ります。少なくともすぐに戻ってくると、結果が変更されないことが明らかです。
シェード

5
@Sjoed:ここで2番目のSjoerd。結果を記録する場合は、呼び出し元のサイトで記録できます。理由を知りたい場合は、各出口点/割り当てでログを記録する必要があるため、この点で両方が同一になります。
マチューM.

32

それは依存します -一般に、私は早く関数の外に抜け出すためにたくさんのコードを移動しようとするつもりはありません-コンパイラは通常私のためにそれを処理します。ただし、必要な基本パラメーターが上部にあり、それ以外の場合は続行できない場合は、早めにブレークアウトします。同様に、if機能が機能で巨大なブロックを生成する場合、その結果として早期にブレークアウトします。

ただし、関数が呼び出されたときにデータが必要な場合、通常は単に返すのではなく、例外をスローします(例を参照)。

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}

9
そして、最終結果が維持可能である場合、誰がどのスタイルが選択されたかを気にしますか?
ジェフシヴァー

3
@Jeff Siver-したがって、これが「聖戦」スタイルの質問になる傾向があるのは、結局のところ、個人的な好みと、内部スタイルガイドが言うものに帰着します。
rjzii

1
ここで重要なことは、彼が早く戻るのではなく、例外を投げていることです。戻り値は、妥当性チェックのために再利用しないでください。さまざまな条件があり、メソッドを使用するコードに失敗の理由を知らせたい場合はどうでしょうか?突然、1つのメソッドから実際のビジネスデータ、何も返さない(空の結果)、またはさまざまな文字列、コード、数値などが返される可能性があります。失敗した理由を説明するだけです。結構です。
ダンマン

私のコンパイラが示唆するような循環的複雑度はどうですか?コードを入れ子にしないほうがいいのではないでしょうか?
l --''''''--------- '' '' '' '' '' ''

24

私は早期復帰を好む。

1つのエントリポイントと1つの出口ポイントがある場合は、常に頭のコード全体を出口ポイントまで追跡する必要があります(以下のコードの一部が結果に対して他の処理を行うかどうかはわかりませんので、存在するまで追跡する必要があります)。どのブランチが最終結果を決定するかは関係ありません。これに従うのは難しいです。

1つのエントリと複数のエントリが存在する場合、結果が得られたときに戻りますが、最後まで追跡してわざわざ他の操作を行っていないことを確認します(返されてから他に何も行われないため)。メソッド本体を複数のステップに分割するようなもので、各ステップで結果を返すか、次のステップで運を試すことができます。


13

手動でクリーンアップする必要があるCプログラミングでは、ワンポイントリターンについて多くのことが言われます。今すぐ何かをクリーンアップする必要がない場合でも、誰かがあなたの関数を編集し、何かを割り当て、戻る前にクリーンアップする必要があるかもしれません。それが起こると、すべてのreturnステートメントを調べる悪夢のような仕事になります。

C ++プログラミングでは、デストラクタがあり、スコープ出口ガードさえあります。コードがそもそも例外安全であることを保証するためにこれらすべてがここにある必要があるので、コードは早期終了に対して十分に保護されているため、そうすることは論理的なマイナス面を持たず、純粋にスタイルの問題です。

「最終的に」ブロックコードが呼び出されるかどうか、ファイナライザーが何かを確実に行う必要がある状況に対処できるかどうか、Javaについて十分な知識がありません。

C#私は確かに答えられません。

D言語は、適切な組み込みのスコープ終了ガードを提供するため、早期終了に向けて十分に準備されているため、スタイル以外の問題は発生しません。

もちろん、関数はそもそもそれほど長くないはずです。また、巨大なswitchステートメントがある場合、コードもおそらく不適切にファクタリングされます。


1
C ++では、戻り値の最適化と呼ばれるものを使用できます。これにより、値を返すときに通常発生するコピー操作をコンパイラが本質的に省略することができます。ただし、困難なさまざまな条件下では、複数の戻り値がそのようなケースの1つです。つまり、C ++で複数の戻り値を使用すると、実際にコードが遅くなる可能性があります。これは確かにMSCに当てはまり、複数の可能な戻り値を使用してRVOを実装することは(不可能ではないにしても)非常に難しいことを考えると、すべてのコンパイラで問題になる可能性があります。
イーモンネルボンヌ

1
Cでは、単に使用gotoし、場合によっては2ポイントリターンを使用します。例(コメントではコードの書式設定はできません):foo() { init(); if (bad) goto err; bar(); if (bad) goto err; baz(); return 0; err: cleanup(); return 1; }
ミラビロス

1
gotoではなく、「メソッドの抽出」を好みます。クリーンアップコードが常に呼び出されるようにするために、戻り値変数またはgotoを実装する必要があると思われる場合、それを複数の関数に分割する必要があるにおいです。これにより、クリーンアップコードを常に実行しながら、Guard句やその他の早期復帰テクニックを使用して複雑な条件付きコードを簡素化できます。
BrandonLWhite

「誰かがあなたの機能を編集するかもしれません」-これは意思決定のためのそのようなばかげた説明です。将来は誰でも何でもできます。誰かが将来物を壊さないようにするために、今日何か特別なことをすべきだという意味ではありません。
ビクターYarema

コードを保守可能にするために呼び出されます。現実の世界では、コードはビジネス目的で記述されており、開発者が後で変更する必要がある場合もあります。私は、キャッシングを導入し、それが本当に++現代のCに適切な書き換えを必要としていることスパゲッティ混乱リコールするCURLパッチを当てていたのに当時、私はその答えを書いた
CashCow

9

勝利のための早期リターン。ugいように見えるかもしれませんがif、特に複数の条件をチェックする必要がある場合は、大きなラッパーほどmuchくありません。


9

私は両方を使用します。

場合はDoSomething、コードの3-5行で、コードは、単に最初のフォーマット方法を使用して美しく見えます。

しかし、それよりも多くの行がある場合は、2番目の形式を好みます。開始ブラケットと終了ブラケットが同じ画面上にない場合は気に入らない。


2
まさか美しい!インデントが多すぎる!
ジミーケイン

@JimmyKane 3〜5行で発生するインデントは非常に多く、特にレベルごとに2(?)行が必要なため、残りはインデントされます:制御構造とブロックの開始、ブロックの終了に1つ
デュプリケータ

8

single-entry-single-exitの古典的な理由は、そうでなければ正式なセマンティクスが口に出せないいものになることです(GOTOが有害と見なされた同じ理由)。

別の言い方をすれば、戻りが1つしかない場合にソフトウェアがいつルーチンを終了するかを推論するのは簡単です。これは例外に対する議論でもあります。

通常、アーリーリターンアプローチを最小限に抑えます。


ただし、正式な分析ツールの場合、ツールが必要とするセマンティクスを使用して外部関数を合成し、コードを人間が読める状態に保つことができます。
ティムウィリスクロフト

@ティム。それは、あなたがどれだけのことをしたいのか、そして何を分析しているのかに依存します。コーダーが物事について正気であれば、SESEはかなり読みやすいと思います。
ポールネイサン

分析に対する私の姿勢は、Selfプロジェクトの最適化によって形作られています。動的メソッド呼び出しの99%は、静的に解決および削除できます。そのため、かなり直線的なコードを分析できます。働くプログラマーとして、私が作業するコードの大部分は平均的なプログラマーによって書かれたものであると確信できます。したがって、彼らは非常に良いコードを書かないでしょう。
ティムウィリスクロフト

@Tim:結構です。静的言語にはまだ多くの遊びがあることを指摘しなければなりませんが。
ポールネイサン

例外に直面しない理由。これが、単一出口がCで優れているのに、C ++では何も買わない理由です。
ピーターチェン

7

個人的には、最初に合格/不合格の条件チェックを行うことを好みます。これにより、関数の最上部で最も一般的な障害のほとんどを、残りのロジックでグループ化できます。


6

場合によります。

すぐに確認する明らかな行き止まり状態がある場合、関数の残りの部分を無意味に実行することになる早期復帰。*

関数がより複雑で、そうでなければ複数の出口点がある可能性がある場合は、Retval +シングルリターンを設定します(読みやすさの問題)。

* これは多くの場合、設計上の問題を示している可能性があります。残りのコードを実行する前に、多くのメソッドで外部/パラメーターの状態などを確認する必要がある場合は、おそらく呼び出し側で処理する必要があります。


6
共有される可能性のあるコードを書くとき、私のスローガンは「何も想定しない。誰も信頼しない」です。入力と依存する外部状態を常に検証する必要があります。誰かがあなたに悪いデータを与えたので、おそらく何かを破壊するよりも例外を投げる方が良い。
TMN

@TMN:良い点。
ボビーテーブル

2
ここで重要な点は、OO ではリターンではなく例外投げるということです。複数のリターンが悪い場合があり、複数の例外のスローは必ずしもコードの匂いではありません。
マイケルK

2
@MichaelK:事後条件が満たされない場合、メソッドは例外を生成する必要があります。場合によっては、関数が開始する前でも事後条件が達成されているため、メソッドは早期に終了する必要があります。たとえば、コントロールのラベルをに変更するために「コントロールラベルの設定」メソッドを呼び出すFredと、ウィンドウのラベルは既にFredであり、コントロールの名前を現在の状態に設定すると、強制的に再描画が行われます手元に迷惑をかける)、古い名前と新しい名前が一致する場合、set-nameメソッドを早期に終了することは完全に合理的です。
-supercat

3

Ifを使用する

GOTOについてのDon Knuthの本では、ifステートメントで最も可能性の高い状態が常に最初に来る理由を説明しました。これはまだ合理的なアイデアであるという仮定の下で(そして時代の速度を純粋に考慮していないものではありません)。コードが失敗しないよりも失敗する可能性が高い場合を除いて、エラー処理に使用されることが多いという事実を考慮すると、早期リターンは良いプログラミング手法ではないと思います:-)

上記のアドバイスに従う場合、その戻り値を関数の下部に配置する必要があります。そして、そこに戻り値を呼び出さずに、エラーコードを設定して2行返すだけですみます。それにより、1エントリ1出口の理想を達成します。

Delphi固有...

Delphiプログラマにとってこれは良いプログラミング手法であると思いますが、証拠はありません。前D2009、我々は値を返すために、原子方法を持っていない、我々は持っているexit;result := foo;か、私達はちょうど例外を投げることができました。

代用しなければならなかった場合

if (true) {
 return foo;
} 

ために

if true then 
begin
  result := foo; 
  exit; 
end;

あなたはあなたの機能のすべての1つでそれを好むのにうんざりするかもしれません

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

まったく避けexitてください。


2
新しいDelphisでは、if true then Exit(foo);最初またはそれぞれに初期化resultするテクニックを頻繁に使用してから、すべてのエラー条件をチェックし、1つが満たされている場合に短縮することができます。成功事例は、(通常)メソッドの最後のどこかに設定されます。nilFALSEExit;result
JensG

ええ、私はその新しい機能が気に入っていますが、Javaプログラマーをなだめるのはお菓子のように思えますが、次に知っているのは、プロシージャ内で変数を定義できるようにすることです。
ピーターターナー

そして、C#プログラマー。はい、正直に言うと、宣言と使用の間の行数を減らすので、本当に便利だと思います(IIRCには何らかのメトリックもあり、名前を忘れました)。
JensG

2

次の声明に同意します。

私は個人的にガード句のファンです(2番目の例)。関数のインデントを減らすからです。関数から複数のリターンポイントが発生するため、それらを好まない人もいますが、それらの方が明確だと思います。

stackoverflowのこの質問から取られました。


+1ガード条項。また、ポジティブ指向のガードを好む:if(condition = false)if(!condition)returnではなくif(condition = false)return
JustinC

1

私は最近、極端に、早期のリターンをほぼ独占的に使用しています。これを書く

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

なので

self = [super init];
if (!self)
    return;

// your code here

return self;

しかし、それは本当に重要ではありません。関数に1つまたは2つ以上のレベルのネストがある場合、それらを無効にする必要があります。


同意する。インデントは、読み取りで問題を引き起こすものです。インデントが少ないほど良い。あなたの簡単な例は同じレベルのインデントを持っていますが、その最初の例は確かに、より多くの脳力を必要とするより多くのインデントに成長します。
-user441521

1

私は書くことを好む:

if(someCondition)
{
    SomeFunction();
}

2
うわー。それは事前検証ですか?または、検証専用の方法DoSomeFunctionIfSomeConditionですか?
STW

これにより、懸念事項がどのように分離されますか?
justkt

2
関数の実装(依存関係ロジック)を外部にすることで、カプセル化に違反しています。
TMN

1
これがパブリックメソッドで実行され、SomeFunction()が同じクラスのプライベートメソッドである場合は、大丈夫です。しかし、誰かがSomeFunction()を呼び出している場合、そこでチェックを複製する必要があります。各メソッドがその作業を行うために必要なものを処理する方が良いと思います。他の誰もそれを知る必要はありません。
Per Wiklander

2
これは間違いなく、Robert C. Martinが「Clean Code」で提案しているスタイルです。関数は1つのことだけを行う必要があります。ただし、「実装パターン」では、OPで提案されている2つのオプションのうち、2番目の方が優れているとKent Beckが示唆しています。
スコットホイットロック

1

あなたのように、私は通常最初のものを書きますが、最後のものを好みます。ネストされたチェックがたくさんある場合、通常は2番目の方法にリファクタリングします。

エラー処理がチェックからどのように移動するのが好きではありません。

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

私はこれが好きです:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something

0

上部の条件は「前提条件」と呼ばれます。を置くとif(!precond) return;、すべての前提条件が視覚的にリストされます。

大きな「if-else」ブロックを使用すると、インデントのオーバーヘッドが増加する可能性があります(3レベルのインデントに関する引用を忘れていました)。


2
何?Javaで早期に戻ることはできませんか?C#、VB(.NETおよび6)、および明らかにJava(私はそうでしたが、15年以内にこの言語を使用していなかったので調べなければなりませんでした)はすべて、早期のリターンを許可します。そのため、「強く型付けされた言語」が機能を備えていないと非難しないでください。stackoverflow.com/questions/884429/…–
ps2goat

-1

if文を小さくすることを好みます。

したがって、次のいずれかを選択します。

if condition:
   line1
   line2
    ...
   line-n

そして

if not condition: return

line1
line2
 ...
line-n

あなたが「早期返還」として説明したものを選択します。

気を付けてください、私は早期の返品など何も気にしません。コードを単純化したり、ifステートメントの本体を短くしたりしたいのです。

ifとforとwhileがひどい場合は入れ子にしてください。


-2

他の人が言うように、それは依存します。値を返す小さな関数の場合は、早期の戻り値をコーディングできます。しかし、サイズの大きい関数の場合、コードが戻る前に実行されるものを置くことができるとわかっている場所にコードを配置するのが好きです。


-2

私は機能レベルでフェイルファストを練習しています。コードの一貫性とクリーンさを維持します(私と一緒に仕事をした人にとって)。そのため、私はいつも早く帰ります。

いくつかの頻繁にチェックされる条件では、AOPを使用する場合、それらのチェックの側面を実装できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.