0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ガード節(Guard Clause)入門 ─ ネストを減らし、読みやすいコードへ

Last updated at Posted at 2025-10-24

TL;DR

  • ガード節は「条件を満たさない場合に早期に抜ける(return/throw/continue)記述」で、ネストを浅くし幸せな経路(Happy Path)を左側にまっすぐ流せます
  • 効果:可読性・保守性・差分の見やすさが上がり、バグ混入を減らします
  • 注意:後処理(リソース解放/トランザクション)とエラーログの重複に気を付ける。言語ごとの「安全な抜け方」を使う(defer/with/using/try-with-resources 等)

1. ガード節とは?

想定外の入力や前提違反を冒頭で弾くことで、以降の処理を前提が満たされた“幸せな経路”に限定する」テクニックです。
別名:早期リターン(early return)fail-fast

典型的には以下の3種類を"上から順に"並べます:

  1. 存在/Nullチェック
  2. 権限/状態チェック
  3. 入力妥当性チェック

満たさない場合は return / throw / continue / break などで早期に離脱します。

2. なぜ使うのか(メリット)

  • ネスト削減if の入れ子地獄(「矢印コード」)を防ぎ、1〜2段の深さに保てる。
  • 読みやすさ:否定条件を先に処理することで、本筋が左にまっすぐ読める。
  • 差分が明瞭:PRレビューで本筋の変更が見やすく、レビュー効率とバグ検出率が上がる。
  • 早期失敗:壊れた前提で先に進まず、バグの影響範囲を最小化できる。

3. Before / After(多言語サンプル)

JavaScript / TypeScript

Before(矢印コード):

function checkout(user?: User, cart?: Cart) {
  if (user) {
    if (user.active) {
      if (cart && Array.isArray(cart.items) && cart.items.length > 0) {
        // 本処理
      } else {
        throw new Error('Empty cart');
      }
    } else {
      throw new Error('Inactive user');
    }
  } else {
    throw new Error('Unauthenticated');
  }
}

After(ガード節):

function checkout(user?: User, cart?: Cart) {
  if (!user) throw new Error('Unauthenticated');
  if (!user.active) throw new Error('Inactive user');
  if (!cart || cart.items.length === 0) throw new Error('Empty cart');

  // 本処理(Happy Path)
}

チーム規約例:ESLint の no-else-returnmax-depth(例: 2)を有効化


Python

def parse_age(payload: dict) -> int:
    if "age" not in payload:
        raise KeyError("age is required")

    try:
        age = int(payload["age"])
    except (TypeError, ValueError):
        raise ValueError("age must be an integer")

    if age < 0:
        raise ValueError("age must be non-negative")

    return age

例外で早期離脱。リソース扱い時は with 文で後始末を自動化


Go

func Save(u *User, repo Repository) error {
    if u == nil {
        return errors.New("nil user")
    }
    if err := u.Validate(); err != nil {
        return err
    }

    // Happy Path
    if err := repo.Save(u); err != nil {
        return err
    }
    return nil
}

エラーパスは上から落とす。後処理は defer を活用。


Java

public Order place(String orderId, List<Item> items) {
    Objects.requireNonNull(orderId, "orderId");
    if (items == null || items.isEmpty()) {
        throw new IllegalArgumentException("items must not be empty");
    }

    // Happy Path
    return doPlace(orderId, items);
}

I/O は try-with-resourcesガード途中の return/throw でも確実にクローズ


Kotlin

fun pay(user: User?, amount: Int) {
    requireNotNull(user) { "user is required" }
    require(amount > 0) { "amount must be positive" }

    // Happy Path
}

Swift(言語構文としての guard)

func handle(user: User?) {
    guard let user = user else {
        logger.error("no user")
        return
    }
    // Happy Path
}

Ruby

def ship(order)
  raise ArgumentError, "order is required" unless order
  return if order.canceled?

  # Happy Path
end

RuboCop: Style/GuardClause をオンにするとレビューが楽になります。


C#(素朴な形 + ガード用ライブラリ)

public void Ship(Order order, int quantity) {
    if (order is null) throw new ArgumentNullException(nameof(order));
    if (quantity <= 0) throw new ArgumentOutOfRangeException(nameof(quantity));

    // Happy Path
}

(オプション:Ardalis.GuardClauses を使う)

Guard.Against.Null(order);
Guard.Against.NegativeOrZero(quantity);
// Happy Path

4. どこで使う?

  • 入力/前提の検証:必須フィールド、ID、権限、状態(active/inactive など)
  • ループ処理:不対象は continue で先に弾く
  • 早期キャンセル:非同期/長時間処理は「キャンセル済みなら即 return」
  • 境界/エッジ条件:空配列、上限超過、タイムアウト など

5. 実運用での注意点

  1. 後始末(リソース解放)

    • 早期離脱でも確実に解放される構文を使う:
      • Python with
      • Java try-with-resources
      • C# using
      • Go defer
      • Ruby ensure
      • Swift defer
  2. エラーログの重複

    • 「投げる側でログ、受ける側でもログ」の二重記録に注意。原則1箇所で記録
  3. 例外か戻り値か

    • プログラミングエラー(契約違反) → 例外で fail-fast
    • ドメインとして想定済みの失敗 → 戻り値(Result/Either/Option)やエラーコードで表現する設計も検討
  4. ガードの乱立

    • 似た条件をまとめる、メッセージを一貫させる、関数抽出で読みやすく
    • 3〜5行を超えるロジックは専用のバリデータ/ポリシーに切り出す

6. チーム導入の小ワザ

  • Lint/Formatter
    • JS/TS: no-else-return, max-depth
    • Ruby: Style/GuardClause
    • Go: golangci-lintnestif で入れ子検出
  • レビュー指針(Snippet)
    • 否定条件は先に返す」「Happy Path を左に寄せる」「深さは最大2
  • テスト
    • 前提違反ケースの網羅(Null・空・境界・権限)を先に書くと、ガード節の価値がすぐに見える

7. アンチパターン

  • 深いネストを“気合い”で維持:保守性が落ちます。ガード節で平坦化
  • 早期 return が散らばりリソース漏れ:上記の構文(with/using/defer 等)を徹底
  • 例外投げすぎ:ドメイン上の通常失敗は戻り値/型で表現する設計も検討

8. 用語メモ(他言語の「ガード」)

  • Swift: guard キーワードが言語機能
  • Haskell/Rust/Elixir など:パターンマッチのガードpattern if condwhen)という文脈でも登場
    この記事の「ガード節」は一般的な早期離脱テクニックを指します

9. まとめ

  • ガード節は前提違反を先に弾くことで、本筋をまっすぐ読めるようにするシンプルで強力な手法
  • 導入は小さな関数から。否定条件を反転して先に返すだけで可読性が変わります
  • 仕上げに、後処理の安全化ログ戦略の一貫性だけ忘れずに
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?