if / returnのベストプラクティス


60

私はif声明を持っているときに戻るより良い方法と考えられるものを知りたいです。

例1:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }
   else
   {
      myString = "Name " + myString;
      // Do something more here...
      return true;
   }
}

例2:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }

   myString = "Name " + myString;
   // Do something more here...
   return true;
}

両方の例でわかるように、関数は戻りますtrue/falseelse、最初の例のようにステートメントを配置することをお勧めしますか、それを配置しない方が良いですか?


7
最初の「if」のエラーのみをチェックする場合は、「else」を含めないほうがよいでしょう。エラーは実際のロジックの一部と考えるべきではないからです。
メルトアカカヤ

2
個人的には、副作用を引き起こす関数についてもっと心配したいのですが、これは単なる選択の悪い例でしょうか?
jk。


14
私には、最初のバージョンは宿題を終え、そしてそれらがなくなってたら、その後、学生たちの残りの部分に言っていなかった部屋にすべての学生を却下ようなものです「あなたがあれば今すぐでした宿題を終えます...」。理にかなっていますが、不要です。条件は実際にはもう条件ではないので、をドロップする傾向がありelseます。
ニコール

1
あなたがしたい「ここでもっと何かをする」とは何ですか?これにより、関数の設計方法が完全に変わる可能性があります。
ベネディクト

回答:


81

例2は、ガードブロックとして知られています。何かが間違っていた場合(パラメーターが正しくないか、無効な状態)に、例外を早期に返す/スローするのに適しています。通常のロジックフローでは、例1を使用することをお勧めします


+1-優れた区別、および私が答えようとしていたこと。
テラスティン

3
@ pR0Psには以下の同じ答えがあり、それが従うべきよりクリーンなパスになる理由についてのコード例を提供します。良い答えのために+1。

この質問はSOで何度も尋ねられてきましたが、議論の余地がありますが、これは良い答えです。最後から帰ってくるように訓練された多くの人々は、この概念にいくつかの問題を抱えています。
ビルK

2
+1。ガードブロックを不適切に回避すると、矢印コードが発生する傾向があります。
ブライアン

どちらの例もガードブロックを示していませんか?
アーニー

44

私の個人的なスタイルはif、ガードブロックにシングルを使用し、実際のメソッド処理コードでif/ を使用することelseです。

この場合、myString == nullガード条件としてを使用しているため、単一のifパターンを使用する傾向があります。

もう少し複雑なコードを考えてみましょう。

例1:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }
    else{
        //processing block
        myString = escapedString(myString);

        if (myString == "foo"){
            //some processing here
            return false;
        }
        else{
            myString = "Name " + myString;
            //other stuff
            return true;
        }
    }
}

例2:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }

    //processing block
    myString = escapedString(myString);

    if (myString == "foo"){
        //some processing here
        return false;
    }
    else{
        myString = "Name " + myString;
        //other stuff
        return true;
    }
}

例1では、ガードとメソッドの残りの両方がif/ else形式になっています。例2と比較してください。例2では、​​ガードブロックは単一のif形式であり、メソッドの残りの部分ではif/ else形式を使用しています。個人的には、例2はわかりやすく、例1は乱雑でインデントが過剰に見えます。

これは不自然な例であり、else ifステートメントを使用してクリーンアップできることに注意してください。ただし、ガードブロックと実際の関数処理コードの違いを示すことを目指しています。

とにかく、まともなコンパイラーは両方に対して同じ出力を生成するはずです。どちらか一方を使用する唯一の理由は、個人的な好み、または既存のコードのスタイルに準拠することです。


18
例2は、他の2つ目を取り除けばさらに読みやすくなります。
Briddums

18

個人的には、2番目の方法を好みます。短く、インデントが少なく、読みやすいように感じます。


インデントを減らすために+1。あなたは複数のチェックを持っている場合、これはobvoious GEST
d.raev

15

私の個人的なプラクティスは次のとおりです。

  • いくつかの出口点を持つ関数は好きではありません。保守や追跡が難しいことがわかりました。本質的に少しずさんなため、コードの変更によって内部ロジックが壊れることがあります。複雑な計算の場合、最初に戻り値を作成し、最後に返します。これにより、各if-else、switchなどのパスを慎重にたどり、適切な場所に値を正しく設定する必要があります。また、デフォルトの戻り値を設定するか、開始時に初期化しないでおくかを決めるのにも少し時間を費やします。このメソッドは、ロジックまたは戻り値のタイプまたは意味が変更された場合にも役立ちます。

例えば:

public bool myFunction()
{
   // First parameter loading
   String myString = getString();

   // Location of "quick exits", see the second example
   // ...

   // declaration of external resources that MUST be released whatever happens
   // ...

   // the return variable (should think about giving it a default value or not) 
   // if you have no default value, declare it final! You will get compiler 
   // error when you try to set it multiple times or leave uninitialized!
   bool didSomething = false;

   try {
     if (myString != null)
     {
       myString = "Name " + myString;
       // Do something more here...

       didSomething = true;
     } else {
       // get other parameters and data
       if ( other conditions apply ) {
         // do something else
         didSomething = true;
       }
     }

     // Edit: previously forgot the most important advantage of this version
     // *** HOUSEKEEPING!!! ***

   } finally {

     // this is the common place to release all resources, reset all state variables

     // Yes, if you use try-finally, you will get here from any internal returns too.
     // As I said, it is only my taste that I like to have one, straightforward path 
     // leading here, and this works even if you don't use the try-finally version.

   }

   return didSomething;
}
  • 唯一の例外は、開始時(または、まれにプロセス内)の「クイック終了」です。実際の計算ロジックが入力パラメーターと内部状態の特定の組み合わせを処理できない場合、またはアルゴリズムを実行せずに簡単な解決策を持っている場合、すべてのコードをブロック(場合によっては)ブロックにカプセル化することは役に立ちません。これは「例外状態」であり、コアロジックの一部ではないため、検出したらすぐに計算から抜け出す必要があります。この場合、elseブランチはありません。通常の状態では、実行は単純に続行されます。(もちろん、「例外状態」は例外をスローすることでより適切に表現されますが、時には過剰になります。)

例えば:

public bool myFunction()
{
   String myString = getString();

   if (null == myString)
   {
     // there is nothing to do if myString is null
     return false;
   } 

   myString = "Name " + myString;
   // Do something more here...

   // not using return value variable now, because the operation is straightforward.
   // if the operation is complex, use the variable as the previous example.

   return true;
}

「1つの出口」ルールは、計算が解放する必要がある外部リソースを必要とする場合、または関数を終了する前にリセットする必要があることを示す場合にも役立ちます。時々開発中に追加されます。アルゴリズム内に複数の出口があると、すべてのブランチを適切に拡張するのがはるかに難しくなります。(例外が発生する可能性がある場合、まれな例外的なケースでの副作用を避けるために、リリース/リセットも最終ブロックに入れる必要があります...)。

あなたのケースは「実際の作業の前に迅速に終了する」カテゴリに分類されるようで、例2バージョンのように記述します。


9
「複数の出口点がある関数が気に入らない」ための+1
Corv1nus

@LorandKedves-例を追加しました-気にしないでください。
マシューフリン

@MatthewFlynnまあ、あなたが私の答えの最後に提案したものと反対であることを気にしない場合;-)例を続けます、私は最終的にそれが私たちの両方にとって良いことを願っています:-)
Lorand Kedves

@MatthewFlynn(申し訳ありませんが、私は不機嫌野郎だ、と私は、現在のバージョンのようにあなたを願っています私の意見を明確にするために私を助けてくれてありがとう。。)
ロラーンドKedves

13
複数の出口点を持つ関数と、可変の結果変数を持つ関数のうち、前者は、2つの悪のうち、はるかに少ないように思えます。
ジョンパーディ

9

次の2つの理由から、できる限りガードブロックを使用することを好みます。

  1. これらは、特定の条件が与えられた場合に迅速に終了できるようにします。
  2. コードの後半にある複雑で不要なifステートメントの必要性を取り除きます。

一般的に言えば、メソッドの中核機能が明確で最小限のメソッドを見ることを好みます。ガードブロックは、これを視覚的に実現するのに役立ちます。


5

「Fall Through」アプローチが好きです。

public bool MyFunction()
{
   string myString = GetString();

   if (myString != null)
   {
     myString = "Name " + myString;
     return true;
    }
    return false;
}

アクションには特定の条件があり、それ以外はデフォルトの「フォールスルー」リターンです。


1

私が単一のif条件を持っている場合、スタイルについて反論するのにあまり時間を費やさないでしょう。しかし、複数のガード条件がある場合、style2を好むでしょう。

これを想像してください。テストが複雑であり、複雑さを避けるために、テストを単一のif-OR条件に結び付けたくないと仮定します。

//Style1
if (this1 != Right)
{ 
    return;
}
else if(this2 != right2)
{
    return;
}
else if(this3 != right2)
{
    return;
}
else
{
    //everything is right
    //do something
    return;
}

