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?

Claude Code のサブエージェント機能で iOS/Flutter 向けコードレビュー職人を作った話

0
Posted at

自己紹介

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
コーポレートサイト

はじめに

個人開発や受託案件をやっていると、必ず一度はぶつかる悩みがあります。

  • PR(Pull Request)を上げる前のセルフレビューで、毎回チェック観点が抜ける
  • リリース直前にクラッシュリスクや force unwrap が残っていないか心配
  • レビュー観点を CLAUDE.md に書き始めたら、どんどん肥大化してきた

私もまったく同じでした。CLAUDE.md に「force unwrap 禁止」「print() 禁止」「Swift 6 Concurrency に注意」と書き足していくうちに、メインのコーディング指示と「レビュー観点」が混ざり、Claude Code が読み込むコンテキストが膨らんでいきました。

これを解決してくれたのが Claude Code の サブエージェント機能 です。本記事では、私が実際に iOS(SwiftUI)/ Flutter 向けに運用している 4パスレビュアー code-reviewer の設定全文と、設計思想・使い方・効果を共有します。

「Claude Code のサブエージェントを自分用にカスタマイズしたいけど、何から書けばいいかわからない」という中級エンジニアの方の参考になれば嬉しいです。

難易度:⭐⭐(応用)

Claude Code のサブエージェントとは

サブエージェント(subagent)は、メインセッションとは 独立したコンテキストウィンドウ を持つ、専門特化型の AI アシスタントです。

公式ドキュメントから引用すると、特徴は以下の通りです。

  • 各サブエージェントは独自のシステムプロンプト・ツールアクセス・パーミッションを持つ
  • メインの会話のコンテキストを汚さずに、サブタスクを「丸投げ」できる
  • 結果はサマリーだけが返ってくるので、長大な探索ログやファイル内容で本流が埋もれない
  • description の文面を見て、Claude が「いつこのサブエージェントに委譲するか」を判断する

定義方法は驚くほどシンプルで、.claude/agents/<name>.mdfrontmatter + プロンプト本文 を書くだけです。frontmatter で modeltoolseffortmaxTurns などを制御できます。対話的に作りたい場合は /agents コマンドからも作成可能です。

なぜ「コードレビュー」をサブエージェント化するのか

「メインセッションでもレビューはできるじゃないか」と思われるかもしれません。実際できます。ただ、専用エージェントに切り出すと以下のメリットが効いてきます。

1. コンテキスト分離で精度が上がる

レビューは「対象ファイルを Read → Grep で関連箇所を探索 → ビルド実行」と、大量の入出力が発生します。これをメインセッションでやるとコンテキストが食われ、本来やりたい実装タスクの精度が落ちます。サブエージェントなら自分専用の窓で完結して、結果だけ返ります。

2. ツールを絞ってレビューに専念させられる

後述しますが、私の code-reviewertools: Read, Grep, Glob, Bash のみで、EditWrite を持たせるかは設計判断のポイントです。コードリーディングと修正の権限を分離できるのは、サブエージェントならではです。

私の現状の構成では Edit/Write を含めていないため、ファイル修正は Bash 経由(sed -i やヒアドキュメントでの上書き)に限定 されます。小さな置換中心のレビューにはこれで十分ですが、大規模なリファクタやマルチファイル修正までやらせたい場合は Edit を追加する選択肢もあります(理由は後述)。

3. CLAUDE.md がスリムになる

レビュー観点をエージェントファイルに移すだけで、CLAUDE.md から「コーディング規約」と「レビュー観点」の混在が解消されます。

4. 横展開しやすい

同じ観点を別プロジェクトに持ち込むのが、ファイル1枚のコピーで済みます。

設計思想:4パス構造

私の code-reviewer の最大の特徴は、レビューを 4つのパス(pass)に分割 していることです。

パス 観点
パス1 機能性 + クラッシュリスク
パス2 パフォーマンス + メモリ
パス3 セキュリティ
パス4 UX + コード品質

なぜわざわざ分けるのか。理由は以下です。

  • 観点を混ぜると認知負荷で漏れが出る(人間でも AI でも同じ)
  • 観点ごとに「深さ」を確保したい:パフォーマンス確認とセキュリティ確認では、見るべき場所も使うツールも違う
  • 修正→ビルドのループを観点ごとに回せる:パス1の修正後にビルドして次のパスへ、と段階的に検証できる

