302

たとえば、自分のコードでReSharperを実行すると、次のようになります。

    if (some condition)
    {
        Some code...            
    }

ReSharper は上記の警告 (「if」ステートメントを反転してネストを減らす) を表示し、次の修正を提案しました。

   if (!some condition) return;
   Some code...

なぜそれが良いのかを理解したいと思います。「goto」のように、メソッドの途中で「return」を使用するのは問題があるといつも思っていました。

4

25 に答える 25

349

これは美的だけでなく、メソッド内の最大ネスト レベルも減らします。メソッドを理解しやすくするため、これは一般的にプラスと見なされます (実際、多くの 静的 解析 ツールは、コード品質の指標の 1 つとしてこれを測定します)。

一方で、メソッドに複数の出口点を持たせることもできます。これは、別のグループの人々が信じていることではありません。

個人的には、ReSharper と最初のグループに同意します (例外のある言語では、「複数の出口点」について議論するのはばかげていると思います。ほとんど何でもスローできるため、すべてのメソッドに多数の潜在的な出口点があります)。

パフォーマンスに関して:両方のバージョンは、すべての言語で同等である必要があります(ILレベルでない場合は、コードでジッターが発生した後は確実です)。理論的には、これはコンパイラに依存しますが、今日広く使用されているほとんどのコンパイラは、これよりもはるかに高度なコード最適化のケースを処理できます。

于 2011-12-08T20:34:26.280 に答える
321

メソッドの途中で戻ることは必ずしも悪いことではありません。コードの意図がより明確になるのであれば、すぐに戻った方がよいかもしれません。例えば:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

この場合、_isDeadが true であれば、すぐにメソッドから抜け出すことができます。代わりに、次のように構成する方がよい場合があります。

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

このコードはリファクタリング カタログから選びました。この特定のリファクタリングは次のように呼ばれます: ネストされた条件をガード句に置き換えます。

于 2008-11-06T10:11:23.747 に答える
104

これは少し宗教的な議論ですが、ネストを減らすべきだという ReSharper の意見には同意します。これは、関数からの複数のリターン パスを持つことのマイナス面を上回ると思います。

入れ子を少なくする主な理由は、コードの読みやすさと保守性を向上させるためです。将来、他の多くの開発者があなたのコードを読む必要があることを覚えておいてください。一般に、インデントが少ないコードははるかに読みやすいです。

前提条件は、関数の開始時に早く戻っても問題ない良い例です。関数の残りの部分の可読性が、前提条件チェックの存在によって影響を受けるのはなぜですか?

メソッドから複数回戻ることのマイナス点については、デバッガーは現在非常に強力であり、特定の関数がいつどこで戻っているかを正確に見つけるのは非常に簡単です。

関数に複数の戻り値があることは、保守プログラマーの仕事に影響を与えません。

コードの可読性が低下します。

于 2008-11-06T10:01:21.960 に答える
70

他の人が述べたように、パフォーマンスに影響を与えるべきではありませんが、他にも考慮すべき点があります。これらの正当な懸念は別として、これにより、状況によっては問題が発生する可能性もあります。double代わりに a を扱っていたとします。

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

一見同等の反転とは対照的です。

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

そのため、特定の状況では、正しく反転されているように見えるものが正しく反転されてifいない場合があります。

于 2011-12-08T21:52:54.150 に答える
54

関数の最後でのみ戻るという考えは、言語が例外をサポートする前の時代から戻ってきました。これにより、プログラムはメソッドの最後にクリーンアップ コードを配置できることに依存できるようになり、それが呼び出され、他のプログラマーがクリーンアップ コードをスキップする原因となったメソッド内のリターンを隠蔽しないことを確認できます。 . クリーンアップ コードをスキップすると、メモリまたはリソースのリークが発生する可能性がありました。

ただし、例外をサポートする言語では、そのような保証はありません。例外をサポートする言語では、ステートメントまたは式を実行すると、メソッドを終了させる制御フローが発生する可能性があります。これは、finally または using キーワードを使用してクリーンアップを行う必要があることを意味します。

とにかく、多くの人が「メソッドの最後でのみ返す」というガイドラインを引用していると思いますが、それがなぜこれまでに良いことだったのかを理解せずに引用していると思います.