//Style 2
if (this1 != Right)
{ 
   return;
}
if(this2 != right2)
{
    return;
}
if(this3 != right2)
{
    return;
}


//everything is right
//do something
return;

ここには2つの主な利点があります

  1. 単一の関数内のコードを2つの視覚的な対数ブロックに分けています。検証の上位ブロック(ガード条件)と実行可能なコードの下位ブロックです。

  2. 1つの条件を追加/削除する必要がある場合、if-elseif-elseラダー全体を台無しにする可能性を減らします。

もう1つの小さな利点は、ケアするブレースのセットが1つ少ないことです。


0

これは、どちらのほうが良いかという問題です。

If condition
  do something
else
  do somethingelse

よりよく自分自身を表現する

if condition
  do something
do somethingelse

小さなメソッドの場合はそれほど違いはありませんが、大きな複合メソッドの場合は適切に分離されないため、理解が難しくなる可能性があります


3
メソッドが長すぎて2番目の形式を理解するのが難しい場合、長すぎます。
ケビンクライン

7
do something返品が含まれる場合、2つのケースは同等です。そうでない場合、2番目のコードは、最初のブロックの動作方法ではない述部にdo somethingelse関係なく実行されifます。
アドナン

また、OPは彼の例で説明していました。ちょうど質問の行に従ってください
ホセヴァレンテ

0

例1がいらいらしているのは、値を返す関数の最後にreturnステートメントがないと、すぐに "Wait、there is some wrong"フラグがトリガーされるためです。したがって、このような場合、例2を使用します。

ただし、通常、エラー処理、ロギングなどの関数の目的に応じて、より複雑になります。そのため、私はこれについてLorand Kedvesの答えに賛成し、通常はコストがかかっても1つの終了ポイントを持ちます。追加のフラグ変数の。ほとんどの場合、メンテナンスとその後の拡張が容易になります。


0

お使いのときif文は必ず返す、関数の残りのコードのために他を使用する理由はありません。そうすると、余分な行とインデントが追加されます。不要なコードを追加すると、読みにくくなります。誰もが知っているように、コードの読み取りは難しい


0

あなたの例では、else明らかに不要ですが、...

コードの行をすばやくスキミングするとき、通常、目はブレースとインデントを見て、コードの流れを理解します。実際にコード自体に落ち着く前に。したがって、を記述しelse、コードの2番目のブロックの周りにブレースとインデントを配置すると、実際には、これが1つのブロックまたは他のブロックが実行される「AまたはB」状況であることがわかります。つまり、読者はreturnイニシャルの後に表示される前にブレースとインデントを表示しifます。

私の意見では、これはコードに冗長なものを追加することで実際に読みやすくなるまれなケースの1つです。


0

関数が何かを返すことがすぐにわかるので、例2をお勧めします。しかし、それ以上に、次のように1つの場所から戻ってきたいと思います。

public bool MyFunction()
{
    bool result = false;

    string myString = GetString();

    if (myString != nil) {
        myString = "Name " + myString;

        result = true;
    }

    return result;
}

このスタイルを使用すると、次のことができます。

  1. 私が何かを返していることをすぐに見てください。

  2. 1つのブレークポイントだけで、関数のすべての呼び出しの結果をキャッチします。


これは、私が問題に取り組む方法に似ています。唯一の違いは、結果変数を使用してmyString評価をキャプチャし、それを戻り値として使用することです。
チャックコンウェイ

@ChuckConway Agreeed、しかし私はOPのプロトタイプに固執しようとしていた。
カレブ

0

私の個人的なスタイルは行く傾向があります

