---
title: "アーリーリターン（early return）とは？ガード節の書き方と言語別の実装・使わない場面"
url: "https://www.issoh.co.jp/tech/details/2700/"
published: 2024-06-21
updated: 2026-10-26
categories: ["開発"]
publisher: "株式会社一創"
---

# アーリーリターン（early return）とは？ガード節の書き方と言語別の実装・使わない場面

アーリーリターン（early return、早期リターン）は、処理の途中で結果が確定した場合などに、`return` で関数から早く抜ける書き方です。異常系を先に返すと、本来の処理が `if` の奥に沈まず、関数を上から下へ一直線に読めます。この記事では、ネストした条件分岐の書き換え方、ループでの `continue`、6言語での書き方、リンターによる不要なelseの検出と自動修正の限界を実測付きで示します。後半では「returnは1つにすべき」という単一出口原則と、Appleのgoto failバグから分かる使うべきでない場面も扱います。

## まとめ

アーリーリターンは、本筋の処理に進まないケースを先に返し、正常系をインデントの浅い位置に残す手法です。こうして先に判定して抜ける `if ...: return` を**ガード節（guard clause）**と呼びます。この書き方は『リーダブルコード』7.5節「関数から早く返す」とファウラーのリファクタリングカタログで紹介され、Go・CakePHPのコーディング規約も推奨しています。

- 使う場面：引数の検証・対象外データの除外、本筋から外れる特別なケースの先出し。ループでは `continue` が同じ役割を担う。
- 効果：手元の計測で、ifが3段に入れ子になった関数を書き換えると認知的複雑度は9から3に下がった。一方、サイクロマティック複雑度は4のまま変わらない。
- 検出：ESLintの `no-else-return`、Pylintの `R1705`、Ruffの `RET505`。ただし自動修正では条件の反転まではしてくれない。
- 避ける場面：A・B・Cのように対等な場合分け、後片付けを手書きする処理、波かっこを省いて次の行に書くガード節。

## アーリーリターン（早期リターン）の意味とガード節との関係

アーリーリターンとは、関数の途中（多くは冒頭）で条件を判定し、その先が不要と分かった時点で `return` する書き方です。「アーリーリターン」「早期リターン」「early return」「早期return」は同じものを指す表記ゆれです。英語圏では「return early」「bail early」とも言います。