特に Swift 6 の Concurrency や SwiftData の落とし穴は、パフォーマンスや UX と一緒くたにすると見落とされがちなので、パス1 で「クラッシュリスク」として明示的に拾う 設計にしています。

frontmatter の意味と選定理由

実際に使っている code-reviewer.md の frontmatter は以下です。

---
name: code-reviewer
description: iOS(SwiftUI)・Flutterアプリのコードレビューを4パスで実行する。コードレビューやPR前の品質チェックが必要なときに使う。
tools: Read, Grep, Glob, Bash
model: sonnet
effort: high
maxTurns: 40
---

各項目の選定理由を解説します。

name: code-reviewer

呼び出し名。code-reviewer サブエージェントでこの PR をレビューして のようにエージェント名を指定して明示的に呼び出すときに使います。

description

「何をするか」ではなく「いつ使うか」で書く のが鍵です。Claude はこの文面を見て委譲を判断するので、「コードレビューやPR前の品質チェックが必要なときに使う」という発火条件を含めると自動選択精度が上がります。

tools: Read, Grep, Glob, Bash

レビューに必要な最低限のツールセットです。この構成では Edit/Write がないため、ファイル修正は Bash 経由(具体的には sed -i やヒアドキュメント cat <<EOF > file での上書き) に限定されます。修正パッチが git diff でレビューしやすいというメリットがある一方、sed には構文認識がないので 修正範囲が大きいとパッチが壊れやすい という制約があります。

そのため、複数ファイルにまたがる大規模なリファクタや構造化された書き換えをサブエージェントにやらせたい場合は、toolsEdit を追加した方が安全です。「報告だけさせたい」「軽微な修正までで十分」なら現状のまま、「複雑な修正まで完結させたい」なら Edit 追加、という運用判断になります。

model: sonnet

コスト効率重視で Sonnet を選択。Opus にしない理由は、レビュー観点が体系化されていれば Sonnet で十分高品質な指摘が返ってくるためです。観点リスト(後述)が「考えるべきこと」を明示しているので、推論力よりも指示追従性が効きます。

effort: high

推論の深さ設定。コードの隠れた依存関係や Concurrency 違反は浅い思考だと見逃すので high に設定しています。

maxTurns: 40

ターン上限。4パス × 各10手程度(ファイル読み→修正→ビルド確認)を想定して 40 に設定。これより少ないと途中で打ち切られ、多いと暴走時のコスト爆発リスクがあります。

プロンプト本文の解説

frontmatter の下に、本文として「Role / Context / Workflow / Output Format / Constraints」を書きます。重要な部分を引用しつつ解説します。

Role:役割を一文で

あなたはiOS / Flutterアプリのコードレビューを専門とするシニアエンジニアである。
4パスの構造化レビューで、クラッシュリスク・パフォーマンス問題・セキュリティ・UX品質を
漏れなく検出し、発見した問題は即座に修正する。

「シニアエンジニア」というロールを明示することで、表面的なスタイル指摘ではなく、実害のある問題を優先して指摘してくれるようになります。

Context:プロジェクト固有のスタックと厳守ルール

## Tech Stack
- iOS:Swift 6 / SwiftUI / SwiftData / iOS 18.0+
- 課金:StoreKit 2(サブスクリプション)
- 広告:Google AdMob
- Flutter:Dart
- プロジェクト管理:XcodeGen(project.yml)

そして厳守ルール(Swift)として、私の CLAUDE.md のコーディング規約と 1対1対応 する形で記載しています。

## 厳守ルール(Swift)
- force unwrap(`!`)/ `try!` / `as!` 禁止 → `guard let` / `if let` / `do-catch` / `as?`
- `print()` 禁止 → `Logger`(os.log)使用
- View は200行以下(超過はサブViewに分割)
- ViewModel は `@Observable @MainActor final class` パターン
- シングルトンは `static let shared`
- SwiftData `@Model` にはデフォルト値を設定
- エラーハンドリングは `Result` 型または `throws` で統一(コールバックでエラーを返さない)
- 命名は Swift API Design Guidelines 準拠(メソッドは動詞始まり、Boolは `is`/`has`/`should` プレフィックス)
- 各Viewファイルに `#Preview` マクロを記載
- ファイル構成: `App/`, `Models/`, `ViewModels/`, `Views/`, `Services/`, `Resources/`