于 2008-11-06T10:16:43.457 に答える
34

逆にされたifの名前があることを付け加えたいと思います-GuardClause。できる限り使っています。

私は、最初に2つのコード画面があり、それ以外がない場合にコードを読むのが嫌いです。ifを反転して戻ります。そうすれば、誰もスクロールに時間を浪費することはありません。

http://c2.com/cgi/wiki?GuardClause

于 2011-12-13T17:12:41.393 に答える
22

美観に影響を与えるだけでなく、コードのネストも防ぎます。

実際には、データが有効であることを確認するための前提条件としても機能します。

于 2011-12-08T20:36:05.670 に答える
18

これはもちろん主観的なものですが、次の 2 つの点で大幅に改善されていると思います。

  • 保持されている場合、関数が何もする必要がないことがすぐにわかりますcondition
  • ネスティングレベルを低く保ちます。ネスティングは、あなたが思っている以上に可読性を損ないます。
于 2008-11-06T10:05:38.080 に答える
15

複数のリターン ポイントは、各リターン ポイントの前にクリーンアップ コードを複製する必要があるため、C (および程度は低いが C++) では問題でした。ガベージ コレクションでは、try| finallyコンストラクトとusingブロックの両方を恐れる必要はまったくありません。

最終的には、あなたとあなたの同僚が読みやすいと思うものに帰着します。

于 2008-11-06T10:23:13.943 に答える
12

パフォーマンスに関しては、2 つのアプローチに顕著な違いはありません。

しかし、コーディングはパフォーマンス以上のものです。明快さと保守性も非常に重要です。そして、パフォーマンスに影響を与えないこのような場合、重要なのはそれだけです。

どちらのアプローチが好ましいかについては、競合する考え方があります。

1 つのビューは、他の人が言及したものです。これは、命令型のスタイルでは自然なことです。何もすることがないときは、早く戻った方がよいでしょう。

より機能的なスタイルの観点から見たもう 1 つの見方は、メソッドには出口点が 1 つだけあるべきだというものです。関数型言語のすべては式です。そのため、if ステートメントには常に else 句が必要です。そうしないと、if 式が常に値を持つとは限りません。したがって、機能的なスタイルでは、最初のアプローチがより自然です。

于 2011-12-08T20:54:24.147 に答える
12

ガード句または前提条件 (おそらくご覧のとおり) は、特定の条件が満たされているかどうかを確認してから、プログラムの流れを中断します。ifステートメントの 1 つの結果だけに本当に関心がある場合に最適です。したがって、次のように言うのではなく:

if (something) {
    // a lot of indented code
}

条件を逆にして、その逆の条件が満たされた場合にブレークします

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

returnほど汚れていませんgoto。関数が実行できなかったコードの残りの部分を示す値を渡すことができます。

ネストされた条件でこれを適用できる最適な例を次に示します。

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

対:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

最初の方がきれいだと主張する人はほとんどいないでしょうが、もちろん、それは完全に主観的なものです。一部のプログラマーは、インデントによって何かが動作している条件を知りたいと思っていますが、私はむしろメソッドの流れを直線的に保ちたいと思っています。

プリコンがあなたの人生を変えたり、あなたを寝かせたりするとは一瞬たりとも言いませんが、あなたのコードが少しだけ読みやすくなるかもしれません。

于 2008-11-06T10:07:18.390 に答える
9

ここにはいくつかの良い点がありますが、メソッドが非常に長い場合、複数のリターン ポイントも読み取り不能になる可能性があります。そうは言っても、複数のリターンポイントを使用する場合は、メソッドが短いことを確認してください。そうしないと、複数のリターンポイントの読みやすさのボーナスが失われる可能性があります。

于 2008-11-06T10:27:57.083 に答える
9

パフォーマンスは2部構成。ソフトウェアが本番環境にあるときにもパフォーマンスを維持できますが、開発中およびデバッグ中にもパフォーマンスを維持したいと考えています。些細なことを「待つ」ことは、開発者にとって最も避けたいことです。最終的に、最適化を有効にしてこれをコンパイルすると、同様のコードが生成されます。したがって、両方のシナリオで効果を発揮するこれらの小さなトリックを知っておくとよいでしょう。

