web-dev-qa-db-ja.com

関数から早期に戻るか、ifステートメントを使用する必要がありますか?

私はよくこの種の関数を両方の形式で記述しましたが、ある形式が別の形式よりも優先されるのか、そしてなぜなのかと思っていました。

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

または

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

    // Do Something
}

私は通常、最初のコードを使用してコード化します。それはコード化中の私の頭の働きですが、2番目のコードはエラー処理をすぐに処理し、読みやすくなるので、2番目のコードを好むと思います。

302
Rachel

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

400
Mason Wheeler

間違いなく後者です。前者は今のところ見た目は悪くありませんが、より複雑なコードを入手した場合、誰もがこれを考えているとは思えません。

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;
}

私は、単一の出口点の利点を理解したことがないことを完全に認めます。

169
Jason Viers

状況によって異なります-一般的に、関数を早期に中断するためにコードの束を移動しようとすることはありません。通常、コンパイラーが私に代わって処理します。とは言っても、上部に必要な基本的なパラメーターがあり、それ以外の場合は続行できない場合は、早めにブレークアウトします。同様に、条件が関数内に巨大な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;
}
32
rjzii

私は早期復帰を好む。

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

1つのエントリと複数のエントリが存在する場合、結果が得られたときに戻り、それを最後まで追跡して、他の誰もそれに対して何も実行していないことを確認しないでください(戻った後は他に何もないため)。それは、メソッド本体をより多くのステップに分割するようなもので、各ステップで結果を返したり、次のステップに運を試させたりする可能性があります。

24
user7197

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

C++プログラミングでは、デストラクタがあり、スコープ出口ガードさえあります。コードが最初から例外セーフであることを保証するために、これらすべてがここにある必要があります。そのため、コードは早期終了から十分に保護されているため、論理的な欠点はなく、純粋にスタイルの問題です。

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

C#確かに答えることはできません。

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

もちろん、関数はそもそもそれほど長くはならないはずです。巨大なswitchステートメントがある場合、コードもおそらく因数分解されます。

13
CashCow

両方を使用しています

DoSomethingが3〜5行のコードの場合、最初のフォーマット方法を使用すると、コードは美しく見えます。

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

9
azheglov

早い者勝ち。それらは醜く見えるかもしれませんが、特にチェックする複数の条件がある場合は、大きなifラッパーよりも醜くありません。

9
STW

単一エントリー単一出口の古典的な理由は、それ以外の場合、正式なセマンティクスが他の方法では言い表せないほど醜くなることです(GOTOが有害と見なされたのと同じ理由)。

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

通常、私は早期返品アプローチを最小限に抑えます。

8
Paul Nathan

個人的には、最初に合否チェックをするのが好きです。これにより、関数の上部にある最も一般的なエラーのほとんどを、その後に続くロジックとともにグループ化できます。

7
nathan

場合によります。

関数の残りの部分を実行しても意味がない、すぐに確認するいくつかの明らかな行き止まりの状態がある場合は、早期に戻ります。*

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

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

6
Bobby Tables

Ifを使用

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

上記のアドバイスに従えば、そのリターンを関数の一番下に置く必要があります。その場合、それをreturnと呼ぶことすらせず、エラーコードを設定して2行返すだけです。これにより、1エントリ1出口の理想を実現します。

Delphi固有...

証明はありませんが、これはDelphiプログラマにとって優れたプログラミング手法であると思います。 D2009より前の場合、値を返すアトミックな方法はありません。exit;およびresult := foo;または、例外をスローすることもできます。

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

if (true) {
 return foo;
} 

for

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

あなたはそれをあなたのすべての機能の一番上で見ることにうんざりして、好むかもしれません

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

exitを完全に避けてください。

3
Peter Turner

次の声明に同意します。

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

stackoverflowのこの質問 から取得。

2
Toto

私は書きたいと思います:

if(someCondition)
{
    SomeFunction();
}
1
Josh K

あなたのように、私は通常最初のものを書きますが、最後のものを好みます。ネストされたチェックがたくさんある場合、通常は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
1
jolt

私は最近のリターンのほとんどすべてを極端に使用しています。私はこれを書きます

self = [super init];

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

return self;

なので

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

// your code here

return self;

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

1
Dan Rosenstark

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

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

0
Ming-Tang