関数に出口点が1つしかないのはなぜですか?[閉まっている]


97

読みやすさと効率性が失われるため、コーディングの悪い方法として単一の出口点関数については常に耳にしました。反対側の主張を聞いたことがありません。

これはCSと関係があると思いましたが、この質問はcstheory stackexchangeで撃ち落とされました。



6
答えは常に正しい答えはないということです。多くの場合、複数の出口を使用してコーディングする方が簡単です。また、上記のコードを更新すると、同じ複数の出口があるため、コードの変更/拡張がより困難であることがわかりました。これらのケースバイケースの決定を行うことが私たちの仕事です。決定に常に「最良の」答えがある場合、私たちの必要はありません。
JS。

1
@finnwファシストmodは最後の2つの質問を削除し、それらが何度も何度も答えられるようにしました
Maarten Bodewes '19

質問の中で「議論する」という言葉にもかかわらず、私はこれが意見に基づく質問だとは本当に思っていません。良いデザインなどとはかなり関係があります。閉じる理由はないようですが、w / eです。
Ungeheuer 2016年

1
単一の出口点は、デバッグ、読み取り、パフォーマンス測定と調整、リファクタリングを簡素化します。これは客観的であり、実質的に重要です。初期のリターン(単純な引数チェック後)を使用すると、両方のスタイルの賢明なブレンドになります。単一の出口点の利点を考えると、戻り値でコードを散らかすことは、怠惰でずさんな不注意なプログラマーの証拠であり、少なくとも子犬を好まない可能性があります。
Rick O'Shea 2017

回答:


108

考え方にはさまざまな種類がありますが、それは主に個人的な好みに起因します。

1つは、出口点が1つしかない場合でも混乱が少ないことです。メソッドを通るパスが1つで、出口を探す場所がわかっています。マイナス側では、インデントを使用してネストを表す場合、コードは右に大きくインデントされ、ネストされたすべてのスコープをたどることが非常に難しくなります。

もう1つは、メソッドの本文全体で右側に5マイルインデントされていなくても、メソッドの本文で特定の条件がtrueであることを確認できるように、前提条件をチェックしてメソッドの開始時に早く終了できることです。これにより、通常、心配する必要のあるスコープの数が最小限に抑えられ、コードの追跡がはるかに容易になります。

3つ目は、好きなところにどこでも退出できることです。これは昔はもっと混乱していましたが、到達不能なコードを検出する構文カラーリングエディターとコンパイラーがあるため、処理がはるかに簡単になりました。

私は真ん中のキャンプにいます。単一の出口点を強制することは無意味な、または逆効果的な制限であるIMHOですが、メソッド全体でランダムに終了することは、特定のコードのビットが機能するかどうかを確認するのが困難になる、ロジックをたどるのが面倒な場合があります。実行されました。しかし、メソッドを「ゲート」することで、メソッドの本体を大幅に簡略化できます。


1
深いネストは、singe exitパラダイムでgo toステートメントを使用することで回避できます。さらに、関数のローカルErrorラベルの下でいくつかの後処理を実行する機会が得られますが、これは複数returnのsでは不可能です。
Ant_222、2015年

2
通常、移動の必要性を回避する優れたソリューションがあります。私は 'return(Fail(...))'し、共有クリーンアップコードをFailメソッドに入れることを好みます。これには、メモリを解放するためにいくつかのローカルを渡す必要がある場合がありますが、コードのパフォーマンスが重要なビットでない限り、これは通常、goto IMOよりもはるかにクリーンなソリューションです。また、いくつかのメソッドで同様のクリーンアップコードを共有することもできます。
Jason Williams

客観的な基準に基づいた最適なアプローチがありますが、私たちは考えの集まり(正しいものと正しくないもの)があり、個人的な好み(正しいアプローチに対する好意または反対)に帰着することに同意できます。
Rick O'Shea

39

私の一般的な推奨事項は、returnステートメントは、実用的な場合は、副作用のある最初のコードの前か、副作用のある最後のコードの後に​​配置することです。私は次のようなものを検討します:

  if(!argument)//非nullかどうかを確認
    ERR_NULL_ARGUMENTを返します。
  ... null以外の引数を処理する
  もし(ok)
    0を返します。
  そうしないと
    ERR_NOT_OKを返します。

より明確:

  int return_value;
  if(argument)//非null
  {
    .. null以外の引数を処理する
    ..結果を適切に設定
  }
  そうしないと
    結果= ERR_NULL_ARGUMENT;
  結果を返す;