質問のケースは明らかで、ReSharper は正しいです。ステートメントをネストしてコード内に新しいスコープを作成するのではなくif、メソッドの開始時に明確なルールを設定します。読みやすさが向上し、保守が容易になり、行きたい場所を見つけるためにふるいにかけなければならないルールの量が減ります。

于 2011-12-08T21:29:15.103 に答える
7

個人的には、1 つの出口点のみを好みます。メソッドを簡潔かつ要点にとどめておけば、簡単に達成できます。また、次にコードに取り組む人に予測可能なパターンを提供します。

例えば。

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

これは、関数が終了する前に関数内の特定のローカル変数の値を確認したい場合にも非常に便利です。最終リターンにブレークポイントを配置するだけで、(例外がスローされない限り) ヒットすることが保証されます。

于 2008-11-07T07:43:25.123 に答える
6

複数の出口点を回避すると、パフォーマンスが向上する可能性があります。C#についてはよくわかりませんが、C ++では、名前付き戻り値の最適化(Copy Elision、ISO C ++ '03 12.8 / 15)は、単一の出口点を持つことに依存しています。この最適化により、戻り値のコピー構築が回避されます(特定の例では問題ありません)。これにより、関数が呼び出されるたびにコンストラクタとデストラクタを保存するため、タイトループでのパフォーマンスが大幅に向上する可能性があります。

しかし、99%の場合、追加のコンストラクタとデストラクタの呼び出しを保存しても、ネストされたifブロックがもたらす可読性を失う価値はありません(他の人が指摘しているように)。

于 2011-12-14T17:33:13.287 に答える
6

コードがどのように見えるかについての多くの正当な理由. しかし、結果はどうですか?

いくつかの C# コードとその IL コンパイル形式を見てみましょう。

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length == 0) return;
        if ((args.Length+2)/3 == 5) return;
        Console.WriteLine("hey!!!");
    }
}

この単純なスニペットはコンパイルできます。生成された.exeファイルを で開きildasm、結果を確認できます。アセンブラのすべてを投稿するわけではありませんが、結果について説明します。

生成された IL コードは次のことを行います。

  1. 最初の条件が の場合、2 番目の条件falseのコードにジャンプします。
  2. true最後の命令にジャンプする場合。(注: 最後の命令はリターンです)。
  3. 2 番目の条件では、結果が計算された後に同じことが起こります。比較して: の場合に到達したConsole.WriteLinefalse、これが の場合に最後に到達しましたtrue
  4. メッセージを印刷して返します。

そのため、コードは最後までジャンプするようです。ネストされたコードで通常の if を実行するとどうなるでしょうか?

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length != 0 && (args.Length+2)/3 != 5) 
        {
            Console.WriteLine("hey!!!");
        }
    }
}

結果は、IL 命令で非常によく似ています。違いは、条件ごとに 2 つのジャンプがあったことfalseですtrue。これで、IL コードの流れが改善され、ジャンプが 3 つになりました (コンパイラがこれを少し最適化しました)。

  1. 1 回目のジャンプ: Length が 0 の場合、コードが再びジャンプする部分 (3 回目のジャンプ) の最後まで。
  2. 2 番目: 2 番目の条件の途中で、1 つの命令を回避します。
  3. 3 番目: 2 番目の条件が の場合false、最後にジャンプします。

とにかく、プログラムカウンターは常にジャンプします。

于 2008-11-06T11:11:38.057 に答える
5

理論的には、反転ifによって分岐予測のヒット率が上がると、パフォーマンスが向上する可能性があります。実際には、特にコンパイル後に分岐予測がどのように動作するかを正確に知ることは非常に難しいと思います。そのため、アセンブリ コードを作成している場合を除いて、日常の開発ではそれを行いません。

分岐予測の詳細はこちら.

于 2011-12-24T12:39:35.773 に答える
4

それは単に物議を醸しています。早期復帰の問題について「プログラマー間の合意」はありません。私の知る限り、それは常に主観的なものです。

ほとんどの場合真になるように条件を記述したほうがよいため、パフォーマンスに関する議論を行うことは可能です。より明確であると主張することもできます。一方、ネストされたテストを作成します。

この質問に対する決定的な答えは得られないと思います。

于 2008-11-06T09:59:27.143 に答える
3