関数から早く返す書き方を解説しているのは、[『リーダブルコード』（オライリー・ジャパン）](https://www.oreilly.com/library/view/ridaburukodo-yoriliang-ikodowoshu-kutamenosinpurudeshi-jian-de-natekunituku/9784873115658/)の第7章「制御フローを読みやすくする」にある7.5節「関数から早く返す」です。書籍全体の要点は[リーダブルコードとは？読みやすいコードの4原則と書籍のエッセンス](/column/details/3345/)で整理しています。

ガード節（読み方は「ガードせつ」）は、通常の処理に進まない条件を先に判定して `return` や例外送出で抜ける部分を指す、リファクタリングの用語です。マーチン・ファウラーのカタログには[「Replace Nested Conditional with Guard Clauses」](https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html)として載っています。そこでは、`isDead`・`isSeparated`・`isRetired` の各条件に応じて結果を変数へ代入していく入れ子の条件分岐を、条件ごとに即 `return` して最後に通常の支給額を返す形へ置き換えています。例が示すとおり、ガード節は入力の検証だけでなく、本筋から外れる特別なケースの結果を先に返す用途にも使います。アーリーリターンは「途中で抜ける」書き方全般を指し、ガード節はその代表的な使い方です。リファクタリング全体の進め方は[リファクタリングとは？24の観点とタイミング・進め方](/column/details/2836/)を参照してください。

## ネストした条件分岐からアーリーリターンへの書き換え手順

正常系を `if` の内側へ進めていく書き方では、条件が1つ増えるたびにインデントが1段深くなります。次のPythonコードは、注文を検証してから受け付ける関数です。

```
def process_order_nested(order):
    if order is not None:
        if "items" in order:
            if len(order["items"]) > 0:
                return "受注OK"
            else:
                return "エラー: 商品が空です"
        else:
            return "エラー: itemsキーがありません"
    else:
        return "エラー: 注文がNoneです"
```

正常系の `"受注OK"` はインデント4段目に埋もれています。しかも、各 `else` は対応する `if` から離れた場所にあります。「注文がNone」のエラーは関数の最後の行にあり、対応する条件を判定している、関数本体の最初の行から最も遠くなっています。

条件を反転させ、失敗したらその場で返すように書き換えます。

```
def process_order_guard(order):
    if order is None:
        return "エラー: 注文がNoneです"
    if "items" not in order:
        return "エラー: itemsキーがありません"
    if len(order["items"]) == 0:
        return "エラー: 商品が空です"
    return "受注OK"
```

条件とその結末が同じ場所に並ぶので、対応する `else` を探す必要がなくなります。最後の `return "受注OK"` は「ここまでの関門をすべて通過した」という意味になります。新しい検証を足すときも、既存のネストを組み替えずに `if ...: return` を1つ挟むだけです。`None`・空の辞書・空の `items`・商品ありの4通りを入力し、2つの関数が同じ値を返すことを確認しています。

書き換えの手順は次の3つです。

1. 一番外側の `if` の条件を反転する（`is not None` を `is None` にする）。
2. その `else` の中身を反転した `if` の直下に移して `return` する。
3. 元の `if` の中身をインデント1段分戻し、内側の `if` にも同じ手順を繰り返す。

## ループ内のネストを減らすcontinueの使い方

ループの中でも同じ考え方が使えます。処理しない要素を `continue` で先に飛ばす書き方で、アーリーコンティニュー（early continue）と呼ばれます。

```
def total_nested(items):
    total = 0
    for item in items:
        if item is not None:
            if item["qty"] > 0:
                total += item["price"] * item["qty"]
    return total


def total_continue(items):
    total = 0
    for item in items:
        if item is None:
            continue
        if item["qty"] <= 0:
            continue
        total += item["price"] * item["qty"]
    return total
```

「関数を抜ける」のではなく「その要素の処理だけを打ち切る」点が `return` と違います。ループの後に集計や後片付けが続く関数では、`return` と書き間違えると残りの要素がすべて無視されます。ループ全体を打ち切りたいときは `break` を使います。どれを使うかは、どこまで抜けたいかで決めます。

## 言語別のアーリーリターンの書き方

アーリーリターンは言語の機能ではなく、`return` を持つ言語ならどこでも書けます。ただし、言語によっては専用の構文があったり、規約が書き方を決めていたりします。以下は動作確認済みの関数部分の抜粋です。Javaでは `java.io` と `java.nio.file` の該当クラス、Goでは `os` と `strings` のインポートが必要です。Rustでは `is_active: bool` と `rank: i32`、Swiftでは `isActive: Bool` と `rank: Int` を持つ `User` 型を別途定義しています。

### Pythonのガード節とwithによるファイルの後片付け

Pythonは `if 条件:` の次の行に `return` を書くだけです。`None` の判定には `is None` を使い、空のコレクションは `if not items:` で弾けます。前の章の `process_order_guard` がそのまま基本形です。ファイルを扱う関数は `with` 文の中で返せば、どこで抜けてもファイルが閉じられます。

### JavaScript・TypeScriptのガード節と波かっこの扱い

次の例はJavaScriptです。TypeScriptで `noImplicitAny` を有効にする場合は、引数に型注釈が必要です。例えば、`User` 型を定義したうえで `getDiscount(user: User | null | undefined): number` と宣言します。`!user` は `null` と `undefined` をまとめて弾きます。

```
function getDiscount(user) {
  if (!user) return 0;
  if (!user.isActive) return 0;
  return user.rank * 100;
}
```

1行で書くなら、`return` は条件と**同じ行**に置きます。条件の次の行へ波かっこなしで書くと、後で行を足したときに条件の外へはみ出します。これは後半の章で扱うgoto failと同じ構造です。チームで書き方を揃えるなら、ESLintの `curly` ルールで波かっこを必須にする方法もあります。

### Javaの早期returnとtry-with-resources

Javaでは、すべての `return` が宣言した型を返す必要があります。`void` メソッドなら、値のない `return;` がガード節になります。リソースを扱うときは、try-with-resourcesの中でアーリーリターンしても `close()` が呼ばれます。

```
static String firstLine(Path path) throws IOException {
    try (BufferedReader r = Files.newBufferedReader(path)) {
        String line = r.readLine();
        if (line == null) return "";
        return line.trim();
    }
}
```

空のファイルを渡すと `""` が返り、1行目が `" hello "` なら `"hello"` が返ることをOpenJDK 25.0.4.1で確認しています。

### Rustのlet … elseと?演算子による早期リターン

Rustには、ガード節を宣言的に書く `let ... else` があります。[2022年11月3日リリースのRust 1.65.0](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0/)で安定化しました。パターンに一致しなければ `else` ブロックへ進み、そこで `return` などにより必ず抜けなければなりません。

```
fn get_discount(user: Option<&User>) -> i32 {
    let Some(user) = user else { return 0; };
    if !user.is_active { return 0; }
    user.rank * 100
}

fn read_config(path: &str) -> Result<String, io::Error> {
    let text = fs::read_to_string(path)?;
    Ok(text)
}
```

`Result` や `Option` を返す関数では、`?` 演算子もアーリーリターンとして働きます。`Result` なら `Err` を、`Option` なら `None` をその場で呼び出し元へ返します。存在しないパスを渡すと `Err(NotFound)` が返ることをrustc 1.99.1で確認しています（`use std::fs;` と `use std::io;` が前提です）。

### Goのエラー処理とdeferによる後片付け

[Go Code Review Commentsの「Indent Error Flow」](https://go.dev/wiki/CodeReviewComments#indent-error-flow)は、「通常の処理経路のインデントを最小に保ち、エラー処理を先にインデントして片付ける」よう求めています。`if err != nil { return ... }` を並べるのはこのためです。

```
func readName(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	buf := make([]byte, 64)
	n, err := f.Read(buf)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(buf[:n])), nil
}
```

`defer f.Close()` は、ファイルを開けたと確認できた後に書きます。こうすると、以降のどの `return` で抜けても閉じられます。この例は最大64バイトを1回だけ読み取り、空ファイルでは `io.EOF` を返します。ファイル全体を読む用途では、読み取りを繰り返すか `io.ReadAll` などを使います。

### Swiftのguard文とスコープからの退出要件

Swiftには、ガード節専用の `guard` 文があります。[言語リファレンス](https://github.com/swiftlang/swift-book/blob/main/TSPL.docc/ReferenceManual/Statements.md)は、`else` 節で `Never` を返す関数を呼ぶか、`return`・`break`・`continue`・`throw` でスコープの外へ制御を移すことを求めています。

```
func getDiscount(_ user: User?) -> Int {
    guard let user else { return 0 }
    guard user.isActive else { return 0 }
    return user.rank * 100
}
```

`else` 節に `print` だけを書いて抜けないと、Swift 6.3.3ではビルドが `'guard' body must not fall through` というエラーで止まります。抜け忘れをコンパイラが検出してくれるので、ガード節を書き損じにくい言語です。

## 複雑度の指標で見るアーリーリターンの効果

制御フローの複雑さの変化は、指標で比較できます。ただし、指標が下がることだけで読みやすさを保証できるわけではありません。前の章の4つの関数を、サイクロマティック複雑度（radon 6.0.1）と認知的複雑度（flake8-cognitive-complexity 0.1.0、内部ライブラリcognitive-complexity 1.3.0）で計測しました。このライブラリはSonarSourceの原アルゴリズムと完全には一致しないため、表はこの実装での測定値です。

| 関数                            | サイクロマティック複雑度 | 認知的複雑度 |
| ----------------------------- | ------------ | ------ |
| process\_order\_nested（ifが3段） | 4            | 9      |
| process\_order\_guard（ガード節）   | 4            | 3      |
| total\_nested（ループ内ネスト）        | 4            | 6      |
| total\_continue（continue）     | 4            | 5      |

今回の書き換えでは判定の数が変わらないため、サイクロマティック複雑度も変わりません。ただし、論理演算子やループなども加算対象なので、`if` の数だけで値は決まりません。下がったのは認知的複雑度だけです。[SonarSourceのCognitive Complexity仕様](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)は、`if` に1点を足し、ネストしていればその深さに応じた点をさらに足します。一方、早期の `return` そのものには点を足しません。ネストを浅くすれば、点が下がる仕組みです。

今回のように判定数を変えずにガード節へ書き換える場合、radonで測るサイクロマティック複雑度は下がらないため、CIのしきい値超過も解消しません。その場合は、判定の数そのものを減らす必要があります（辞書による対応表への置き換えや関数の分割など）。指標ごとの違いとツール別の既定値は[サイクロマティック複雑度とは？計算方法・目安10の根拠とツールで値が違う理由](/tech/details/5208/)で解説しています。

## リンターで不要なelseを検出する設定と限界

「`return` の後の `else` は不要」という指摘は、主要なリンターが機械的に出せます。

| ツール    | ルール                            | 既定                    |
| ------ | ------------------------------ | --------------------- |
| ESLint | no-else-return                 | 設定で有効化                |
| Pylint | R1705 no-else-return           | 有効                    |
| Ruff   | RET505 superfluous-else-return | 無効（extend-select で指定） |

[ESLintのno-else-return](https://eslint.org/docs/latest/rules/no-else-return)は `allowElseIf` オプションの既定が `true` で、`return` の後の `else if` は許されます。`false` にすると `else if` も指摘されます。[PylintのR1705](https://pylint.readthedocs.io/en/latest/user%5Fguide/messages/refactor/no-else-return.html)と[RuffのRET505](https://docs.astral.sh/ruff/rules/superfluous-else-return/)は同じ内容のPython版です。Ruff 0.17.2では、何も設定しない状態で有効になっている407ルールにRET505は入っていません。既定のルールを残したまま足すには、`extend-select` に指定します。

```
# pyproject.toml
[tool.ruff.lint]
extend-select = ["RET505"]
```

前の章の `process_order_nested` にかけると、結果はツールによって違いました。Ruff 0.17.2とPylint 3.3.9が指摘したのは、最も内側の `else` 1か所だけです。外側の2つは「`if` の本体が `return` で終わっていない」ため対象外でした。ESLint v10.13.0は、`if` を2段入れ子にしたJavaScriptの割引計算例で、内側と外側の両方の `else` を指摘しました。ただ、`--fix` で直した結果は `else` を消して中身を外へ出しただけです。`if (user) { if (user.isActive) { ... } return 0; } return 0;` という形でネストは残りました。

つまり、リンターがするのは不要な `else` の検出と、ESLintやRuffでの `else` の除去までです。条件を反転させてガード節に組み替える作業は、人が前の章の手順で行います。リンターは、書き換えた後に `else` が戻ってこないよう見張る役割と考えると使いどころを誤りません。

## アーリーリターンを使うべきでない場面

アーリーリターンにも反対意見があります。根拠のある反論は次の3つに分けられ、それぞれに対処法があります。

### 単一出口原則が求められる関数

「関数の出口は1つにすべき」という単一出口原則（single exit）は、いまもコーディング規約に残っています。代表例はロッキード・マーティンが2005年に公開した[JSF AV C++ Coding Standards](https://www.stroustrup.com/JSF-AV-rules.pdf)です。そのAV Rule 113は、MISRA Rule 82を改訂したもので、「Functions will have a single exit point.」と定めています。根拠は「出口が多い関数は理解も解析も難しくなりがち」というものです。

ただし、同じ規則には例外が書かれています。「単一出口にすると制御が分かりにくくなる、または大きく複雑になる（追加の変数を導入するなど）場合は不要」です。あわせて「後片付けはすべての出口で管理すること」とも書かれています。厳格な組込み向け規約でも、`result` 変数を持ち込んでネストを深くしてまで出口を1つにすることは求めていないわけです。

判断の基準は次のとおりです。短い関数で前提をチェックするだけなら、ガード節による複数の `return` を優先します。関数が長く、途中に副作用や後片付けが挟まるなら、出口を集約するか、関数を分割します。所属する現場の規約が単一出口を定めているなら、規約に従います。

### 後片付けを手書きする処理とgoto fail

波かっこのない条件分岐に無条件のジャンプが紛れ込んだ例が、2014年に見つかったAppleのSSL/TLS検証の脆弱性（[CVE-2014-1266](https://support.apple.com/en-asia/103608)）です。[Security-55471のsslKeyExchange.c](https://github.com/apple-oss-distributions/Security/blob/Security-55471/libsecurity%5Fssl/lib/sslKeyExchange.c)では、エラー時に後片付けへ飛ぶ `goto fail;` が2行続いていました。

```
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;
    if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
        goto fail;
```

波かっこがないため、条件に従うのは1行目の `goto fail;` だけです。2行目は必ず実行され、`err` が0（成功）のまま後続の署名検証を飛ばしました。Appleは影響を「ネットワーク上の特権的な位置にいる攻撃者が、SSL/TLSで保護されたセッションのデータを取得・改変できる可能性」と説明しています。

これは `return` ではなく `goto` による途中脱出ですが、教訓はアーリーリターンにもそのまま当てはまります。1つ目は、ガード節を波かっこなしで次の行に書かないことです（1行に収めるか、波かっこで囲みます）。2つ目は、途中で抜ける関数では後片付けを手書きせず、`with`・try-with-resources・`defer` のような言語の仕組みに任せることです。

### 対等な場合分けへの乱用

アーリーリターンを避けたいという主張の多くは、「`if` の後に暗黙の `else` を読み手に補わせる」「`if` と後続の処理の間に別のコードを挟み込めてしまう」という点を問題にしています。この指摘は、本筋のない対等な分岐に `return` を使ったときに当たっています。

```
// 対等な3つの分岐。ガード節にする必要はない
function getLabel(score) {
  if (score >= 80) {
    return "A";
  } else if (score >= 60) {
    return "B";
  } else {
    return "C";
  }
}
```

この3つの結末は、どれも正常な結果で、どれが本筋ということがありません。こうした分岐は `if`/`else if`/`else` や対応表で対称に書いた方が意図が伝わります。ESLintの `no-else-return` を既定の `allowElseIf: true` のまま使うと、`else if` は許されて最後の `else` だけが指摘されます。対称な書き方を優先する関数では、指摘に合わせて崩すのではなく、対応表への置き換えを検討します。

線引きは「関数に本筋の処理があり、その分岐が本筋から外れるケースを片付けているか」です。ファウラーの例でいえば、`normalPayAmount()` による支給額の計算が本筋で、`isDead`・`isSeparated`・`isRetired` に該当する場合は特別なケースです。こうした分岐はアーリーリターンにし、本筋のない対等な場合分けは `if`/`else` にします。ガード節を関数の冒頭にまとめ、本処理の途中から抜ける `return` を最小限にすれば、読み手が関数の終わり方を見失いません。条件式そのものを読みやすくするには、真偽を表す変数名の付け方が効きます。具体的な付け方は[命名規則を英語で書く方法｜ケース・品詞・対義語と公式ガイドの規定](/column/details/2328/)にまとめています。

## よくある質問

### アーリーリターンと早期リターンは違うものですか？

同じものです。「アーリーリターン」「早期リターン」「early return」「早期return」はいずれも、処理の途中で結果が確定した場合などに、`return` で関数から早く抜ける書き方を指します。

### ガード節の読み方と、early returnとの違いは？

ガード節は「ガードせつ」と読み、英語ではguard clauseです。アーリーリターンが途中で抜ける書き方全般を指すのに対し、ガード節は、通常処理を続けない条件を先に処理して抜ける部分を指します。入力検証や特別な場合の処理に使います。

### 早期リターンを禁止しているコーディング規約はありますか？

JSF AV C++のAV Rule 113のように、単一出口を定める規約はあります。ただしその規約にも、単一出口にすると追加の変数などで制御が複雑になる場合は不要という例外があります。一方で、GoのCode Review Comments、CakePHPのコーディング規約（[Bail Early](https://book.cakephp.org/5.x/contributing/cakephp-coding-conventions.html)）は早く抜けることを推奨しています。

### Pythonでearly returnはどう書きますか？

関数の冒頭に `if 条件:` と `return 値` を並べ、無効な入力を先に返します。ループの中では `return` の代わりに `continue` を使うと、その要素だけを飛ばせます。

### early return patternとは何ですか？

ネストした条件分岐をガード節に置き換え、正常系を浅いインデントに残す定石のことです。ファウラーのカタログでは「Replace Nested Conditional with Guard Clauses」という名前で紹介されています。

## 関連記事

- [リーダブルコードとは？読みやすいコードの4原則と書籍のエッセンス](/column/details/3345/)
- [サイクロマティック複雑度とは？計算方法・目安10の根拠とツールで値が違う理由](/tech/details/5208/)
- [リファクタリングとは？24の観点とタイミング・進め方](/column/details/2836/)
- [良いコードの書き方｜可読性・保守性を高める12の原則とJavaの実例](/column/details/2313/)
- [KISS原則（KISSの法則）とは？Keep it simple, stupidの意味・由来と実践例](/tech/details/5754/)

---

出典: [アーリーリターン（early return）とは？ガード節の書き方と言語別の実装・使わない場面](<https://www.issoh.co.jp/tech/details/2700/>)（株式会社一創）
