Claude Codeで詳細設計書を起こす — 既存コードから処理フローと例外を表に
設計書のない既存コードから、機能単位の詳細設計書を起こす手順です。サブエージェントに処理フロー・入出力・分岐・例外を表で返させ、全行にファイル名と行番号を添えさせて、スクリプトで検算します。
設計書が残っていない業務システムで、改修や引き継ぎのために「この機能は何をするのか」を文書にしたい。そんな場面で、Claude Codeにコードを読ませて詳細設計書を起こす手順をまとめます。
要点は3つです。設計書を処理フロー・入出力・分岐と例外の3つの表に固定すること。表の全行にファイル名:行番号の根拠を添えさせること。そして、その行番号をスクリプトで検算してから残すことです。機能単位の読み取りは、サブエージェントに分けて任せます。
機能単位の詳細設計書に何を書くか
詳細設計書は、1つの機能について「呼ばれてから終わるまで」を追える粒度の文書です。システム全体の構成を説明するものではありません。全体像は新メンバー向けアーキテクチャ概要の側で扱い、ここでは機能1つを深く読みます。
既存コードから起こす場合、書く内容は次の3つに絞れます。
詳細設計書の3つの表
処理フロー
呼び出しから終了までの手順を番号順に並べた表です。各ステップが読んでいるコードの位置を添えます。
入出力
引数、リクエスト項目、読み書きするテーブルや外部呼び出しを、型と必須条件つきで並べた表です。
分岐と例外
どの条件でどの結果になるか、どの失敗でどのエラーが返るかを並べた表です。ここが改修時に最も事故を起こしやすい箇所です。
文章で流れを説明した設計書は、読み手が「この一文はコードのどこか」を探せないまま古くなります。表にして根拠の位置を持たせておくと、コードが変わったときにどの行が怪しいかを機械的に拾えます。
雛形を先に決めて、空欄を許す
白紙から書かせると、Claudeは一般的な設計書の語り口で埋めがちです。列を固定した雛形を最初に渡し、コードから読み取れなかった欄は「不明」と書かせます。
# 機能名: <機能名>
## 1. 処理フロー
| No | 処理 | 呼び出し先 | 根拠 |
|---|---|---|---|
## 2. 入出力
| 区分 | 項目 | 型 | 必須 | 備考 | 根拠 |
|---|---|---|---|---|---|
## 3. 分岐と例外
| No | 条件 | 結果 | 発生箇所 | 根拠 |
|---|---|---|---|---|
## 4. 不明点
- 読み取れなかった理由と、確認したい相手「根拠」列の書式はsrc/orders/cancel.ts:42-58のように、リポジトリのルートからの相対パスと行範囲に統一します。この書式を揃えておくと、後の検算スクリプトが1つの正規表現で拾えます。
「不明点」の節を雛形に入れておくのも大切です。節がないと、読み取れない箇所を推測で埋めた文書になります。設計書では、空欄のほうが誤記より安全です。
設計書づくり専用のサブエージェントを置く
読み取りを任せるサブエージェントは、プロジェクトの.claude/agents/に定義ファイルとして置きます。公式ドキュメントによると、追加や編集は数秒以内に検出され、再起動は要りません。ただし、そのセッションを始めた時点で存在しなかったagentsディレクトリに最初のファイルを作った場合は、再起動が必要です。
---
name: design-doc-extractor
description: 指定された機能のコードを読み、処理フロー・入出力・分岐と例外の表を、ファイル名と行番号つきで返す。設計書の下書きづくりに使う
tools: Read, Grep, Glob
model: sonnet
maxTurns: 40
---
あなたはコードリーディングの担当です。依頼された機能だけを読み、
次の3つの表を返します。ファイルは書き換えません。
1. 処理フロー(No / 処理 / 呼び出し先 / 根拠)
2. 入出力(区分 / 項目 / 型 / 必須 / 備考 / 根拠)
3. 分岐と例外(No / 条件 / 結果 / 発生箇所 / 根拠)
規則:
- 全行の「根拠」に `パス:開始行-終了行` を書く。読んでいない行は引用しない
- コードから読み取れない内容は表に入れず、「不明点」に理由つきで書く
- 呼び出し先が依頼範囲の外にあるときは、名前と場所だけを書き、中身は追わない
- 例外は throw / return エラー / catch の握りつぶしを区別して書く各フィールドの役割は次のとおりです。
tools: Read, Grep, Globは許可リストです。省略すると、サブエージェントが使えるツールをすべて引き継ぎます。読み取り専用にしたいので、Edit・Write・Bashは含めませんmaxTurnsは、上限に達したとき出力を「途中まで」の印つきで返します。Claudeに頼めば、続きを再開させられます。印がつくのはv2.1.246以降ですmodelはsonnet・opus・haiku・fableなどを指定します。読み取りと表の整形が中心なら、軽いモデルで足りる場面が多くあります。優先順位の詳細はサブエージェントのモデル指定と配分にありますdescriptionは、Claudeが自動で任せるかどうかの判断材料です。「いつ使うか」まで書いておきます
権限をさらに絞る設計はサブエージェントの権限ルール設計に、読み取り専用サブエージェントの定義例はVB6を題材にした仕様書の起こし方にあります。
組み込みのExploreでなく自作する理由
組み込みのExploreも読み取り専用です。ただ、公式ドキュメントでは、ExploreとPlanはCLAUDE.mdとgit statusを読まずに始まると説明されています。設計書づくりでは、CLAUDE.mdに書いた業務用語とコード名の対応表を、サブエージェントにも読ませたい場面があります。自作のサブエージェントは、omitClaudeMd: trueを指定しない限りCLAUDE.mdを読み込みます。用語集の置き方はドメイン用語集をCLAUDE.mdに置く方法にまとめています。
もう1つの理由は再開です。ExploreとPlanは一度きりの実行で、エージェントIDを返さないため、後から続きを頼めません。自作のサブエージェントなら、maxTurnsで止まった分の続きを頼めます。
機能単位で分けて走らせる
機能の切り方は、コードの階層ではなく利用者の操作に合わせます。「注文キャンセル」「請求書の発行」のように、画面やAPIの1操作を1機能とします。ディレクトリ単位で分けると、1つの機能がまたがる複数のモジュールが別々の設計書に分かれてしまいます。
入口を先に洗い出す
まず、メインの会話で機能の入口の一覧を作らせます。公式のワークフロー集には、関連ファイルを探させ、次に連携を尋ね、最後に実行の流れをたどらせる三段階の聞き方が載っています。これを入口の洗い出しに使います。
src/ 配下で外部から呼ばれる入口(HTTPルート、バッチのmain、
キューのコンシューマー)を、ファイルパスと行番号つきで一覧にして。
機能名は業務用語で付けて。読んでいない箇所は一覧に入れないで。返ってきた一覧を人が見て、機能の粒度を直します。この一覧が、設計書ごとの分担表になります。
分担表の機能ごとに並列で任せる
分担が決まったら、機能ごとにサブエージェントを動かします。
design-doc-extractorで、次の3機能を並列に処理して。
1. 注文キャンセル(入口: src/orders/cancel.ts)
2. 請求書発行(入口: src/billing/issue.ts)
3. 在庫引当(入口: src/stock/allocate.ts)
各機能について、雛形の表3つと不明点を返して。公式ドキュメントには、独立した調査を複数のサブエージェントに同時に任せる並列調査のパターンが載っています。ただし同じページに、各サブエージェントの結果がメインの会話に戻るため、詳細な結果を返す数が多いとコンテキストを消費するという注意があります。サブエージェント自身もトークンを使います。
この注意は設計書づくりに直接響きます。表はコードの行数に比例して長くなるためです。次のように運用すると、メインの会話を詰まらせずに済みます。
- 1回に任せる機能は数個にとどめ、返ってきた表はすぐ
docs/design/のファイルに保存する - 保存したあとは、メインの会話では次の機能の入口を渡すだけにする
- 表そのものを会話で再度貼らせない。確認はファイルを読ませて行う
同時に動かせる数にも既定の上限があり、20個が動いている状態で新しく起動すると、Concurrent subagent limit reachedで失敗します。設計書づくりでここに届くことは少ないはずです。
サブエージェントはファイルを書きません。toolsにWriteを入れていないためです。保存はメインの会話が受け持ちます。書き込みの権限を分けておくと、読み取り側が既存のコードを誤って変更する事故を構造的に防げます。
返ってくる表の読み方
雛形の記入例を、架空の注文キャンセル処理で示します。コードは次のような形だったとします。
// src/orders/cancel.ts(架空の例)
export async function cancelOrder(orderId: string, userId: string) {
const order = await repo.find(orderId);
if (!order) throw new NotFoundError("order");
if (order.userId !== userId) throw new ForbiddenError();
if (order.status === "shipped") return { ok: false, reason: "SHIPPED" };
await repo.update(orderId, { status: "cancelled" });
try { await mail.send(order.email, "cancelled"); } catch (e) { /* 無視 */ }
return { ok: true };
}この関数から、次のような表になります。雛形に沿った例示であり、Claudeの実際の出力ではありません。
| No | 条件 | 結果 | 発生箇所 |
|---|---|---|---|
| 1 | 条件注文IDが存在しない | 結果NotFoundErrorを投げる | 発生箇所cancel.ts:3 |
| 2 | 条件注文者とユーザーが異なる | 結果ForbiddenErrorを投げる | 発生箇所cancel.ts:4 |
| 3 | 条件状態が出荷済み | 結果{ok:false, reason:"SHIPPED"}を返す | 発生箇所cancel.ts:5 |
| 4 | 条件メール送信が失敗 | 結果例外を握りつぶし、成功として返す | 発生箇所cancel.ts:7 |
注目したいのは4行目です。例外が呼び出し元に伝わらず、利用者には成功と返る。これは「分岐と例外」の表で、throw・return・catchの握りつぶしを区別する規則を置いた理由です。握りつぶされた失敗は、改修のときに気づかれずに残りやすい挙動です。
3行目も要注意です。出荷済みはエラーではなく通常の戻り値で表現されており、呼び出し側がthrowだけを見ていると取りこぼします。表の「結果」欄に、throwなのかreturnなのかを書かせる意味はここにあります。
行番号を検算してから残す
サブエージェントが書いた行番号は、ずれることがあります。そこで、保存した設計書の「根拠」を機械で確かめます。次のスクリプトは、書式パス:行またはパス:開始-終了をすべて拾い、ファイルがあるか、行範囲がファイルの行数を超えていないかを調べます。
#!/usr/bin/env bash
# scripts/check-evidence.sh 使い方: bash scripts/check-evidence.sh docs/design/cancel-order.md
doc="$1"
grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z0-9]+:[0-9]+(-[0-9]+)?' "$doc" | sort -u |
while IFS=: read -r f r; do
e=${r#*-}
if [ ! -f "$f" ]; then echo "NOFILE $f:$r"; continue; fi
n=$(wc -l < "$f" | tr -d ' ')
[ "$e" -gt "$n" ] && echo "RANGE $f:$r (全${n}行)"
done
exit 0bash scripts/check-evidence.sh docs/design/cancel-order.md出力が空なら、存在しない場所を指す根拠はありません。NOFILEやRANGEが出た行は、その行を差し戻してClaudeに読み直させます。
この検算で分かるのは、場所が実在することだけです。行の中身が主張と合っているかは別に見る必要があります。そこで、検算を通った設計書に対して、メインの会話で次のように頼みます。
docs/design/cancel-order.md の分岐と例外の表から、行を3つ選んで。
根拠の行範囲を実際に読み、条件と結果が一致しているか確かめて。
一致しない行があれば、直さずに指摘だけして。3行の抜き取りで全体の精度は分かりません。ただ、ずれが1行でも見つかれば、その設計書全体を疑う合図になります。
つまずきやすい点
落とし穴
動的な呼び出しは静的な読みでは追えない
DIコンテナの設定、リフレクション、設定ファイルで切り替わるクラス名は、コードを読むだけでは呼び出し先が決まりません。雛形に「不明点」の節を置き、追えなかった呼び出しはそこへ書かせます。表の「呼び出し先」に推測で書かせないことが、設計書の信頼性を保ちます。
- 範囲外を追わせすぎる: 依頼した機能の外まで読み進めると、返す表が膨らみ、メインの会話を圧迫します。規則に「範囲外は名前と場所だけ」と入れておきます
- サブエージェントは会話履歴を持たない: 公式ドキュメントでは、サブエージェントは新しい独立したコンテキストで始まり、メインの会話の履歴や読んだファイルを見ないと説明されています。業務上の前提は、依頼文に書くか、CLAUDE.mdに置きます
- 設計書をコードの正にしない: ここで起こした文書は、コードの現状を写したものです。仕様の正はコードで、設計書は読み手が早く着くための地図です。コードを変更したら、検算スクリプトを継続的インテグレーションで回し、行範囲が壊れた設計書を拾います
まとめ
詳細設計書を起こす作業は、読み取りと書き込みを分けると安定します。読むのはツールを絞ったサブエージェント、保存と検算はメインの会話とスクリプトの役割です。表の全行に根拠の位置を持たせれば、設計書の真偽を行単位で確かめられます。
最初は1機能だけで試すのが現実的です。分岐と例外の表に、握りつぶされた例外や出荷済みのような「throwされない失敗」が出てくれば、設計書として価値のある粒度に達しています。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。