Flutter 用には別途、

## 厳守ルール(Dart/Flutter)
- `Equatable` の `==` をオーバーライドする場合は `hashCode` も必須
- `BuildContext` を async gap の後に使わない
- `dispose()` でコントローラ・ストリームを解放

を記載しています。

Step 0:レビュー対象を必ず特定させる

レビュー開始前に、変更ファイルを特定する。

# 未コミットの変更
git diff --name-only HEAD
# または指定コミット範囲
git diff --name-only main...HEAD

「全ファイルなめて」と指示されても無駄なので、最初に git diff で対象を絞る ことを強制しています。

Workflow:4パスを順番に必ず回す

各パスの観点を例示します(パス1のみ抜粋)。

## パス1:機能性+クラッシュリスク
- 正常系:期待値を返すか
- 異常系:nil / 空配列 / 大量データでも動くか
- force unwrap / `try!` / `as!` / 二重resume / 未処理Optional がないか
- SwiftData の cascade delete 後の参照、`@Model` init のデフォルト値
- Swift 6 Concurrency:
  - `Sendable` 準拠が必要な型に付いているか
  - actor isolation 違反がないか(`@MainActor` の付け忘れ、不要な `nonisolated`)
  - `Task` 内で `self` キャプチャ時の競合リスク

パス2(パフォーマンス/メモリ)、パス3(セキュリティ)、パス4(UX/コード品質)も同様に観点を箇条書きで列挙しています。

そして 「各パスで問題を発見したら即修正 → ビルド確認してから次のパスに進む」 という運用ルールを明記しています。

ビルド確認コマンド

cd [プロジェクトルート] && xcodegen generate && \
  xcodebuild -project [App].xcodeproj -scheme [App] \
  -destination 'platform=iOS Simulator,name=iPhone 16' build 2>&1 | tail -5

XcodeGen を使っているプロジェクト前提のコマンドです。tail -5 で末尾だけ取るのは、ビルドログ全文がコンテキストを食わないようにする工夫です。

Output Format:テーブル+深刻度マーク

## パス[N]:[パス名]

| # | ファイル:行 | 深刻度 | 問題 | 対応 |
|---|------------|--------|------|------|
| 1 | Foo.swift:42 | 🔴 高 | force unwrap | guard let に修正済み |

修正後ビルド:✅ 成功

深刻度は3段階。

  • 🔴 高:クラッシュ・データ損失・セキュリティ脆弱性(即修正必須)
  • 🟡 中:パフォーマンス劣化・UX問題・規約違反(修正推奨)
  • 🔵 低:コード品質・可読性(改善提案)

人間がレビュー結果を読むときに、対応すべき優先度がひと目でわかるのが狙いです。

Constraints:暴走防止のガードレール

- 4パスを必ず順番に実行する(スキップ禁止)
- 問題を発見したら報告だけでなく即修正する
- 修正後は必ずビルド確認する(2回失敗したらユーザーに報告)
- コミット前に `print(` が残っていないか最終確認する
- 日本語で回答する

「2回失敗したらユーザーに報告」という上限を明示しているのは、修正がループしてターンを浪費しないための保険です。

実際の使い方

Claude Code のサブエージェントは、メインセッションが description フィールドの内容を読み取り、Task ツール経由で自動的に委譲する仕組みです。そのため特別な構文を覚える必要はなく、自然言語で依頼するだけで動きます。

> この PR(Pull Request)をレビューしてください
> リリース前のセルフチェックをお願いします

このように「レビューして」「PR前にチェックして」と書くだけで、description に「コードレビューやPR前の品質チェックが必要なときに使う」と明記してあるおかげで、Claude Code が code-reviewer への委譲を自動判断してくれます。

明示的に呼び出したい(自動判断に任せず確実に発火させたい)場合は、エージェント名を含めて指示します。

> code-reviewer サブエージェントで、変更ファイルをレビューしてください

こう書けば、Claude Code は迷わず code-reviewerTask ツールで起動します。

レビュー1サイクルの実例

たとえば、以下のような Swift コードをレビュー対象にしたとします。

import SwiftUI

struct User: Identifiable {
    let id: UUID = UUID()
    let name: String
}

struct BadUserListView: View {
    @State var users: [User]?

