関数には return ステートメントが 1 つだけ必要ですか?
-
09-06-2019 - |
質問
関数内に return ステートメントを 1 つだけ使用する方が良い理由はありますか?
それとも、関数から戻るのが論理的に正しい場合はすぐに関数から戻っても問題ありません。つまり、関数内に多くの return ステートメントが存在する可能性があるということです。
解決
私は、「簡単な」状況のためにメソッドの先頭にいくつかのステートメントを返すことがよくあります。たとえば、これは次のとおりです。
public void DoStuff(Foo foo)
{
if (foo != null)
{
...
}
}
...次のようにして (私見ですが) 読みやすくすることができます。
public void DoStuff(Foo foo)
{
if (foo == null) return;
...
}
はい、関数/メソッドから複数の「終了ポイント」があっても問題ないと思います。
他のヒント
誰も言及も引用もしていない コードの完成 だからやります。
17.1 リターン
各ルーチンでのリターンの数を最小限に抑える. 。ルーチンの下部を読んでいて、上部のどこかに戻った可能性があることに気づいていない場合、ルーチンを理解するのは難しくなります。
使う 戻る 可読性が向上する場合. 。特定のルーチンでは、答えがわかったら、それを呼び出し元のルーチンにすぐに返したくなることがあります。クリーンアップを必要としない方法でルーチンが定義されている場合、すぐに戻らないということは、さらにコードを記述する必要があることを意味します。
このテクニックが実際に役立つことがわかったので、複数の出口ポイントに対して恣意的に決定するのは非常に賢明ではないと言えます。 何度も何度も, 、実際私もよくあるのですが、 既存のコードをリファクタリングした わかりやすくするために複数の出口ポイントにします。このように 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;
}
後者の方がかなりわかりやすいと思います。私の知る限り、複数の出口点に対する批判は、今日ではかなり時代遅れの視点です。
私は現在、コードベースに取り組んでいますが、そのコードベースに取り組んでいる 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 条件と中括弧が適切に一致していることを確認してください。
この方法では、コードのメンテナンスが非常に困難になり、エラーが発生しやすくなります。
構造化プログラミング 関数ごとに return ステートメントは 1 つだけ持つべきだと述べています。これは複雑さを制限するためです。Martin Fowler などの多くの人は、複数の return ステートメントを含む関数を記述する方が簡単だと主張しています。彼はこの議論を古典の中で提示しています リファクタリング 彼が書いた本。彼の他のアドバイスに従って小さな関数を作成すると、これはうまく機能します。私はこの観点に同意しており、関数ごとに 1 つの return ステートメントを遵守するのは厳格な構造化プログラミング純粋主義者だけです。
Kent Beck がガード条項について議論しているときに指摘しているように、 実装パターン ルーチンの入口点と出口点を 1 つだけにする...
「同じルーチンの多くの場所に出入りするときに、混乱を防ぐことでした。どのステートメントが実行されたかを理解することが大変な作業であった多くのグローバルデータで書かれたFortranまたはアセンブリ言語プログラムに適用されると、それは理にかなっています...小規模な手法とほとんどがローカル データであるため、不必要に保守的になります。」
ガード句を使用して書かれた関数は、長くネストされた一連の関数よりもはるかに理解しやすいと思います。 if then else
発言。
副作用のない関数では、複数の戻り値を持つ理由はなく、関数型スタイルで関数を記述する必要があります。副作用のあるメソッドでは、処理がより順次的になる (時間インデックスが付けられる) ため、実行を停止するコマンドとして return ステートメントを使用して、命令型スタイルで記述します。
言い換えれば、可能であれば、このスタイルを優先してください
return a > 0 ?
positively(a):
negatively(a);
この上に
if (a > 0)
return positively(a);
else
return negatively(a);
ネストされた条件を複数の層で記述していることに気付いた場合は、述語リストなどを使用して、それをリファクタリングできる方法がおそらくあります。if と else が構文的に大きく離れていることがわかった場合は、それをより小さな関数に分割することをお勧めします。条件付きブロックが 1 画面分のテキストを超えると、読みにくくなります。
すべての言語に適用される厳格なルールはありません。return ステートメントが 1 つだけあるようなものでは、コードが適切になるわけではありません。しかし、優れたコードでは、そのように関数を記述できる傾向があります。
C から引き継いだ C++ のコーディング標準でこれを見たことがあります。RAII やその他の自動メモリ管理がない場合、リターンのたびにクリーンアップする必要があり、これはカット アンド ペーストを意味します。クリーンアップまたは goto (論理的にはマネージ言語の「finally」と同じ) であり、どちらも悪い形式とみなされます。C++ または別の自動メモリ システムでスマート ポインターとコレクションを使用することを実践している場合、それに強い理由はなく、読みやすさがすべてとなり、判断が求められることになります。
私はステートメントを返すという考えに傾いています。 真ん中 機能が悪い。return を使用して関数の先頭にいくつかのガード句を構築することもできます。もちろん、問題なく関数の最後に何を返すかをコンパイラに指示することもできますが、 真ん中 関数の部分は見落としやすく、関数の解釈が難しくなる可能性があります。
関数内に return ステートメントを 1 つだけ使用する方が良い理由はありますか?
はい, 、 がある:
- 単一の出口ポイントは、事後条件をアサートするのに最適な場所です。
- 関数の最後の 1 つの戻り値にデバッガー ブレークポイントを設定できると、多くの場合便利です。
- 戻り値が少ないほど、複雑さが軽減されます。一般に、リニア コードの方が理解しやすいです。
- 関数を 1 つの戻り値に単純化しようとすると複雑さが生じる場合、より小さく、より一般的で、理解しやすい関数にリファクタリングする動機になります。
- デストラクターのない言語を使用している場合、または RAII を使用していない場合は、1 回のリターンによってクリーンアップする必要がある箇所の数が減ります。
- 一部の言語では、単一の終了ポイントが必要です (Pascal や Eiffel など)。
この疑問は、複数の return または深くネストされた if ステートメントの間の誤った二分法として提起されることがよくあります。ほとんどの場合、出口ポイントが 1 つだけの非常に線形な (深いネストがない) 3 番目の解決策が存在します。
アップデート:どうやら MISRA ガイドラインは単一出口を促進します, 、 あまりにも。
はっきり言っておきますが、そうだと言っているわけではありません いつも 複数の返品があるのは間違いです。しかし、その他の点では同等のソリューションがあるとすれば、リターンが 1 つのソリューションを優先する十分な理由はたくさんあります。
終了ポイントが 1 つあると、関数の最後に 1 つのブレークポイントを設定して、実際にどのような値が返されるかを確認できるため、デバッグに有利です。
一般に、関数の終了点は 1 つだけにするようにしています。ただし、そうすることで実際には必要以上に複雑な関数本体が作成されてしまう場合があります。その場合は、複数の終了ポイントを用意した方がよいでしょう。実際には、結果として生じる複雑さに基づいて「判断を下す」必要がありますが、目標は、複雑さと理解しやすさを犠牲にすることなく、出口ポイントをできるだけ少なくする必要があります。
いや、だって 私たちはもう 1970 年代には生きていない. 。関数が長すぎて複数の戻りが問題になる場合は、長すぎます。
(例外のある言語の複数行関数には複数の終了点があるという事実とはまったく異なります。)
私の好みは、物事が本当に複雑にならない限り、単一の出口を選択することです。場合によっては、複数の存在ポイントが他のより重大な設計上の問題を覆い隠してしまう可能性があることがわかりました。
public void DoStuff(Foo foo)
{
if (foo == null) return;
}
このコードを見たとき、私はすぐにこう尋ねます。
- 'foo' が null になることはありますか?
- もしそうなら、「DoStuff」の何人のクライアントがヌルの「foo」で関数を呼び出したことがあるでしょうか?
これらの質問に対する答えによっては、次のことが考えられます。
- 決して真ではないため、チェックは無意味です。それは主張であるべきです)
- このチェックが真になることはほとんどないため、いずれにせよ何らかの他のアクションを実行する必要があるため、特定の呼び出し元関数を変更する方が良いかもしれません。
上記のどちらの場合でも、アサーションを使用してコードを再加工して、「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 つにする傾向がありますが、読みやすい場合はコードに複数の出口ポイントを持たせるようにします。
強制的に 1 つだけを使用する return
ある意味コード臭が発生するためです。説明しましょう:
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);
}
確かに、これは長くて少し面倒ですが、この方法で関数をリファクタリングする過程で、
- 再利用可能な関数を多数作成しました。
- 関数をより人間が読みやすいものにし、
- 関数の焦点は、値が正しい理由にあります。
必要な数だけ、またはコードをきれいにするもの (たとえば、 ガード条項).
私個人としては、return ステートメントを 1 つだけ持つべきだという「ベスト プラクティス」を聞いたことも見たこともありません。
ほとんどの場合、私はロジック パスに基づいてできるだけ早く関数を終了する傾向があります (ガード句はその好例です)。
(C# で作成したコードでは) 複数の戻り値が通常は適切であると思います。シングルリターンのスタイルは C からの名残です。しかし、おそらく C でコーディングしているわけではありません。
すべてのプログラミング言語において、メソッドの出口ポイントを 1 つだけ要求するという法律はありません。. 。このスタイルの優位性を主張し、それを「規則」や「法律」にまで格上げする人もいますが、この信念はいかなる証拠や研究によっても裏付けられていません。
複数の戻りスタイルは、リソースの割り当てを明示的に解除する必要がある C コードでは悪い習慣になる可能性がありますが、自動ガベージ コレクションや自動ガベージ コレクションなどの構造を持つ Java、C#、Python、JavaScript などの言語では、 try..finally
ブロック(そして using
C# のブロック)、この引数は当てはまりません。これらの言語では、リソースの割り当てを一元的に手動で解除する必要があることは非常にまれです。
単一のリターンの方が読みやすい場合とそうでない場合があります。コードの行数が減るか、ロジックが明確になるか、中括弧やインデント、一時変数の数が減るかどうかを確認してください。
したがって、リターンは技術的な問題ではなく、レイアウトと読みやすさの問題であるため、芸術的感性に合う限り多くのリターンを使用してください。
について話しました これについては私のブログで詳しく説明します.
出口が 1 つであることについては良いこともありますが、避けられないことについては悪いこともあります。 「矢」 結果を生み出すプログラミング。
入力検証またはリソース割り当て中に複数の終了ポイントを使用する場合、すべての「エラー終了」を関数の先頭にわかりやすく配置するようにしています。
どちらも スパルタプログラミング 「SSDSLPedia」の記事と 単一関数の出口点 「Portland Pattern Repository's 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 ステートメントは使用しません (または、少しの労力で使用できる場合はまったく使用しません)。コードははるかに単純になります。そして矢印ではありません。そして、確定申告以外の場合は、ほとんどが例外となるでしょう。
要するに;
- 少数のリターンは多くのリターンより優れています
- 巨大な矢よりも複数のリターンの方が優れており、 ガード条項 概ね大丈夫です。
- 可能であれば、例外はおそらくほとんどの「ガード句」を置き換えることができます/置き換えるべきです。
あなたはこの格言を知っています - 美は見る人の目にある.
と誓う人もいます NetBeans そしていくつかは インテリJアイデア, 、いくつかによって パイソン そしていくつかは PHP.
一部の店では、次のことを要求すると仕事を失う可能性があります。
public void hello()
{
if (....)
{
....
}
}
問題は可視性と保守性です。
私はブール代数を使用してロジックとステート マシンの使用を削減および簡素化することに夢中になっています。しかし、私がコーディングに「数学的手法」を使用することは、目に見えず、保守性も低いため、不適切であると信じていた過去の同僚もいました。そしてそれは悪い習慣になります。皆さん、申し訳ありませんが、私が採用しているテクニックは、私にとって非常にわかりやすく、保守しやすいものです。なぜなら、6 か月後にコードに戻ると、ことわざのようなスパゲッティの混乱を見るよりも、コードを明確に理解できるからです。
おい、相棒(元クライアントがよく言っていたように)、私が修正してほしいときに修正方法を知っている限り、好きなようにしてください。
20年前、私の同僚が今日で言うところの雇用を理由に解雇されたことを覚えています。 アジャイル開発 戦略。彼は綿密な段階的計画を立てていました。しかし、彼のマネージャーは彼に「ユーザーに機能を段階的にリリースすることはできない!」と怒鳴っていました。必ず守らなければなりません 滝」 マネージャーに対する彼の返答は、段階的な開発の方が顧客のニーズにより正確に対応できる、というものでした。彼は顧客のニーズに合わせて開発することを信じていましたが、マネージャーは「顧客の要求」に合わせてコーディングすることを信じていました。
私たちはデータの正規化を破ることで罪を犯していることがよくありますが、 MVP そして MVC 境界線。関数を構築する代わりにインライン化します。私たちは近道をします。
個人的には、PHP は悪い習慣だと信じていますが、私は何を知っていますか。すべての理論的議論は、結局のところ、一連のルールを満たそうとすることに帰着します。
品質=精度、保守性、収益性。
他のすべてのルールは背景に消えていきます。そしてもちろん、このルールは決して消えることはありません。
怠inessは良いプログラマーの美徳です。
私は、ガード句を使用して早期にリターンするか、そうでない場合はメソッドの最後で終了することを好みます。単一の開始および終了ルールには歴史的な重要性があり、複数のリターン (および多くの欠陥) を伴う単一の C++ メソッドで A4 10 ページに及ぶレガシー コードを扱う場合に特に役立ちました。最近では、メソッドを小さく保つことで、複数の終了が理解の妨げにならないようにするのが良い方法として受け入れられています。上記からコピーした次の Kronoz の例では、問題は何が起こるかです。 // 残りのコード...?:
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;
}
この例が多少不自然であることは承知していますが、リファクタリングしたくなるでしょう。 フォーリーチ 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;
}
私が思いつく正当な理由の 1 つは、コードのメンテナンスのためです。出口は 1 つだけです。結果の形式を変更したい場合は、実装がはるかに簡単です。また、デバッグの場合は、そこにブレークポイントを貼り付けるだけです:)
そうは言っても、私はかつて、コーディング標準によって「関数ごとに 1 つの return ステートメント」が課せられているライブラリで作業しなければならなかったことがありますが、それはかなり大変だと感じました。私は数値計算のコードをたくさん書きますが、「特殊な場合」がよくあるため、非常に理解しにくいコードになってしまいました...
十分に小さな関数、つまり 1 つの画面の長さで全体を表示できる関数の場合は、複数の終了ポイントで問題ありません。長い関数に同様に複数の終了ポイントが含まれている場合、それは関数をさらに細分化できることを示しています。
つまり、複数の出口関数を避けています 絶対に必要な場合を除いて. 。私は、より複雑な関数のあいまいな行での迷走リターンによるバグの痛みを感じたことがあります。
私は、単一の終了パスを強制するひどいコーディング標準を使用して作業してきましたが、関数が些細なものであれば、その結果はほとんどの場合、構造化されていないスパゲッティになります。つまり、大量の中断と継続が発生し、邪魔になるだけです。
単一の出口ポイント - 他のすべての条件が等しい - により、コードが大幅に読みやすくなります。しかし、落とし穴があります。人気の建設
resulttype res;
if if if...
return res;
は偽物です。「res=」は「return」よりも優れているわけではありません。return ステートメントは 1 つですが、実際に関数が終了するポイントは複数あります。
複数の戻り値 (または "res=") を含む関数がある場合、多くの場合、単一の終了点を持つ複数の小さな関数に分割することをお勧めします。
私の通常のポリシーは、コードを追加してコードの複雑さが大幅に軽減されない限り、関数の最後には return ステートメントを 1 つだけ置くことです。実際、私はどちらかというと Eiffel のファンです。Eiffel は return ステートメントを持たずに、唯一の return ルールを適用します (結果を入れるための自動作成された「result」変数があるだけです)。
確かに、複数のリターンを使用すると、それらを含まない明白なコードよりもコードが明確になる場合があります。関数が複雑すぎて複数の return ステートメントなしでは理解できない場合は、さらに再作業が必要になるという意見もありますが、場合によっては、そのようなことについては現実的であることが良いこともあります。
数回以上のリターンが発生した場合は、コードに問題がある可能性があります。それ以外の場合は、特にコードがすっきりする場合には、サブルーチン内の複数の場所から戻れると便利な場合があることに同意します。
パール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 }
}
}
このように書いた方が良いでしょう
パール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]
}
これは単なる簡単な例であることに注意してください
私はガイドラインとして最後にシングルリターンに投票します。これは、 共通コードのクリーンアップ処理 ...たとえば、次のコードを見てください...
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);
}
}
これはおそらく珍しい視点ですが、複数の return ステートメントが優先されるべきだと信じる人は、4 つのハードウェア ブレークポイントのみをサポートするマイクロプロセッサでデバッガを使用する必要がなかったと思います。;-)
「アロー コード」の問題は完全に正しいですが、デバッガーを使用している状況では、複数の return ステートメントを使用すると解決するように見える問題が 1 つあります。終了と戻り条件の確認を保証するブレークポイントを設定するための便利な包括的な位置はありません。
関数内の return ステートメントが増えるほど、その 1 つのメソッドの複雑さが増します。return ステートメントが多すぎるのではないかと思った場合は、その関数のコード行が多すぎるかどうかを自問するとよいでしょう。
しかし、そうではありません。1 つまたは複数の return ステートメントには何も問題はありません。一部の言語では、他の言語 (C) よりもこの方法が推奨されます (C++)。