特定の条件によって関数が何も実行できない場合は、関数が何を実行するかよりも上の位置で、関数から早期に復帰することをお勧めします。ただし、関数が副作用を伴うアクションを実行したら、すべての副作用を処理する必要があることを明確にするために、下から戻ることを好みます。


ok変数を管理する最初の例は、私にとっては単一リターンのアプローチのように見えます。また、もし-elseブロックは単純に書き換えることができます:return ok ? 0 : ERR_NOT_OK;
Melebius

2
最初の例では、最初に、returnすべてを実行するすべてのコードの前にa があります。?:演算子の使用に関しては、それを別々の行に書き込むことで、多くのIDEでデバッグブレークポイントをnot-okシナリオに接続することが容易になります。ところで、「単一の出口点」の真の鍵は、重要なのは、通常の関数への特定の呼び出しごとに、出口点が呼び出し直後の点であることを理解することにあります。今日のプログラマーはそれを当たり前のことと考えていますが、常にそうであるとは限りませんでした。いくつかのまれなケースでコードが...機能につながる、スタックスペースなしで取得する必要があり
supercat

...条件付きまたは計算されたgotoを介して終了します。一般に、アセンブリ言語以外でプログラムするのに十分なリソースがあるものは、スタックをサポートできますが、非常に厳しい制約(ある場合にはZEROバイトのRAMまで)の下で動作する必要のあるアセンブリコードを記述しました。そのような場合、複数の出口点があると役立ちます。
スーパーキャット2016年

1
いわゆるより明確な例は、はるかに不明確で読みにくいです。1つの出口点は、常に読みやすく、保守しやすく、デバッグしやすいです。
GuidoG 2016年

8
@GuidoG:省略されたセクションに表示される内容によっては、どちらのパターンも読みやすくなる場合があります。「return x;」の使用 ステートメントに到達した場合、戻り値はxであることを明確にします。「result = x;」を使用する 結果が返される前に、何かが結果を変更する可能性を残します。実際に結果を変更する必要がある場合に役立ちますが、コードを調べるプログラマは、答えが「できない」としても、結果がどのように変化するかを確認する必要があります。
スーパーキャット2016年

15

単一の入り口と出口は、構造化プログラミングと段階的なスパゲッティコーディングの独自のコンセプトでした。変数に割り当てられたメモリ空間を適切にクリーンアップする必要があるため、複数の出口点関数にはより多くのコードが必要であると考えられています。関数が変数(リソース)を割り当て、適切なクリーンアップなしに関数から早期に抜けると、リソースリークが発生するシナリオを考えてみます。さらに、すべての出口の前にクリーンアップを構築すると、多くの冗長なコードが作成されます。


これはRAIIの問題ではありません
BigSandwich

14

ほとんど何でも、それは成果物のニーズに帰着します。「昔」では、複数のリターンポイントを持つスパゲッティコードはメモリリークを引き起こしました。これは、その方法を好んだコーダーは通常、うまくクリーンアップできなかったためです。ネストされたスコープから戻る場合、スタックが戻り中にポップされたため、一部のコンパイラーが戻り変数への参照を「失う」という問題もありました。より一般的な問題は、関数の呼び出し状態を戻り状態とまったく同じにしようとする再入可能なコードの1つでした。oopのミューテーターはこれに違反し、コンセプトは棚上げされました。

複数の出口点が提供する速度を必要とする成果物、特にカーネルがあります。これらの環境には通常、独自のメモリとプロセス管理があるため、リークのリスクが最小限に抑えられます。

個人的には、returnステートメントにブレークポイントを挿入し、コードがそのソリューションをどのように決定したかについてコードインスペクションを実行するために、それを使用することが多いので、私は単一の出口を持っているのが好きです。私は入り口に行って一歩進むことができました。これは、広範囲にネストされた再帰的なソリューションで行います。コードレビュアーとして、関数の複数の戻り値には、より深い分析が必要です。つまり、実装を高速化するためにそれを実行している場合、Peterを奪ってPaulを救っています。コードレビューにはより多くの時間が必要となり、効率的な実装の推定が無効になります。

-2セント

詳細については、このドキュメントを参照してください:NISTIR 5459


8
multiple returns in a function requires a much deeper analysis機能がすでに大きい場合にのみ(> 1画面)、それ以外の場合は分析が容易になります
dss539

2
複数のリターンによって分析が容易になることはなく、反対のことです
GuidoG

1
リンクは無効です(404)。
fusi 2016

1
@fusi-archive.orgで見つけ、ここのリンクを更新
sscheider 2017

4