    var body: some View {
        List(users!) { user in  // ← force unwrap でクラッシュリスク
            Text(user.name)
        }
    }
}

#Preview {
    BadUserListView(users: [User(name: "Alice"), User(name: "Bob")])
}

これを code-reviewer に投げると、パス1で以下のような結果が返ります。

## パス1:機能性+クラッシュリスク

| # | ファイル:行 | 深刻度 | 問題 | 対応 |
|---|------------|--------|------|------|
| 1 | BadUserListView.swift:11 | 🔴 高 | `users!` の force unwrap:users が nil の場合クラッシュ | `if let` で安全にアンラップ+ローディング表示に修正済み |

修正後ビルド:✅ 成功

修正後コードの例:

import SwiftUI

struct User: Identifiable {
    let id: UUID = UUID()
    let name: String
}

struct GoodUserListView: View {
    @State var users: [User]?

    var body: some View {
        if let users {
            List(users) { user in
                Text(user.name)
            }
        } else {
            ProgressView()
        }
    }
}

#Preview {
    GoodUserListView(users: [User(name: "Alice"), User(name: "Bob")])
}

@State var users: [User]?if let で安全にアンラップし、nil のときは ProgressView() でローディング表示にする──という修正方針も「Constraints の厳守ルール」に基づいて自動で選ばれます。

その後、パス2(パフォーマンス)、パス3(セキュリティ)、パス4(UX/コード品質)と順番に進み、最後に1行サマリーで「全パス完了:パス1で1件修正、他パス指摘なし」のように報告されます。

このサブエージェントを使ってよかったこと

実際に運用してみて感じている効果は以下です。

リリース前の最終チェック時間が体感半分に

4パス構造で観点が明確なので、人間側で「あれ見たっけ、これ見たっけ」と迷う時間がなくなりました。

レビュー観点の抜けが激減

特に Swift 6 Concurrency と Keychain/UserDefaults の使い分け(パス3)は、人間だと忘れがちな観点が安定して拾われます。

CLAUDE.md がスリムに

「コーディング規約」と「レビュー観点」の混在が解消され、CLAUDE.md は実装時の指示に集中できるようになりました。

Flutter 案件にも横展開しやすい

Dart/Flutter 向けの厳守ルールも同じファイル内に書いているので、案件が変わっても同じ code-reviewer で回せます。

自分用にカスタマイズするときのコツ

最後に、これからサブエージェント化する方への実践的なコツを共有します。

1. まず CLAUDE.md のレビュー観点を切り出す

すでに CLAUDE.md に書いている「コーディング規約」「禁止事項」を、そのまま agent ファイルの「厳守ルール」セクションにコピーするところから始めるのが楽です。

2. パス分割は「観点が違うものを混ぜない」

機能性とパフォーマンスを混ぜると、認知負荷で漏れます。最低でも「クラッシュリスク」「パフォーマンス」「セキュリティ」「UX」の4軸は分けることをおすすめします。

3. tools は最小限から

「報告だけさせたい」なら Read/Grep/Glob のみ、「Bash でのビルド確認や sed での軽微な修正まで完結させたい」なら Bash も追加、「複数ファイルにまたがる構造化された修正までやらせたい」なら Edit も追加、という具合に運用に合わせて段階的に広げるのがおすすめです。最初から全部盛りにせず、必要になったら足す方が安全です。

4. description は「いつ呼ぶか」で書く

「〜が必要なときに使う」という発火条件を含めることで、自動委譲の精度が上がります。

5. ビルドコマンドはプロジェクトに合わせて差し替え

私は XcodeGen + iOS Simulator 前提のコマンドを書いていますが、Flutter 案件なら flutter analyze && flutter test などに差し替えると、同じ4パス構造のまま流用できます。

まとめ

  • Claude Code のサブエージェントは 「専門家を雇う」感覚 で使えます
  • コードレビューは 4パス構造化 することで、人間にも AI にも漏れが出にくくなります
  • iOS/Flutter で個人開発・受託をしているエンジニアにこそ刺さる活用法です
  • まずは CLAUDE.md のレビュー観点を切り出して、.claude/agents/code-reviewer.md を1ファイル作るところから始めてみてください

「PR セルフレビューの抜け漏れが怖い」「リリース前のチェックを自動化したい」という方の参考になれば嬉しいです。

参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

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?