早期returnは可読性の味方なのか、それともアンチパターンなのか。海外のコミュニティや公式ドキュメントを手がかりに、賛否と背景を整理しました。あなたはどっち派!?
なぜ議論になるのか
同じ「関数からreturnする処理」でも「早期に戻る実装」(早期return派)と「単一出口で最後に戻る実装」(単一出口派)があり、可読性・保守性・安全性といった点で評価が割れています。X(旧Twitter)でも「ガード節で先に弾けばネストが浅くなって読みやすい」「出口は1つにすべき。後始末やログが散らばると危ない」「deferやfinallyがある言語なら早期returnが自然」「安全規格がある現場では単一出口が無難」といった声が交錯しています。さらに技術ブログやQ&Aでも、読みやすさを重視する立場と、後始末の一元化や規約順守を重視する立場がせめぎ合っています。
海外ではどうなっているのか
では、海外(英語圏)ではどんな議論がされているのでしょうか。Stack Overflowではもはや定番の議論になっているようで、例えば以下のようになっています。
Software Engineering Stack Exchangeでも同趣旨の質問が周期的に立ち上がり、過去回答の引用→新たな反論→折衷案の提示という流れが繰り返されています。「読みやすさを最優先する流儀」と「後始末や監査を一元化したい流儀」がせめぎ合っているのが実情です。
善か悪か…果たして結論はどっちなのでしょう。具体的にそれぞれの派閥の主張を見てみましょう。
早期return派
早期return派の代表的な主張は以下の通り。
-
可読性・ネスト削減
前提違反や異常系を関数冒頭で「先に弾く(ガード節)」ことで、多段ifを避け、正常系を上から下へ一直線に読めるようにできる。また、レビューで意図を追いやすくなる。 -
テスト容易性・責務の明確化
無効入力・権限不足・外部依存の失敗などを短い早期returnで分離すると、異常系テストが個別に書きやすく、関数の責務も見えやすくなる。結果として小関数化とも相性が良い。 -
言語機能が後押し
with(コンテキストマネージャ)や try/except により後始末が自動・明示できる言語では、出口が複数でも安全にできます。ログや計測はデコレータ等で入口/出口フックに寄せられるため、出口1つに依存しない設計が取りやすい。
pythonでこれらの例を見てみましょう。
from pathlib import Path
import json
def load_user(email: str | None, path: str) -> dict | None:
if not email: # ① 無効入力は先に戻る
return None
p = Path(path)
if not p.is_file(): # ② 事前条件も先に戻る
return None
with p.open(encoding="utf-8") as f: # 途中でreturnしても自動クローズ
try:
data = json.load(f)
except json.JSONDecodeError: # ③ 異常系はここで終了
return None
user = data.get(email)
if not user or not user.get("active"):
return None
return user # ← 正常系が一直線
単一出口派
単一出口派の代表的な主張は以下の通りです。
-
後始末の一元化・漏れ防止
ファイルやロック、トランザクションの解放・ロールバック・終了時ログなどを関数末尾に集約できるため、取りこぼしを避けやすい。例外や分岐が増えても、最後で必ず片付ける方針を守れる。 -
ロギング/監査/計測の一点集中
終了処理を1か所に置くと、ブレークポイントや計測、監査ログの出力をそこに集められる。出口が複数のときに発生しがちな「ある経路だけログが無い」を避けやすい。 -
チーム規約・静的解析に適合
「関数は単一の出口」とする社内コーディング標準や静的解析ルールに素直に従えるのが利点。安全寄りの運用や長期保守の現場で好まれる。
同じくpythonでこれらの例を見てみましょう。
from pathlib import Path
import json
from typing import Optional
def load_user_single_exit(email: Optional[str], path: str) -> Optional[dict]:
result: Optional[dict] = None
f = None
try:
# 事前条件チェック(returnはせず、結果だけ決める)
if email and Path(path).is_file():
f = open(path, encoding="utf-8")
try:
data = json.load(f)
user = data.get(email)
if user and user.get("active"):
result = user
except json.JSONDecodeError:
pass # 異常時は result を None のまま
finally:
if f:
f.close() # 資源解放を1か所で確実に
# ここに終了ログ・計測・監査フックなどを集約できる
return result # ← 返り値は最後の1回だけ
結局どっちが善でどっちが悪なの?
再び海外でどう言われているのか見てみましょう。
早期returnを後押しする声
“The code reads well if the successful flow of control runs down the page, eliminating error cases as they arise.”
— Effective Go(エラー処理の流儀)
https://go.dev/doc/effective_go
“Always opportunistically return as soon as possible from the function. Once your work is done, get the heck out of there!”
— Jeff Atwood “Flattening Arrow Code”
https://blog.codinghorror.com/flattening-arrow-code/
“the
Err(err)
branch expands to an early return.”
— Rust By Example(?
演算子の解説)
https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html
単一出口を推す声(例)
“A function should have a single point of exit at the end.”
— MISRA C:2012 Rule 15.5(車載など安全規格系)
https://www.mathworks.com/help/bugfinder/ref/misrac2012rule15.5.html
“It is a preferred practice that all functions shall have just one exit point.”
— Barr Group Embedded C Coding Standard
https://barrgroup.com/62-functions
ご覧の通り、早期returnを支持する公式/実務の言説と、単一出口を求める標準/規約の言説が並立しています。どちらを善とみなすかは、言語機能・安全規格・可観測性設計といった前提に強く左右されます(注記:MISRA C:2025ではDisappliedになったそうです。Xでご指摘を頂きました)。
善悪ではなく適材適所です
折衷・文脈依存を示す声
以上のような状況から「単純な引数チェックなどは早期returnで弾き、重い後始末は末尾に寄せる」といった折衷案がしばしば提示されています。また、「出口を1つにすると監査や計測を集約しやすい」という意見と並んで、「入口/出口フック(デコレータ、AOP、ミドルウェア)を使えば出口の数に依存しない」という設計論も繰り返し登場します。要するに、どの言語機能・プラットフォームを備えているかが選択を左右する、という立場が広く共有されています。
結論、言語・業界/業種に依存する
-
言語機能が強い場合(Go/Swift/Rust/Python 等)
defer/guard/?/with/try-finally で後始末を自動化/明示化できるため、早期returnで異常系を先出ししても安全に運用できます。 -
C/C系や既存資産が大きい現場
例外や自動解放が薄いコードベースでは、解放・ロールバック・終了ログを末尾一点に集約したい動機が強く、単一出口が選ばれがちです(社内規約や静的解析ルール次第ですが)。 -
安全クリティカル/組込み(車載・医療・産業制御)
規格・標準・監査要件が明確に存在し、単一出口を前提とする方針がある場合はそちらを優先。レビューや監査の観点で出口一点主義が運用しやすいからです。安全・セキュア・高信頼の場合はこちらを採用します。 -
Web/業務アプリ/SaaS
失敗を冒頭で素早く返す(バリデーション先出し)ほうが、可読性・レビュー効率・テスト設計の面でメリットが大きいことが多いです。ログやメトリクスは共通ミドルで担保し、出口数に依存しない運用を取ります。 -
高並行・ロック多用の領域
ロック解放はwith/using/deferなどスコープ終端で必ず後始末の仕組みがあるなら早期returnでも安全。仕組みが弱い/徹底できないなら単一出口+末尾集約が堅実です。
まとめると、言語機能・規格要件・アーキテクチャの三点セットで正解が変わります。どちらかを教条的に禁ずるのではなく、プロジェクトの前提に合わせてデフォルト方針を決め、例外的にもう一方を使う——この適材適所が海外の実務的コンセンサスのようです。
これらの議論の共有もなしにいきなりレビューで指摘するのはなしですよ!
(本記事は弊社ブログに2025/09/02に掲載した記事の著者自身による転載です。)
参考リンク
- Effective Go(エラー処理の章の引用): https://go.dev/doc/effective_go
- Coding Horror「Flattening Arrow Code」: https://blog.codinghorror.com/flattening-arrow-code/
- Rust By Example「
?
演算子」: https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html - MISRA C:2012 Rule 15.5: https://www.mathworks.com/help/bugfinder/ref/misrac2012rule15.5.html
- Barr Group Embedded C Coding Standard(6.2 Functions): https://barrgroup.com/62-functions
- Stack Overflow: 「Should a function have only one return statement?」: https://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement
- Stack Overflow: 「Why should a function have only one exit-point?」: https://stackoverflow.com/questions/4838828/why-should-a-function-have-only-one-exit-point