私の見解では、関数(または他の制御構造)を1点のみで終了するというアドバイスは、しばしば売られ過ぎです。通常、1つのポイントでのみ終了する2つの理由が挙げられます。

  1. 単一出口コードは、おそらく読みやすく、デバッグも簡単です。(私はこの理由をあまり考えていないと認めますが、それは与えられています。実質的に読みやすく、デバッグが簡単なのは、単一エントリーのコードです。)
  2. 単一出口コードはリンクし、よりきれいに戻ります。

2番目の理由は微妙であり、特に関数が大きなデータ構造を返す場合、いくつかのメリットがあります。しかし、私はそれをあまり心配しません...

学生の場合、クラスで最高の成績を収めたいと考えています。インストラクターが好むことを行います。彼はおそらく彼の観点からは正当な理由があります。したがって、少なくとも、あなたは彼の見方を学ぶでしょう。これ自体に価値があります。

幸運を。


4

私はかつては単一出口スタイルの擁護者でした。私の推論は主に痛みから来ました...

単一出口の方がデバッグが簡単です。

私たちが現在持っている技術とツールを考えると、単体テストとロギングによって単一の出口が不要になるため、これははるかに妥当な立場ではありません。つまり、デバッガーでコードの実行を監視する必要がある場合、複数の出口点を含むコードを理解して操作することははるかに困難でした。

これは、状態を調べるために割り当てを挿入する必要がある場合に特に当てはまりました(最新のデバッガーではウォッチ式に置き換えられました)。また、問題を隠したり、実行を完全に壊したりする方法で制御フローを変更することも簡単すぎました。

単一出口メソッドは、デバッガーでステップスルーするのが簡単で、ロジックを壊すことなく分解するのが簡単でした。


0

答えはコンテキストに大きく依存します。GUIを作成していて、APIを初期化し、メインの開始時にウィンドウを開く関数がある場合、エラーがスローされる可能性がある呼び出しでいっぱいになり、それぞれがプログラムのインスタンスを閉じます。ネストされたIFステートメントを使用してインデントすると、コードがすぐに右に歪む可能性があります。各ステージでエラーを返す方が、コードにいくつかのフラグを付けてデバッグするのと同じくらい簡単である一方で、よりよく、実際にはより読みやすいかもしれません。

ただし、メソッドの結果に応じてさまざまな条件をテストし、さまざまな値を返す場合は、単一の出口点を持つことをお勧めします。以前は、MATLABで非常に大きくなる可能性のある画像処理スクリプトを扱っていました。複数の出口点があると、コードの追跡が非常に難しくなる可能性があります。switchステートメントの方がはるかに適切でした。

最善の方法は、学習しながら学ぶことです。何かのコードを書いているなら、他の人のコードを見つけて、彼らがどのようにそれを実装しているか見てみてください。好きなビットと嫌いなビットを決めます。


-5

関数に複数の出口点が必要だと思われる場合は、関数が大きすぎて実行しすぎています。

Robert C. Martinの本「Clean Code」の関数に関する章を読むことをお勧めします。

基本的に、関数は4行以下のコードで作成する必要があります。

Mike Longのブログからのメモ:

  • 関数の最初のルール:それらは小さいはずです
  • 関数の2番目のルール:それらはそれよりも小さくなければなりません
  • ifステートメント内のブロック、whileステートメント、forループなどは1行の長さにする必要があります
  • …そしてそのコード行は通常関数呼び出しになります
  • インデントのレベルは1つまたは2つ以下にする必要があります
  • 関数は1つのことを行う必要があります
  • 関数ステートメントはすべて同じ抽象化レベルである必要があります
  • 関数の引数は3つ以下にする必要があります
  • 出力引数はコードのにおいです
  • 関数にブールフラグを渡すことは本当にひどいです。定義上、関数で2つのことを実行しています。
  • 副作用は嘘です。

29
4行?そのような単純さを可能にするコードは何ですか?Linuxカーネルやgitなどがそうすることは本当に疑わしい。
しんぞう2016年

7
「ブールフラグを関数に渡すのは本当にひどいです。定義上、関数で2つのことを実行しています。」定義により?いいえ...そのブール値は4行のうち1行にのみ影響を与える可能性があります。また、関数のサイズを小さく保つことに同意しますが、4つは少し制限が多すぎます。これは非常に緩いガイドラインとして解釈する必要があります。
ジェシー

12
このような制限を追加すると、コードが混乱しやすくなります。それは、メソッドが簡潔で簡潔であり、不必要な副作用なしに、意図されていることだけを行うことにこだわっているということです。
ジェシー

10
私が何度も反対票を投じることができればと願うSOのまれな答えの1つ。
Steven Rands 2017

8
残念ながら、この回答は複数のことを行っており、おそらく4行未満で他の複数に分割する必要があります。
Eli
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.