そこにはすでに多くの洞察に満ちた答えがありますが、それでも、少し異なる状況に向けたいと思います: 前提条件の代わりに、実際に関数の上に置かれるべきであり、段階的な初期化を考えてください。各ステップが成功するかどうかを確認してから、次のステップに進む必要があります。この場合、上部のすべてを確認することはできません。

Steinberg の ASIOSDK を使用して ASIO ホスト アプリケーションを作成しているときに、ネスティング パラダイムに従っているため、自分のコードが本当に読みにくいことに気付きました。上記の Andrew Bullock が述べたように、それは 8 レベルの深さであり、そこに設計上の欠陥は見られません。もちろん、一部の内部コードを別の関数にパックし、残りのレベルをネストして読みやすくすることもできますが、これはかなりランダムに思えます。

ネストをガード句に置き換えることで、クリーンアップ コードの一部が関数の最後ではなくかなり早い段階で発生するはずだったという私の誤解を発見しました。入れ子になったブランチでは、私はそれを見たことがなかったでしょう。それが私の誤解につながったとさえ言えます。

したがって、これは、反転された if がより明確なコードに貢献できる別の状況である可能性があります。

于 2011-06-14T11:41:48.900 に答える
2

それは意見の問題です。

私の通常のアプローチは、単一行のifを避け、メソッドの途中で戻ることです。

メソッドのどこにでも提案されているような行は必要ありませんが、メソッドの上部にある一連の仮定をチェックし、それらがすべて合格した場合にのみ実際の作業を行うことについては、何か言いたいことがあります。

于 2008-11-06T10:00:17.453 に答える
2

私の意見では、単に void (またはチェックするつもりのない無駄な戻りコード) を返すだけであれば、初期の戻りは問題なく、ネストを回避し、同時に関数が完了したことを明示するため、読みやすさが向上する可能性があります。

実際に returnValue を返す場合 - 通常、ネストはより良い方法です。なぜなら、returnValue を 1 か所 (最後に - 当たり前のこと) だけで返すためです。多くの場合、コードの保守性が向上する可能性があります。

于 2008-11-06T11:05:49.753 に答える
0

この種のコーディングにはいくつかの利点がありますが、私にとって大きな利点は、すぐに戻ることができれば、アプリケーションの速度を向上できることです。IE 前提条件 X のおかげで、エラーですぐに戻ることができることを知っています。これにより、最初にエラー ケースが取り除かれ、コードの複雑さが軽減されます。多くの場合、CPU パイプラインがよりクリーンになるため、パイプラインのクラッシュや切り替えを停止できます。第 2 に、ループに陥っている場合は、すぐに中断または復帰することで、多くの CPU を節約できます。一部のプログラマーは、ループ不変条件を使用してこの種の迅速な終了を行いますが、これにより、CPU パイプラインが壊れて、メモリ シークの問題が発生し、CPU が外部キャッシュからロードする必要があることを意味します。でも、基本的には自分の思い通りにすればいいと思うのですが、つまり、正しいコードの抽象的な概念を実装するためだけに複雑なコード パスを作成するのではなく、ループまたは関数を終了します。持っている道具がハンマーしかない場合、すべてが釘のように見えます。

于 2008-11-06T10:41:21.723 に答える
0

前述のように、一般的な合意はありません。煩わしさを軽減するために、この種の警告を「ヒント」に減らすことができます

于 2008-11-06T10:03:22.907 に答える
0

私の考えでは、「関数の途中」でのリターンはそれほど「主観的」であってはなりません。理由は非常に単純です。次のコードを使用してください。

    関数 do_something( データ ){

      if (!is_valid_data( データ ))
            false を返します。


       do_something_that_take_an_hour( データ );

       インスタンス = 新しい object_with_very_painful_constructor( データ );

          if (インスタンスが無効です) {
               エラーメッセージ( );
                戻る ;

          }
       connect_to_database ( );
       get_some_other_data( );
       戻る;
    }

おそらく最初の「返品」はそれほど直感的ではありませんが、それは本当に節約になります。きれいなコードに関する「アイデア」が多すぎて、「主観的な」悪いアイデアをなくすにはもっと練習が必要です。

于 2008-11-06T10:09:52.377 に答える