function doQuery(string) {
    if (!query(string)) {
        query("ABORT");
        return false;
    } // else
    if(!anotherquery(string) {
        query("ABORT");
        return false;
    } // else
    return true;
}

コメントアウトされたelseステートメントを使用してプログラムの流れを示し、読みやすくしますが、多くの手順が必要な場合に画面全体に簡単に到達できるような大きなインデントを避けます。


1
個人的に、私はあなたのコードで作業する必要がないことを願っています。
CVn

複数のチェックがある場合、このメソッドは少し長くなる可能性があります。各if句から戻ることは、gotoステートメントを使用してコードのセクションをスキップすることに似ています。
チャックコンウェイ

これと、else句を含むコードのどちらかを選択した場合は、後者も選択します。これは、コンパイラもそれらを事実上無視するためです。それはに依存するであろうけれどもelse if ません一度元以上のインデントを増やすこと-それがあればやった私はあなたに同意するだろう。しかし、elseそのスタイルが許可されている場合、私の選択は完全にドロップすることです。
マークハード

0

私は書くだろう

public bool SetUserId(int id)
{
   // Get user name from id
   string userName = GetNameById(id);

   if (userName != null)
   {
       // update local ID and userName
       _id = id;
       _userNameLabelText = "Name: " + userName;
   }

   return userName != null;
}

私が戻ることが最も明らかであるので、私はこのスタイルを好みuserName != nullます。


1
問題は、なぜあなたはそのスタイルを好むのですか?
カレブ

...率直に言って、なぜこのような場合に文字列が必要なのかわかりません。最後に追加のブールテストを追加する理由は言うまでもありません。
シャドゥール

@Shadur余分なブール値テストと未使用のmyString結果を知っています。OPから関数をコピーするだけです。現実に近いものに変更します。ブールテストは簡単であり、問​​題ではありません。
tia

1
@Shadurここでは最適化を行っていませんよね?私のポイントは、コードを読みやすく保守しやすくすることです。個人的には、1回の参照nullチェックのコストでそれを支払います。
tia

1
@tia私はあなたのコードの精神が好きです。userNameを2回評価する代わりに、変数で最初の評価をキャプチャし、それを返します。
チャックコンウェイ

-1

私の経験則では、elseを使用せずにif-returnを実行するか(ほとんどエラーの場合)、すべての可能性に対して完全なif-else-ifチェーンを実行します。

このようにして、ブランチに凝りすぎないようにします。これを行うと、アプリケーションロジックをifにエンコードし、メンテナンスが難しくなります(たとえば、enum OKまたはERRですべてのifを書く場合!OK <-> ERRという事実を利用して、次に列挙型に3番目のオプションを追加するのはお尻の痛みになります。)

あなたの場合、たとえば、「null / not nullに加えて」「null / not nullに加えて」の3番目の可能性を心配する必要はない可能性が高いため、「nullを返す」が一般的なパターンであることが確実であるため、プレーンifを使用します将来は。

ただし、データに対してより詳細な検査を行うテストである場合、代わりに完全なif-elseに向かってエラーが発生します。


-1

例2には落とし穴がたくさんあると思うので、今後は意図しないコードにつながる可能性があります。ここで最初に焦点を合わせるのは、「myString」変数を取り巻くロジックに基づいています。したがって、明確にするために、条件のすべてのテストは、既知およびデフォルト/不明のロジックを考慮したコードブロックで行う必要があります

後のコードが、出力を大幅に変更する例2に意図せずに導入された場合:

   if (myString == null)
   {
      return false;
   }

   //add some v1 update code here...
   myString = "And the winner is: ";
   //add some v2 update code here...
   //buried in v2 updates the following line was added
   myString = null;
   //add some v3 update code here...
   //Well technically this should not be hit because myString = null
   //but we already passed that logic
   myString = "Name " + myString;
   // Do something more here...
   return true;

elsenullのチェック直後のブロックでは、将来のバージョンに拡張機能を追加したプログラマーがすべてのロジックを一緒に追加するようになったと思います。これは、元のルールに対して意図されていないロジックの文字列があるためです(値がヌル)。

私はこれを、CodeplexのC#ガイドライン(ここへのリンク:http ://csharpguidelines.codeplex.com/)のいくつかに強く信じています。

「デフォルトブロック(他)が空であると想定される場合は、説明的なコメントを追加します。さらに、そのブロックに到達しないと想定される場合、InvalidOperationExceptionをスローして既存のケースで発生する可能性がある将来の変更を検出します。コードが移動できるすべてのパスが考慮されています。」

このようなロジックブロックを使用して、デフォルトブロック(if-else、case:default)を常に追加して、すべてのコードパスを明示的に考慮し、コードが意図しないロジック結果に開かれたままにしないようにする場合、プログラミングを実践することをお勧めします。


例2のelseがすべてのケースを考慮しないのはどうしてですか?意図した結果は継続して実行されます。この場合、else句を追加しても、メソッドの複雑さが増すだけです。
チャックコンウェイ

問題のすべてのロジックを簡潔かつ決定論的なブロックに含めることができないことに基づいて、私は同意しません。焦点はmyString値の操作にあり、ifブロックに続くコードは、ストリング!= nullの場合の結果のロジックです。したがって、その特定の値を操作する追加のロジックに焦点を当てているため、elseブロックにカプセル化する必要があると思います。そうしないと、意図せずにロジックを分離し、意図しない結果をもたらす可能性が生じます。常に起こるとは限りませんが、これは本当にベストプラクティスの意見スタイルの質問でした。
atconway
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.