web-dev-qa-db-ja.com

「if(...)return ...;」です。 「else」なしで良いスタイルと見なされますか?

このコード:

if( someCondition )
    return doSomething();

return doSomethingElse();

対このコード:

if( someCondition )
    return doSomething();
else
    return doSomethingElse();

本質的には同じですが、最高のスタイル/パフォーマンス/ ...(もちろん答えに意見のない部分がある場合)は何ですか?また、複数の「ifelse」の場合を考えてみましょう。

if( someCondition )
    return doSomething();
else if( someOtherCondition )
    return doSomethingDifferently();
//...
else
    return doSomethingElse();

ありがとう!

19
rubenvb

関数内に複数のreturnステートメントがある場合、これは「早期リターン」と呼ばれます。 Google検索 「早期返品」を行うと、リンクの後に悪いと書かれたリンクが見つかります。

私はナンセンスと言います。

2つの主な理由と1つの副次的な理由があり、人々は早期の返品が悪いと主張しています。私はそれらを通り抜けて、順番に反論します。これが私の意見であり、最終的には自分で決める必要があることを覚えておいてください。

1)理由:返品が早いと、片付けが難しくなります。

反論:それが [〜#〜] raii [〜#〜] の目的です。適切に設計されたプログラムは、実行がスコープを早期に離れた場合にそれらのリソースがリークするような方法でリソースを割り当てません。これを行う代わりに:

.。

_int foo()
{
  MyComplexDevice* my_device = new MyComplexDevice;
  // ...
  if( something_bad_hapened )
    return 0;
  // ...
  delete my_device;
  return 42;
}
_

これをして:

_int foo()
{
  std::auto_ptr<MyComplexDevice> my_device(new MyComplexDevice);
  if( something_bad_hapened )
    return 0;
  // ...
  return 42;
} 
_

また、早期返品によってリソースリークが発生することはありません。ほとんどの場合、配列または文字列を作成するため、_auto_ptr_を使用する必要はありません。場合によっては、vectorstringなどを使用します。 。

例外の可能性があるため、堅牢性のためにとにかくこのようにコードを設計する必要があります。例外は、明示的なreturnステートメントのような早期リターンの形式であり、それらを処理する準備をする必要があります。 foo()で例外を処理することはできませんが、foo()は関係なくリークしないはずです。

2)理由:早期返品はコードをより複雑にします。反論:早期の返品により、実際にはコードが単純になります。

機能には1つの責任があるべきであるというのが一般的な哲学です。私はそれに同意します。しかし、人々はこれを行き過ぎて、関数に複数のリターンがある場合、それは複数の責任を負わなければならないと結論付けています。 (関数は50行を超えてはならない、または他の任意の数であってはならないということでこれを拡張します。)私はいいえと言います。関数の責任が1つしかないからといって、その責任を果たすためにやることがあまりないというわけではありません。

たとえば、データベースを開きます。それは1つの責任ですが、多くのステップで構成されており、それぞれがうまくいかない可能性があります。接続を開きます。ログインする。接続オブジェクトを取得して返します。 3つのステップ。それぞれが失敗する可能性があります。これを3つのサブステップに分割することもできますが、その代わりに次のようなコードを使用します。

_int foo()
{ 
  DatabaseObject db = OpenDatabase(...);
}
_

あなたは持っていることになります:

_int foo()
{
  Connection conn = Connect(...);
  bool login = Login(...);
  DBObj db = GetDBObj(conn);
}
_

つまり、想定される複数の責任をコールスタックの上位に移動しただけです。

3)理由:複数のリターンポイントはオブジェクト指向ではありません。反論:これは、「理由はよくわかりませんが、複数の返品は悪いと誰もが言っている」という言い方です。

別の言い方をすれば、これは実際には、オブジェクトの形をしたボックスにすべてが属していない場合でも、そこにすべてを詰め込もうとする試みにすぎません。確かに、接続はオブジェクトである可能性があります。しかし、ログインですか?ログイン試行は(IMO)オブジェクトではありません。その操作。またはアルゴリズム。このアルゴリズムをオブジェクト型のボックスに詰め込もうとすると、OOPでの不必要な試みになり、コードがより複雑になり、保守が難しくなり、場合によってはさらに効率が低下します。

44
John Dibling

関数は常にできるだけ早く戻る必要があります。これにより、不要なステートメントのセマンティックオーバーヘッドが節約されます。できるだけ早く戻る関数は、最高の明快さと最もクリーンで保守しやすいソースコードを提供します。

SESEスタイルのコードは、割り当てたすべてのリソースを解放するために手動で作成する必要がある場合に適していました。また、いくつかの異なる場所ですべてのリソースを解放することを覚えておくのは無駄でした。しかし、RAIIができたので、間違いなく冗長です。

8
Puppy

それは純粋に個人的な好みやコーディング標準の問題です。個人的に、私は3番目のバリアントを好みます:

return someCondition ? doSomething() : doSomethingElse();
3
fredoverflow

状況によりますが、FredOverflowと同じものを好みます

return someCondition ? doSomething() : doSomethingElse();

これで十分な場合。そうでない場合は、同様の状況が発生するのではないかと思います。コードが長い場合は、30〜40行以上としましょう。ある場所にreturn;を配置する必要がありますが、それは必要ありません。たとえば、次の状況について考えてみます。

if( cond1 )
{
    if( cond2 )
    {
         // do stuff
    }
    else if( cond3 )
    {
        // ..
    }
} 
else if( cond4 )
{
    // ..
}
else
{
    //..
}

私が疑問に思ったのは、-それぞれの場合の最後にreturn;(void関数に)を入れるべきか-これは良いコーディングスタイルか悪いコーディングスタイルか(return;があるかどうかは関係ないため) 。最後に、このコードを後で読む開発者が、これが最終状態であり、この関数でこれ以上何もする必要がないことを知ったので、それを置くことにしました。この場合にのみ興味がある場合は、残りのコードを読まないでください。

3
Kiril Kirov