関数はreturnステートメントを1つだけ持つ必要がありますか?


780

関数内にreturnステートメントを1つだけ持つ方が良い方法である理由はありますか?

それとも論理的に正しいとすぐに関数から戻ることは問題ありませんか?つまり、関数には多くのreturnステートメントがある可能性があります。


25
質問が言語にとらわれないことに同意しません。一部の言語では、複数のリターンを持つことは他の言語よりも自然で便利です。私は、RAIIを使用するC ++関数よりも、C関数の初期のリターンについて不平を言う可能性が高くなります。
Adrian McCarthy

3
これは、密接に関連し、優れた答えを持っている:programmers.stackexchange.com/questions/118703/...
ティムSchmelter

言語にとらわれない?関数型言語を使用している人に、関数ごとに1つのリターンを使用する必要があることを説明します
。p– Boiethios

回答:


741

メソッドの最初に、「簡単な」状況に戻るためのステートメントがいくつかあります。たとえば、これ:

public void DoStuff(Foo foo)
{
    if (foo != null)
    {
        ...
    }
}

...このように読みやすくすることができます(IMHO):

public void DoStuff(Foo foo)
{
    if (foo == null) return;

    ...
}

だから、はい、私は関数/メソッドから複数の「出口点」を持つことは問題ないと思います。


83
同意した。複数の出口点があると手に負えなくなる可能性がありますが、私は間違いなく、IFブロックに関数全体を配置するよりも良いと思います。コードを読みやすくするために、意味のある回数だけreturnを使用してください。
ジョシュアカーモディ

172
これは「ガードステートメント」として知られているFowlerのリファクタリングです。
Lars Westergren、2008

12
関数が比較的短く保たれている場合、戻り点が中央付近にある関数の構造に従うことは難しくありません。
KJAWolf 2009年

21
if-elseステートメントの巨大なブロックで、それぞれにリターンがありますか?それは何もありません。そのようなことは通常、簡単にリファクタリングされます。(少なくとも、より一般的な単一出口のバリエーションと結果変数があるので、複数出口のバリエーションはこれ以上難しいとは思えません。)本当の頭痛がしたい場合は、if-elseループとwhileループの組み合わせ(ローカルで制御)を見てください。 booleans)、ここで結果変数が設定され、メソッドの最後の最終出口点につながります。それは単一の出口のアイデアが狂ってしまった、そしてはい、私はそれを処理しなければならなかった実際の経験から話している。
マーカスアンドレン、

7
'想像してください:' DoStuff '関数の最後に "IncreaseStuffCallCounter"メソッドを実行する必要があります。この場合、どうしますか?:)」 -DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
ジムBalter

355

コードコンプリートについて誰も言及も引用もしていないので、私がやります。

17.1返品

各ルーチンのリターン数を最小限に抑えます。下部でそれを読んで、あなたがそれが上のどこかに戻った可能性に気づいていないなら、ルーチンを理解することはより困難です。

読みやすさを向上させる場合は、リターンを使用します。特定のルーチンでは、答えがわかったら、すぐにそれを呼び出し元のルーチンに戻したいことがあります。ルーチンがクリーンアップを必要としない方法で定義されている場合、すぐに戻らないことは、さらにコードを記述する必要があることを意味します。


64
+1は、「最小化」のニュアンスですが、複数のリターンを禁止しません。
Raedwald

13
「理解するのが難しい」は非常に主観的であり、特に著者が一般的な主張をサポートするための経験的証拠を提供していない場合は...最終的なreturnステートメントのコードの多くの条件付き位置に設定する必要がある単一の変数がある場合も同様に扱われます。 「変数がintの上のどこかに割り当てられている可能性に気付いていない!
ヘストンT.ホルトマン

26
個人的には、可能な限り早期に戻ることを好む。どうして?さて、特定のケースのreturnキーワードを見ると、「私は終わった」ということがすぐにわかります。後で何が起こるかを理解するために読み続ける必要はありません。
Mark Simpson

12
@ HestonT.Holtmann:コードコンプリートをプログラミングの本の中でユニークなものにしたのは、その助言経験的な証拠に裏付けられているということでした。
エイドリアンマッカーシー

9
複数の戻り点があることは必ずしも良いとは限らないが、時には必要であると述べているため、これはおそらく受け入れられる答えになるはずです。
Rafid 2014年

229

私は技術が実際に有用であることがわかってきたように、複数の出口点に対して任意に決定することが非常に賢明だろうと言うでしょう何度も何度も、実際に私は頻繁にしている、既存のコードをリファクタリングし、明確にするための複数の出口点に。したがって、2つのアプローチを比較できます。

string fooBar(string s, int? i) {
  string ret = "";
  if(!string.IsNullOrEmpty(s) && i != null) {
    var res = someFunction(s, i);

    bool passed = true;
    foreach(var r in res) {
      if(!r.Passed) {
        passed = false;
        break;
      }
    }

    if(passed) {
      // Rest of code...
    }
  }

  return ret;
}

これを、複数の出口点許可されているコードと比較してください。

string fooBar(string s, int? i) {
  var ret = "";
  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

後者はかなり明確だと思います。私が知る限り、複数の出口点の批判は最近ではかなり古風な見方です。


12
明快さは見る人の目にあります-私は関数を見て、始まり、中間、終わりを求めます。関数が小さい場合は問題ありませんが、何かが壊れている理由を調べようとしていて、「Rest of Code」が重要であることが判明した場合は、何年もかけてretの理由を探すことができます
Murph

7
まず、これは不自然な例です。2番目に、:string ret; "は2番目のバージョンでどこに行きますか?3番目に、Retには有用な情報が含まれていません。4番目に、1つの関数/メソッドにそれほど多くのロジックがあるのですか?5番目に、DidValuesPass(type res)を作成し、次にRestOfCode()を個別に作成しません。サブファンクション?
Rick Minerich 2009年

25
@リック1.私の経験では不自然ではありませんが、これは実際に私が何度も遭遇したパターンです。2。「残りのコード」で割り当てられています。3.ええと?それは例ですか?4.まあ、私はそれはこの点に工夫だと思うが、それはこれだけを正当化することは可能ですが、5は行うことができ...
LJS

5
@Rickポイントは、巨大なifステートメントでコードをラップするよりも早く戻る方が簡単なことが多いということです。私の経験では、適切なリファクタリングを行ったとしても、多くのことが浮かび上がります。
ljs 2009年

5
複数のreturnステートメントを使用しないことのポイントは、デバッグを容易にすることです。1秒は読みやすくするためです。最後の1つのブレークポイントでは、例外ではなく終了値を確認できます。読みやすさに関しては、2つの関数は同じように動作しません。!r.Passedの場合、デフォルトの戻り値は空の​​文字列ですが、「より読みやすい」ものがnullを返すように変更されます。著者は、以前はほんの数行後にデフォルトがあったことを誤解しました。些細な例でも、デフォルトのリターンが不明確になるのは簡単です。最後に単一のリターンを適用すると、強制が容易になります。
ミッチ

191

私は現在、コードベースに取り組んでおり、コードベースで作業している2人の人が盲目的に「単一の出口点」の理論にサブスクライブしています。経験から、それは恐ろしい恐ろしい習慣だと言えます。コードを維持するのが非常に難しくなるので、その理由を説明します。

「単一の出口点」理論では、必然的に次のようなコードが必要になります。

function()
{
    HRESULT error = S_OK;

    if(SUCCEEDED(Operation1()))
    {
        if(SUCCEEDED(Operation2()))
        {
            if(SUCCEEDED(Operation3()))
            {
                if(SUCCEEDED(Operation4()))
                {
                }
                else
                {
                    error = OPERATION4FAILED;
                }
            }
            else
            {
                error = OPERATION3FAILED;
            }
        }
        else
        {
            error = OPERATION2FAILED;
        }
    }
    else
    {
        error = OPERATION1FAILED;
    }

    return error;
}

これはコードを追跡するのを非常に困難にするだけでなく、後で戻って1と2の間に操作を追加する必要があると言います。おかしな関数全体についてインデントする必要があります。 if / else条件と中括弧が適切に一致している。

この方法では、コードのメンテナンスが非常に困難になり、エラーが発生しやすくなります。


5
@マーフ:各条件を読み取らずに、各条件の後に他に何も起こらないことを知る方法はありません。通常、私はこれらの種類のトピックは主観的であると言いますが、これは明らかに間違っています。各エラーが返されれば、完了です。何が起こったかを正確に把握できます。
GEOCHET 2008

6
@マーフ:この種のコードが使用され、乱用され、乱用されているのを見てきました。内部には真のif / elseがないため、この例は非常に単純です。この種のコードが分解する必要があるのは、「他の」忘れられた1つだけです。私の知る限り、このコードは例外を本当に必要としている。
paercebal 2008

15
あなたはそれをこれにリファクタリングでき、その「純度」を保ちます:if(!SUCCEEDED(Operation1())){} else error = OPERATION1FAILED; if(error!= S_OK){if(SUCCEEDED(Operation2())){} else error = OPERATION2FAILED; } if(error!= S_OK){if(SUCCEEDED(Operation3())){} else error = OPERATION3FAILED; } //など。
ジョー・ピネダ

6
このコードには1つの出口点だけがありません。これらの "error ="ステートメントはそれぞれ出口パス上にあります。関数を終了することだけでなく、ブロックやシーケンスを終了することも重要です。
Jay Bazuzi、2008

6
単一の戻りが「必然的に」結果として深い入れ子になることに私は同意しません。あなたの例は、単一の戻り値(ゴトスなし)を持つ単純な線形関数として書くことができます。また、リソース管理をRAIIだけに頼ることができない、または頼ることができない場合は、早期のリターンにより、リークやコードの重複が発生します。最も重要なのは、早期のリターンによって事後条件を主張することが非現実的になることです。
エイドリアンマッカーシー、

72

構造化プログラミングによると、関数ごとに1つのreturnステートメントのみを使用する必要があります。これは複雑さを制限するためです。Martin Fowlerのような多くの人々は、複数のreturnステートメントで関数を書く方が簡単だと主張しています。彼は彼が書いた古典的なリファクタリングの本でこの議論を提示します。これは、彼の他のアドバイスに従い、小さな関数を書く場合にうまく機能します。私はこの見解に同意し、厳密な構造化プログラミングの純粋主義者だけが関数ごとに単一のreturnステートメントを遵守します。


44
構造化プログラミングは何も言っていません。構造化プログラミングの擁護者として自分自身を説明する(すべてではない)一部の人々は、そう言っています。
wnoise 2008

15
「彼の他のアドバイスに従い、小さな関数を書けば、これはうまくいきます。」これが最も重要なポイントです。小さな関数が多くの戻り点を必要とすることはめったにありません。

6
コメントの@wnoise +1なので、真です。すべての「構造化プログラミング」は、GOTOを使用しないことを述べています。
paxos1977 2008

1
@ceretullis:必要でない限り。もちろん、これは必須ではありませんが、Cで役立つ場合があります。Linuxカーネルがそれを使用するのには、十分な理由があります。GOTOは、有害なGOTOとして、機能が存在する場合でも制御フローを移動するために使用することについて話しました。「決して使用しない」とは表示されませんGOTO
EstebanKüber2009

1
「すべてはコードの「構造」を無視することです。」-いいえ、まったく反対です。「彼らが避けられるべきだと言うのは理にかなっている」-いいえ、そうではありません。
ジムバルター2016年

62

Kent Beckが、ルーチンを作成する実装パターンでガード句について議論するときに注意するように、単一の入口と出口点があります...

「同じルーチン内の多くの場所に出入りするときに起こり得る混乱を防ぐためでした。実行されたステートメントを理解するのが難しい作業である多くのグローバルデータで記述されたFORTRANまたはアセンブリ言語プログラムに適用される場合、それは理にかなっています。 。小さなメソッドとほとんどローカルデータで、それは不必要に保守的です。」

ガード節で記述された関数は、1つの長いネストされたif then elseステートメントの束よりもはるかに簡単に理解できます。


もちろん、「if-then-elseステートメントの長いネストされた束」は、guard句の唯一の選択肢ではありません。
エイドリアン・マッカーシー、

@AdrianMcCarthyより良い代替案はありますか?それは皮肉よりも便利でしょう。
しんぞう

@kuhaku:私はその皮肉を呼ぶかどうかはわかりません。答えは、それがどちらかまたは両方の状況であることを示唆しています:ガード句またはif-then-elseの長いネストされた束。多くの(ほとんど?)プログラミング言語は、ガード節以外にも、このようなロジックを因数分解する多くの方法を提供しています。
エイドリアン・マッカーシー

61

副作用のない関数では、1つ以上の戻り値を持つ理由はありません。関数スタイルで記述する必要があります。副作用のあるメソッドでは、物事はより順次的(時間インデックス付き)であるため、returnステートメントを実行を停止するコマンドとして使用して、命令スタイルで記述します。

つまり、可能であれば、このスタイルを優先します

return a > 0 ?
  positively(a):
  negatively(a);

これ以上

if (a > 0)
  return positively(a);
else
  return negatively(a);

ネストされた条件の複数のレイヤーを作成している場合は、たとえば述語リストを使用して、それをリファクタリングできる方法がおそらくあります。ifsとelsesが構文的に大きく離れている場合は、それをより小さな関数に分解することをお勧めします。1画面以上のテキストにまたがる条件付きブロックは読みにくいです。

すべての言語に適用される厳格な規則はありません。単一のreturnステートメントがあるようなものは、コードを良くしません。しかし、優れたコードでは、そのように関数を記述できる傾向があります。


6
+1「ifsとelsesが構文的に大きく離れている場合は、それをより小さな関数に分解した方がよいでしょう。」
Andres Jaan Tack

4
+1、これが問題である場合、通常、1つの関数でやりすぎていることを意味します。これが最高の投票結果ではないことを本当に憂鬱にしています
Matt Briggs

1
Guardステートメントにも副作用はありませんが、ほとんどの人はそれを有用だと考えています。したがって、副作用がない場合でも、実行を早期に停止する理由があるかもしれません。この回答は、私の意見ではこの問題を完全には扱っていません。
Maarten Bodewes、2015年

@ MaartenBodewes-owlstead 「厳格さと怠惰」を
Apocalisp

43

私はそれをC ++のコーディング標準で見たことがあります。これはCからの二日酔いでした。RAIIや他の自動メモリ管理がない場合は、リターンごとにクリーンアップする必要があります。つまり、カットアンドペーストのいずれかです。クリーンアップまたはgoto(管理された言語では「最終的に」と論理的に同じ)のどちらも、不適切な形式と見なされます。C ++や他の自動メモリシステムでスマートポインターやコレクションを使用することを実践している場合は、その強い理由はなく、読みやすさや判断の呼び出しがすべてになります。


よく言われていますが、高度に最適化されたコード(複雑な3Dメッシュをスキニングするソフトウェアなど)を作成する場合は、削除をコピーした方がよいと思います
Grant Peters

1
どうしてあなたはそれを信じるのですか?最適化が不十分で、の逆参照にオーバーヘッドがあるコンパイラがある場合は、auto_ptrプレーンポインタを並行して使用できます。そもそも、最適化されていないコンパイラで「最適化された」コードを書くのは奇妙です。
ピートKirkham

これにより、ルールに興味深い例外が生じます。プログラミング言語にメソッドの最後に自動的に呼び出されるもの(Javaのtry... などfinally)が含まれておらず、リソースのメンテナンスを行う必要がある場合は、単一のメソッドの最後に戻ります。これを行う前に、コードをリファクタリングして状況を解消することを真剣に検討する必要があります。
Maarten Bodewes、2011

@PeteKirkhamクリーンアップのgotoが悪いのはなぜですか?はいgotoはうまく使えませんが、この特定の使い方は悪くありません。
q126y 2016年

1
C ++の@ q126yは、RAIIとは異なり、例外がスローされると失敗します。Cでは、これは完全に有効な方法です。stackoverflow.com/questions/379172/use-goto-or-notを
ピートカーカム

40

関数の途中の returnステートメントは悪いという考えに頼っています。returnを使用して、関数の上部にいくつかのガード句を構築し、もちろん関数の最後に何が問題なく戻るかをコンパイラーに指示できますが、関数の途中での戻りは見落としやすく、関数を解釈しにくくします。


38

関数内にreturnステートメントを1つだけ持つ方が良い方法である理由はありますか?

はい、あります:

  • 単一の出口点は、事後条件を主張するための優れた場所を提供します。
  • 関数の最後の1つのリターンにデバッガーブレークポイントを配置できると、多くの場合に役立ちます。
  • リターンが少ないほど、複雑さが少なくなります。線形コードは一般に理解が簡単です。
  • 関数を単一の戻り値に単純化しようとすると複雑になる場合、それは、より小さく、より一般的で、より理解しやすい関数にリファクタリングする動機になります。
  • デストラクタのない言語を使用している場合、またはRAIIを使用していない場合は、1回の戻りでクリーンアップする必要のある場所の数が減ります。
  • 一部の言語では、単一の出口点が必要です(たとえば、PascalおよびEiffel)。

多くの場合、この質問は、複数のリターンの間の誤った二分法または深くネストされたifステートメントとして提起されます。ほとんどの場合、出口点が1つだけの非常に線形な(深い入れ子ではない)3番目のソリューションがあります。

更新MISRAガイドラインは、シングルエグジット促進しているようです。

明確にするために、私は複数のリターンを持つことは常に間違っていると言っているのではありません。しかし、それ以外の点では同等のソリューションを考えると、単一のリターンを持つソリューションを好む多くの正当な理由があります。


2
単一のreturnステートメントを使用するもう1つの理由は、おそらく最近では、ロギングを行うことです。メソッドにロギングを追加する場合は、メソッドが返すものを伝える単一のログステートメントを配置できます。
2013

FORTRAN ENTRYステートメントはどの程度一般的でしたか?docs.oracle.com/cd/E19957-01/805-4939/6j4m0vn99/index.htmlを参照してください。また、冒険したい場合は、AOPとアフターアドバイスを使用してメソッドをログに記録できます
Eric Jablow

1
+1最初の2ポイントは私を納得させるのに十分でした。それは最後の2番目の段落です。深いネストされた条件文は、ポリモーフィズムがOOPに導入された主な理由である単一責任ルールの違反を助長するため、私はログ記録要素に同意しません。
フランシスロジャース2014年

これをC#およびコードコントラクトで追加したいのですが、ポストコンディションの問題は問題ではありませんContract.Ensures。これは、複数のリターンポイントで引き続き使用できるためです。
julealgon 2014年

1
@ q126y:goto共通のクリーンアップコードを取得するために使用している場合、クリーンアップコードreturnの最後にシングルが存在するように関数を単純化した可能性があります。したがって、問題はで解決したと言えるかもしれませんがgoto、単純化して1つに解決したと思いますreturn
エイドリアン・マッカーシー

33

関数の最後に単一のブレークポイントを設定して、実際に返される値を確認できるため、単一の出口点があるとデバッグに利点があります。


6
ブラボー!この客観的な理由を述べるのはあなただけです。これが、複数の出口点よりも単一の出口点を好む理由です。デバッガーが任意の出口点にブレークポイントを設定できる場合、おそらく複数の出口点を使用します。私の現在の意見は、コードでデバッガを使用する必要がある他の人たちを犠牲にして、複数の出口ポイントをコーディングする人が自分の利益のためにそれを行うということです(そして私は、複数の出口。)
MikeSchinkel 2010年

3
はい。本番環境で断続的に誤動作するシステムにログ出力コードを追加しています(ここでは、ステップ実行できません)。以前のコーダーが単一出口を使用していた場合は、はるかに簡単でした。
マイケルブラックバーン

3
確かに、デバッグには役立ちます。しかし実際には、ほとんどの場合、呼び出しの直後に、呼び出し関数にブレークポイントを設定することができました。事実上、同じ結果になります。(もちろん、その位置は呼び出しスタックにあります。)YMMV。
foo

お使いのデバッガがステップアウトやステップ・リターン機能を提供(およびそれぞれを、すべてのデバッガは、私の知る限りではありません)しない限り、これは右の戻り値を示しており、ある後に返されます。ただし、変数に割り当てられていない場合、後で値を変更するのは少し難しいかもしれません。
Maarten Bodewes 2014年

7
メソッドの「終わり」(終了、右中括弧、言語に関係なく)にブレークポイントを設定し、場所や数に関係なくそのブレークポイントにヒットすることを許可しないデバッガーを長い間見たことがありません、戻り値はメソッドにあります。また、関数の戻り値が1つしかない場合でも、例外(明示的または継承)で関数を終了できないわけではありません。だから、これは本当に有効なポイントではないと思います。
Scott Gartner

19

一般に、関数からの出口点は1つだけにしようとしています。ただし、そうすることで、実際には必要以上に複雑な関数本体が作成される場合があります。その場合は、複数の出口点を用意することをお勧めします。結果として生じる複雑さに基づく「判断の呼びかけ」である必要がありますが、目標は、複雑さと理解しやすさを犠牲にすることなく、できるだけ少ない出口点でなければなりません。


「一般に、関数からの出口点を1つだけにしようとしています」-なぜですか?「目標はできるだけ少ない出口点でなければなりません」-なぜですか?そして、なぜ19人がこの非回答に投票したのですか?
ジムバルター、2016年

@JimBalter結局のところ、それは個人的な好みに要約されます。出口点が増えると、通常は(常にではありませんが)より複雑な方法になり、誰かが理解しにくくなります。
Scott Dorman、2016年

「それは個人の好みに帰着します。」-言い換えれば、理由を提供することはできません。「通常、より多くの出口点はより複雑な方法につながります(ただし、常にではありません)」-いいえ、実際にはそうではありません。論理的に同等の2つの関数が与えられ、1つはガード句を持ち、もう1つは単一の出口をもつ場合、後者はより循環的な複雑さを持ち、多くの研究がよりエラーが発生しやすく理解しにくいコードの結果を示しています。ここで他の応答を読むことから利益を得るでしょう。
Jim Balter

14

いいえ、1970年代にはもう住んでいないので。関数が長すぎて複数回の戻りが問題になる場合は、長すぎます。

(例外のある言語の複数行関数には、とにかく複数の出口点があるという事実とはかなり異なります。)


14

それが本当に複雑でない限り、私の好みは単一の出口です。場合によっては、複数の存在ポイントが他のより重要な設計問題を隠す可能性があることを発見しました:

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

このコードを見るとすぐに私は尋ねます:

  • 「foo」はヌルになるのでしょうか?
  • もしそうなら、「DoStuff」のクライアントは何人、ヌル「foo」で関数を呼び出しますか?

これらの質問への回答によっては、

  1. 決して真ではないため、チェックは無意味です(つまり、アサーションである必要があります)
  2. このチェックが真になることは非常にまれであり、そのため、これらの特定の呼び出し元関数はおそらく他のアクションを実行する必要があるため、変更する方がよい場合があります。

上記のどちらの場合でも、おそらく「foo」がnullにならず、関連する呼び出し側が変更されることを保証するために、アサーションを使用してコードを作り直すことができます。

複数の存在が実際にマイナスの影響を与える可能性がある他の2つの理由があります(具体的にはC ++コードに固有と思います)。それらはコードサイズとコンパイラの最適化です。

関数の出口でスコープ内にある非POD C ++オブジェクトは、デストラクタが呼び出されます。複数のreturnステートメントがある場合、スコープ内に異なるオブジェクトが存在する可能性があるため、呼び出すデストラクタのリストは異なります。したがって、コンパイラーは各returnステートメントのコードを生成する必要があります。

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

コードサイズが問題である場合-これは回避する価値があるかもしれません。

もう1つの問題は、「名前付き戻り値の最適化」(別名Copy Elision、ISO C ++ '03 12.8 / 15)に関連しています。C ++では、次のことが可能な場合、実装はコピーコンストラクターの呼び出しをスキップできます。

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

コードをそのまま使用すると、オブジェクト「a1」は「foo」で作成され、次にそのコピー構成が呼び出されて「a2」が構成されます。ただし、コピー省略により、コンパイラーはスタック上の「a2」と同じ場所に「a1」を作成できます。したがって、関数が戻るときにオブジェクトを「コピー」する必要はありません。

複数の出口点があると、これを検出しようとするコンパイラの作業が複雑になり、少なくとも比較的新しいバージョンのVC ++では、関数本体に複数の戻りがある場合、最適化は行われませんでした。詳細については、「Visual C ++ 2005の名前付き戻り値の最適化」を参照してください。


1
C ++の例から最後のdtorを除いてすべて取る場合、ifステートメントのスコープが終了したときに、Bおよびその後のCとBを破棄するコードを引き続き生成する必要があるため、複数のリターンがないことで何も得られません。
Eclipseの

4
+1そして、リストの一番下にあるのは、このコーディング慣行が存在する本当の理由 -NRVOです。ただし、これはマイクロ最適化です。そして、すべてのマイクロ最適化プラクティスと同様に、300 kHz PDP-8でのプログラミングに慣れており、クリーンで構造化されたコードの重要性を理解していない約50歳の「エキスパート」によっておそらく開始されました。一般に、Chris Sのアドバイスを参考にして、必要に応じて複数のreturnステートメントを使用してください。
BlueRaja-Danny Pflughoeft、2010年

私はあなたの好みに同意しませんが(私の心では、あなたのAssert提案はthrow new ArgumentNullException()、この場合のC#の場合のように、戻り点でもあります)、私は他の考慮事項を本当に気に入りました、それらはすべて私にとって有効であり、いくつかにおいて重要である可能性がありますニッチなコンテキスト。
julealgon 2014年

これはストローメンで一杯です。なぜの疑問fooテストされては何をするかどうかである主題とは何の関係も、ありませんif (foo == NULL) return; dowork; if (foo != NULL) { dowork; }
ジム・Balter

11

単一の出口点があると、循環的複雑度が低くなるため、理論的には、コードを変更したときにコードにバグが発生する可能性が低くなります。ただし、実践では、より実用的なアプローチが必要であると示唆する傾向があります。したがって、私は単一の出口点を持つことを目指しがちですが、より読みやすい場合は、コードにいくつかの出口点を持たせることができます。


非常に洞察力があります。ただし、プログラマーが複数の出口点をいつ使用するかがわかるまでは、1つの出口点に制限する必要があると思います。
Rick Minerich 2009年

5
あんまり。「if(...)return; ... return;」の循環的複雑さ。「if(...){...} return;」と同じです。どちらにも2つのパスがあります。
スティーブエマーソン

11

returnある意味でコードのにおいを生成するので、私は1つのステートメントのみを使用するように強制しています。説明させてください:

function isCorrect($param1, $param2, $param3) {
    $toret = false;
    if ($param1 != $param2) {
        if ($param1 == ($param3 * 2)) {
            if ($param2 == ($param3 / 3)) {
                $toret = true;
            } else {
                $error = 'Error 3';
            }
        } else {
            $error = 'Error 2';
        }
    } else {
        $error = 'Error 1';
    }
    return $toret;
}

(条件は厳しいです...)

条件が多いほど、関数は大きくなり、読みにくくなります。したがって、コードの匂いに慣れてきたら、それに気づき、コードをリファクタリングしたいと思うでしょう。次の2つの解決策があります。

  • 複数の返品
  • 個別の関数へのリファクタリング

複数の返品

function isCorrect($param1, $param2, $param3) {
    if ($param1 == $param2)       { $error = 'Error 1'; return false; }
    if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
    if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
    return true;
}

個別の機能

function isEqual($param1, $param2) {
    return $param1 == $param2;
}

function isDouble($param1, $param2) {
    return $param1 == ($param2 * 2);
}

function isThird($param1, $param2) {
    return $param1 == ($param2 / 3);
}

function isCorrect($param1, $param2, $param3) {
    return !isEqual($param1, $param2)
        && isDouble($param1, $param3)
        && isThird($param2, $param3);
}

確かに、それは長くて少し厄介ですが、この方法で関数をリファクタリングする過程で、

  • 多くの再利用可能な関数を作成し、
  • 関数をより人間が読めるようにし、
  • 関数の焦点は、値が正しい理由にあります。

5
-1:悪い例。エラーメッセージの処理を省略しました。それが必要ない場合は、isCorrectをreturn xx && yy && zz;と同じように表現できます。ここで、xx、yy、zはisEqual、isDouble、isThird式です。
kauppi 2009

10

必要な数、またはコードをよりクリーンにするもの(guard句など)が必要です。

私は個人的には、「ベストプラクティス」が返すステートメントは1つだけにするべきだと言うことを聞いたり見たりしたことがありません。

ほとんどの場合、私はロジックパスに基づいてできるだけ早く関数を終了する傾向があります(ガード句はこの優れた例です)。


10

通常、複数の戻り値は(C#で作成するコードでは)良いと思います。シングルリターンスタイルはCからの引き継ぎですが、おそらくCでコーディングしていません。

すべてのプログラミング言語で、メソッドに1つの出口点のみを要求する法律はありません。一部の人々はこのスタイルの優位性を主張し、時にはそれを「ルール」または「法則」に引き上げますが、この信念はいかなる証拠や研究によっても裏付けられていません。

リソースを明示的に割り当て解除する必要があるCコードでは、複数の戻りスタイルが悪い習慣である可能性がありますが、Java、C#、Python、JavaScriptなどの言語で、自動ガベージコレクションやtry..finallyブロック(およびusingC#のブロック) )、そしてこの議論は適用されません-これらの言語では、集中的な手動のリソース割り当て解除を必要とすることは非常にまれです。

単一の戻り値が読みやすい場合とそうでない場合があります。コードの行数を減らしたり、ロジックをより明確にしたり、中括弧やインデントや一時変数の数を減らしたりしていないか確認してください。

したがって、これはレイアウトや読みやすさの問題であり、技術的な問題ではないため、芸術的な感性に合わせてできるだけ多くのリターンを使用してください。

私はこれについてブログでもっと詳しく話しました。


10

結果として生じる不可避の「矢印」プログラミングについて言うのが悪いのと同じように、単一の出口点を持つことについて言うのは良いことです。

入力検証またはリソース割り当て中に複数の出口点を使用する場合、すべての「エラー終了」を目に見える形で関数の上部に配置しようとします。

「SSDSLPedia」のスパルタンプログラミングの記事と「Portland Pattern RepositoryのWiki」の単一機能の出口点の記事の両方に、これに関する洞察に満ちた議論があります。また、もちろん、この投稿を検討する必要があります。

たとえば、1つの場所でリソースを解放するために、(例外が有効化されていない言語で)単一の出口点が本当に必要な場合は、gotoを注意深く適用すると効果的です。たとえば、このかなり不自然な例を参照してください(画面の不動産を節約するために圧縮されています)。

int f(int y) {
    int value = -1;
    void *data = NULL;

    if (y < 0)
        goto clean;

    if ((data = malloc(123)) == NULL)
        goto clean;

    /* More code */

    value = 1;
clean:
   free(data);
   return value;
}

個人的には、一般的に、複数の出口点を嫌うよりも、矢印プログラミングを嫌うが、どちらも正しく適用されると役立つ。もちろん、どちらも必要としないようにプログラムを構成するのが最善です。関数を複数のチャンクに分割すると、通常は役立ちます:)

そうするとき、私はとにかくこの例のように複数の出口点に行き着くことがわかります。ここで、いくつかの大きな関数がいくつかの小さな関数に分割されています。

int g(int y) {
  value = 0;

  if ((value = g0(y, value)) == -1)
    return -1;

  if ((value = g1(y, value)) == -1)
    return -1;

  return g2(y, value);
}

プロジェクトまたはコーディングガイドラインによっては、ボイラープレートコードのほとんどをマクロに置き換えることができます。補足として、このように分解すると、関数g0、g1、g2を個別にテストすることが非常に簡単になります。

明らかに、オブジェクト指向で例外対応の言語では、そのようなifステートメントは使用せず(または、十分な努力を払わずにそれを回避できれば)、コードははるかにわかりやすくなります。そして非矢じり。そして、非最終的なリターンのほとんどはおそらく例外です。

要するに;

  • いくつかのリターンは多くのリターンよりも優れています
  • 巨大な矢よりも2回以上のリターンの方が優れており、一般的にガード句は問題ありません。
  • 例外は、可能であれば、ほとんどの「保護条項」を置き換える可能性があります。

この例は、NULLポインターを解放しようとするため、y <0でクラッシュします;-)
Erich Kitzmueller 2009

2
opengroup.org/onlinepubs/009695399/functions/free.html「ptr がnullポインターの場合、アクションは発生しません。」
Henrik Gustafsson、

1
いいえ、NULLをfreeに渡すことは定義された何もしないため、クラッシュしません。最初にNULLをテストする必要があるというのは、迷惑なほどよくある誤解です。
hlovdal 2010

「矢印」パターンは避けられない選択肢ではありません。これは誤った二分法です。
エイドリアンマッカーシー、

9

格言- 美しさは見る人の目にあります

NetBeansIntelliJ IDEAPythonPHPで誓う人もいます

一部のショップでは、これを主張すると仕事を失う可能性があります。

public void hello()
{
   if (....)
   {
      ....
   }
}

問題は、すべての可視性と保守性です。

論理代数と状態機械の使用を減らして単純化するためにブール代数を使うことに夢中です。ただし、私の目には「数学的手法」を使用してコーディングすることは不適切であると考えていた過去の同僚もいました。そしてそれは悪い習慣になるでしょう。申し訳ありませんが、私が採用している手法は非常にわかりやすく、保守しやすくなっています。6か月後にコードに戻ると、コードをはっきりと理解しているので、ことわざのスパゲッティが散らかっています。

(元クライアントが言っていたように)ちょっとバディは、私が修正する必要があるときにそれを修正する方法を知っている限り、あなたがやりたいことをします。

20年前のことを覚えています。私の同僚は、今日アジャイル開発戦略と呼ばれるものを採用したことで解雇されました。彼は細心の段階的な計画を立てていました。しかし彼のマネージャーは彼に怒鳴っていました。「ユーザーに機能を段階的にリリースすることはできません。ウォーターフォールに固執する必要があります。」マネージャーへの彼の反応は、段階的な開発は顧客のニーズにより正確になるだろうというものでした。彼は顧客のニーズに合わせて開発することを信じていましたが、マネージャーは「顧客の要件」に合わせたコーディングを信じていました。

私たちは、データの正規化、MVPMVCの境界を破ったことでしばしば罪を犯します。関数を作成する代わりにインライン化します。私たちは近道をとります。

個人的には、PHPは悪い習慣だと思いますが、何を知っていますか。すべての理論的な議論は、1つのルールセットを実行しようとすることになります。

品質=精度、保守性、収益性。

他のすべてのルールはバックグラウンドにフェードインします。そしてもちろん、このルールは決して衰えません:

怠惰は、優れたプログラマの美徳です。


1
「バディ(元クライアントが言っていたように)は、私が修正する必要があるときにそれを修正する方法を知っている限り、あなたがやりたいことをします。」問題:多くの場合、それを「修正」するのはあなたではありません。
Dan Barron 2013

私があなたがどこに向かっているかに同意しますが、あなたがそこに行く方法に必ずしも同意しないので、この答えに+1してください。理解のレベルがあると私は主張します。つまり、従業員Aが5年のプログラミング経験と会社での5年の経験を持っているという理解は、会社から始まったばかりの新卒の従業員Bのそれとは大きく異なります。私のポイントは、従業員Aがコードを理解できる唯一の人々である場合、それは保守可能ではないので、従業員Bが理解できるコードを書くように私たち全員が努力する必要があるということです。これこそが真の芸術がソフトウェアにある場所です。
Francis Rodgers

9

私は、ガード節を使用して早期に戻り、そうでなければメソッドの最後で終了することに傾倒しています。単一の入り口と出口のルールには歴史的な重要性があり、複数の戻り値(および多くの欠陥)を持つ単一のC ++メソッドで10 A4ページまで実行されたレガシーコードを処理するときに特に役立ちました。最近では、複数の出口を理解するためのインピーダンスが少なくなるようにメソッドを小さく保つことが、承認された優れた方法です。上記からコピーした次のKronozの例では、問題は// Rest of code ...

void string fooBar(string s, int? i) {

  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

この例は多少不自然ですが、foreachループをLINQステートメントにリファクタリングして、ガード節と見なしたくなるかもしれません。繰り返しになりますが、不自然な例では、コードの目的は明らかではなく、someFunction()には他の副作用があるか、結果が//コードの残りの部分...で使用されている可能性があります

if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;

次のリファクタリングされた関数を与える:

void string fooBar(string s, int? i) {

  if (string.IsNullOrEmpty(s) || i == null) return null;
  if (someFunction(s, i).Any(r => !r.Passed)) return null;

  // Rest of code...

  return ret;
}

C ++には例外はありませんか?nullでは、引数が受け入れられないことを示す例外をスローする代わりに、なぜ戻るのでしょうか。
Maarten Bodewes 2013年

1
前述のように、サンプルコードは以前の回答(stackoverflow.com/a/36729/132599)からコピーされています。元の例ではnullが返され、引数の例外をスローするためのリファクタリングは、私が作成しようとしていた点や元の質問にとって重要ではありませんでした。良い習慣の問題として、はい(C#の場合)通常、null値を返すのではなく、guard句でArgumentNullExceptionをスローします。
David Clarke

7

私が考えることができる1つの正当な理由は、コードの保守のためです。出口が1つしかないためです。結果の形式を変更したい場合は、実装するのがはるかに簡単です。また、デバッグのために、そこにブレークポイントを貼り付けることができます:)

そうは言っても、私はかつてコーディング標準が「関数ごとに1つのreturnステートメント」を課したライブラリーで作業する必要があり、それはかなり難しいと思いました。私はたくさんの数値計算コードを書いていて、しばしば「特別な場合」があるので、コードは結局追跡するのが非常に困難になりました...


それは実際には違いはありません。返されるローカル変数のタイプを変更する場合は、そのローカル変数へのすべての割り当てを修正する必要があります。また、メソッドの呼び出しもすべて修正する必要があるため、シグネチャが異なるメソッドを定義することをお勧めします。
Maarten Bodewes 2013年

@ MaartenBodewes-owlstead-違いを生むことができます。ローカル変数へのすべての割り当てを修正したり、メソッド呼び出しを変更したりする必要がない 2つの例だけを挙げると、関数は日付を文字列として返す場合があります(ローカル変数は実際の日付であり、最後の瞬間)、またはそれは小数を返す可能性があり、小数点以下の桁数を変更したい場合。
nnnnnn 2015年

@nnnnnn OK、出力で後処理を行いたい場合...しかし、後処理を行う新しいメソッドを生成し、古いメソッドをそのままにしておきます。これはリファクタリングが少し難しいだけですが、他の呼び出しが新しい形式と互換性があるかどうかを確認する必要があります。しかし、それはそれでも正当な理由です。
Maarten Bodewes、2015年

7

複数の出口点は、十分に小さい関数、つまり、全体として1つの画面長で表示できる関数には適しています。長い関数に同様に複数の出口点が含まれている場合は、関数をさらに細かく分割できることを示しています。

とはいえ、どうしても必要な場合を除いて、複数出口関数は避けます。より複雑な関数のあいまいな行の一部の迷子によるバグの痛みを感じました。


6

私はあなたに単一の出口パスを強制する恐ろしいコーディング標準を使用してきました、そしてその機能が些細なものでない場合、結果はほとんど常に非構造化スパゲッティです-あなたは多くのブレークに行き着き、邪魔になります。


言うまでもなく、if成功を返すかどうかを返す各メソッド呼び出しの前にあるステートメントをスキップするよう心がける必要があります:(
Maarten Bodewes

6

単一の出口点-他のすべてが等しい-はコードを大幅に読みやすくします。しかし、落とし穴があります。人気のある構造

resulttype res;
if if if...
return res;

偽物です。「res =」は「return」よりもはるかに優れています。これには単一のreturnステートメントがありますが、関数が実際に終了する複数のポイントがあります。

複数の戻り値(または "res =" s)を持つ関数がある場合は、単一の出口点を持ついくつかの小さな関数に分割することをお勧めします。


6

私の通常のポリシーは、コードを追加してコードの複雑さを大幅に軽減しない限り、関数の最後にreturnステートメントを1つだけ置くことです。実際、私はむしろEiffelのファンです。Eiffelは、returnステートメントがないことによって唯一のreturnルールを適用します(結果を入れるために自動作成された「result」変数があります)。

確かに、複数のリターンでコードを明確にできる場合があり、それらがない場合の明らかなバージョンよりも明確になります。複数のreturnステートメントなしでは理解できないほど複雑な関数がある場合は、より多くの再作業が必要であると主張することができますが、そのようなことについて実際的であると良い場合があります。


5

最終的にいくつかのリターンを超える場合は、コードに問題がある可能性があります。それ以外の場合、特にコードをよりクリーンにするときに、サブルーチンの複数の場所から戻ることができると便利な場合があることに同意します。

Perl 6:悪い例

sub Int_to_String( Int i ){
  given( i ){
    when 0 { return "zero" }
    when 1 { return "one" }
    when 2 { return "two" }
    when 3 { return "three" }
    when 4 { return "four" }
    ...
    default { return undef }
  }
}

このように書かれたほうがいい

Perl 6:良い例

@Int_to_String = qw{
  zero
  one
  two
  three
  four
  ...
}
sub Int_to_String( Int i ){
  return undef if i < 0;
  return undef unless i < @Int_to_String.length;
  return @Int_to_String[i]
}

これはほんの一例です


Okなぜこれが否決されたのですか?それは意見ではないようではありません。
ブラッドギルバート

5

ガイドラインとして、最後にシングルリターンに投票します。これは、一般的なコードのクリーンアップ処理に役立ちます...たとえば、次のコードを見てください...

void ProcessMyFile (char *szFileName)
{
   FILE *fp = NULL;
   char *pbyBuffer = NULL:

   do {

      fp = fopen (szFileName, "r");

      if (NULL == fp) {

         break;
      }

      pbyBuffer = malloc (__SOME__SIZE___);

      if (NULL == pbyBuffer) {

         break;
      }

      /*** Do some processing with file ***/

   } while (0);

   if (pbyBuffer) {

      free (pbyBuffer);
   }

   if (fp) {

      fclose (fp);
   }
}

Cコードで単一のリターンに投票します。しかし、ガベージコレクションがあり、.. finallyがブロックする言語でコーディングしている場合はどうでしょうか。
Anthony

4

これはおそらく珍しい見方ですが、複数のreturnステートメントが支持されると信じている人は、4つのハードウェアブレークポイントのみをサポートするマイクロプロセッサでデバッガを使用する必要がなかったと思います。;-)

「矢印コード」の問題は完全に正しいですが、複数のreturnステートメントを使用するときに解消すると思われる問題の1つは、デバッガーを使用している状況です。ブレークポイントを設定して、出口、つまり戻り条件が確実に表示されるようにするための便利なキャッチオールポジションはありません。


5
これは、別の種類の時期尚早の最適化です。特別な場合のために最適化してはいけません。コードの特定のセクションをよくデバッグしている場合は、コードの出口点の数だけではなく、それよりも多くの問題があります。
ウェッジ

デバッガにも依存します。
Maarten Bodewes

4

関数内のreturnステートメントが多ければ多いほど、その1つのメソッドはより複雑になります。returnステートメントが多すぎるのではないかと思われる場合は、その関数のコード行が多すぎるかどうかを確認してください。

しかし、1つまたは多くのreturnステートメントに問題はありません。一部の言語では、他の言語(C)よりも良い方法(C ++)です。

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