ルートやコンポーネントから実際のアプリ挙動を抽出して、コードに一致するリビングスペックとギャップリストを作る方法を学びます。

人はアプリの挙動について意見が分かれます。記憶しているバージョンが違うからです。サポートは最後の荒れたチケットを覚えています。セールスはデモの流れを覚えています。エンジニアはその機能が意図していたことを覚えています。3 人に聞くと 3 つの自信に満ちた答えが返ってきて、現在のビルドとは一致しないことがよくあります。
時間がたつと、コードだけが最新のまま残ります。ドキュメントはずれ、チケットはクローズされ、気軽な修正が積み重なります。ルートに新しいバリデーションが入る。UI のトグルがデフォルトを変える。ハンドラが異なるエラーを返し始める。仕様を更新する人はいません(任意に思えるし、どの変更も小さすぎて記録する価値がないように感じるからです)。
それが予測可能な問題を生みます。チームは存在さえ知らなかったエッジケースを壊す変更を出荷します。QA はハッピーパスだけをテストして、ハンドラに埋もれたルールを見逃します。新しいメンバーは UI から挙動を真似るけれど、実際の制約を理解しません。利害関係者は合意された挙動を指し示す代わりに意見で議論します。
良い結果とは完璧なドキュメントではありません。共有された明確さです。誰もが「もし X をしたら何が起きるか?」「システムは何を保証するか?」を推測せずに答えられるべきです。驚きが減り、レビューサイクルが短くなり、「あ、既にそれできるよ」的な瞬間が減ります。チームが同じ真実を見ているからです。
仕様がコードと一致すれば、変更の計画が安全になります。出荷前に何が安定で何が偶発的で何が欠けているかが見えます。
リビングスペックは、アプリが今日実際にどう振る舞うかを短く編集可能にまとめたものです。1 回きりのドキュメントではありません。振る舞いが変わるたびに更新され、チームが信頼できるものになります。
「コードから書く機能仕様」(例:Claude Code を使う場合)とは簡単に言うと、ルートやハンドラ、画面から実際の挙動を読み取り、それを平易な言葉で書き下すことです。
有用なリビングスペックは、ユーザーが見られることとシステムが約束することに焦点を当てます。カバーすべきは:
カバーすべきでないのはコードの構成方法です。ファイル名やリファクタ計画を名前で出し始めると実装の詳細に逸れてしまいます。避けるべきは:
ギャップリストは別物です。仕様を書いている中で見つかった食い違いや不明点を小さなリストにします。
例:あるルートは 10MB を超えるファイルを拒否するが、UI は 25MB と表示する。チームがどちらが正しいか決めるまではギャップです。
小さく始めてください。アプリ全体をドキュメント化しようとすると、信頼されないメモの山になります。ユーザーが一文で説明できるスライスを選びます(「チームを招待する」「チェックアウト」「パスワードをリセットする」など)。良い範囲は単一の機能領域、1 モジュール、またはエントリから結果までの単一のユーザージャーニーです。
真実がどこにあるかでエントリポイントを選びます:
コードを読む前にいくつかの入力を集めておくと、不一致がすぐに目立ちます:既存の API ドキュメント、古いプロダクトノート、サポートチケット、人々が言う「既知の痛み」など。これらがコードに優先するわけではありませんが、エラーやエッジケース、権限などの欠落状態に気づく助けになります。
仕様フォーマットは地味で一貫したものにします。すべての仕様が同じ読みやすさだとチームは速く合意します。
この構造を繰り返し使えば、機能仕様は読みやすく、比較しやすく、更新しやすくなります。
まずはサーバのエントリポイントから始めます。ルートとハンドラは「アプリが何をするか」を具体的に示します:誰が呼べるか、何を送る必要があるか、何が返るか、システムで何が変わるか。
対象のルートを一覧にし、それぞれをユーザーの意図にマッピングします。POST /api/orders のように書くのではなく、「注文を確定する」や「下書きを保存する」と書きます。意図を平易に名付けられないなら、それは既に仕様のギャップです。
各ハンドラを読む際には、入力とバリデーションルールをユーザー向け要件として取り込みます。必須フィールド、許容される形式、実際にエラーを発生させるルールを含めます。例:「メールは有効でなければならない」「数量は最低 1」「開始日は過去にできない」など。
認可とロールチェックも同様に書きます。middleware: requireAdmin と書く代わりに、「管理者のみが任意の注文をキャンセルできる。通常ユーザーは 10 分以内に自分の注文のみキャンセルできる」と記述します。コードが所有権、機能フラグ、テナント境界をチェックしているなら、それも含めます。
次に出力と結果を記録します。成功は何を返すか(作成された ID、更新されたオブジェクトなど)?一般的な失敗はどんな形か(401 未認証、403 権限なし、404 見つからない、409 競合、422 バリデーションエラー)?
最後に副作用を記録します。副作用も挙動の一部です:作成/更新されるレコード、送信されるメールや通知、発行されるイベント、キューに入るバックグラウンドジョブ、他のフローをトリガーするものなど。これらは後で仕様に依存するチームを驚かせないために重要です。
ルートはアプリが何をできるかを教えてくれます。コンポーネントはユーザーが実際にどう体験するかを示します。UI を契約の一部として扱ってください:何が表示され、何がブロックされ、問題が起きたらどうなるか。
まず機能のエントリ画面を見つけます。ページコンポーネント、レイアウトラッパー、フェッチや権限、ナビゲーションを制御する「決定」コンポーネントを探します。実際の振る舞いはだいたいそこにあります。
コンポーネントを読むときは、ユーザーが感じるルールを取り込みます:アクションが無効になる条件、必須ステップ、条件付きフィールド、ローディング状態、エラーの表示方法(インラインかトーストか、オートリトライや「もう一度試す」ボタン)など。また、古いデータを一度表示するか、楽観的更新、最後の保存時刻の表示といった状態やキャッシングの挙動も記録します。
ユーザーに見えないフローも注意深く探します。機能フラグ、実験バケット、管理者専用ゲートの記述を検索してください。ログアウトしたユーザーをサインインにリダイレクトする、アクセス権のないユーザーをアップグレード画面に飛ばすといったサイレントなリダイレクトも記録します。
具体例:Change Email 画面では、保存はメールが有効になるまで無効、リクエスト中はスピナーが出る、成功で確認バナーが出る、バックエンドのバリデーションエラーは入力の下に表示される、のように記述します。コード内に newEmailFlow のようなフラグがあれば、両方のバリアントと違いも記録します。
各 UI フローを短いステップ(ユーザーが何をし、UI がどう応答するか)で書き、条件とエラーは影響を受けるステップの横に置きます。こうすると仕様が読みやすくなり、ギャップが見つけやすくなります。
ルートやコンポーネントからの生のメモは有用ですが、議論しにくいことが多いです。観察したことを PM、デザイナー、QA、エンジニアが読んで合意できる仕様に書き直します。
実務的なパターンはルートや画面ごとに 1 つのユーザーストーリーを作ることです。小さく具体的に保ちます。例:「サインイン済みユーザーとして、パスワードをリセットできるのでアクセスを回復できる」など。コードがロールごとに異なる振る舞いを示していれば(管理者と通常ユーザー)、フッタに隠すのではなく別々のストーリーに分けます。
次に受け入れ条件を書きますが、理想のプロダクトではなく実際のコードパスを反映させます。ハンドラがトークン欠如で 401 を返すなら、それが受け入れ条件です。UI がフィールドが有効になるまで送信を無効にするなら、それも条件です。
データルールは平易に書きます。驚きを生むルール(上限、並び順、ユニーク性、必須フィールド)は特に明確にします。「ユーザー名は一意である(保存時にチェックされる)」は「ユニークインデックス」よりわかりやすいです。
エッジケースは良いドキュメントと実用的なドキュメントの差になります。空の状態、null 値、リトライ、タイムアウト、API コール失敗時にユーザーに何が見えるかを明記します。
不明点に当たったら推測せずマークします:
これらのマーカーがあれば、チームへの簡単な質問に変わり、黙って想定で進むことを防げます。
ギャップリストは第2の Jira にしてはいけません。コードと意図された振る舞いが一致しない箇所、または誰も正しい振る舞いを説明できない箇所を、証拠に基づいて短く記録するものです。うまく運用すれば、合意を得るためのツールになります。
ギャップに含めるべきものを厳格にします:
ギャップを記録するときは次の 3 つを含めます:
証拠があればリストは感情論になりません。例:「POST /checkout/apply-coupon は期限切れクーポンを受け付けるが、CouponBanner.tsx は UI でブロックする。影響:収益とユーザー混乱。Type:bug か missing decision(意図を確認する)」
短く保ってください。最初のパスでは上限を 10 件にするなどハードキャップを設定します。もし 40 件見つかれば、パターン(バリデーション不整合、権限チェック、空の状態)にまとめて代表例だけ残します。
ギャップリスト内で日付やスケジュールを入れないでください。所有権が必要なら軽めに:誰が決定すべきか(プロダクト)や誰が振る舞いを検証できるか(エンジニア)をメモして、実際の計画はバックログ側で行います。
小さくトラフィックの多い範囲を選びます:クーポンと配送オプションのあるチェックアウトなど。目的は製品全体を書き直すことではなく、今日アプリが何をするかを捕まえることです。
まずバックエンドのルートから始めます。ルートにルールが現れることが多いです。POST /checkout/apply-promo、GET /checkout/shipping-options、POST /checkout/confirm のようなルートが見つかるかもしれません。
それらのハンドラから、平易な言葉で挙動を書きます:
次に UI コンポーネントを確認します。PromoCodeInput は成功レスポンスの後にのみ合計が更新され、エラーは入力の下にインラインで表示されるかもしれません。ShippingOptions コンポーネントは最初の読み込みで最安を自動選択し、ユーザーが変更すると価格の内訳が再計算されるかもしれません。
これで読みやすい仕様と小さなギャップリストができます。例:プロモのルートと UI でエラーメッセージが違う("Invalid code" と "Not eligible")、税の丸めルールが明確でない(ラインごとか注文合計か)など。
計画時はまず現状に合意し、それから何を変えるか決めます。意見で議論する代わりに、文書化された振る舞いを見て、一貫性を直すものを一つ選び、残りは「現状の既知の振る舞い」として保留します。
仕様が役に立つのはチームがそれが現状と一致すると合意したときだけです。エンジニア 1 人とプロダクト 1 人で短い読み合わせをします。20〜30 分で、ユーザーが何をできるかとシステムが何を返すかに集中します。
読み合わせでは記述をイエス/ノーの質問に変えます。「このルートに当たるとセッションなしでは常に 403 を返すか?」「この空の状態は意図したものか?」と問い、意図された振る舞いと偶発的に入った挙動を分けます。
編集する前に語彙を合わせます。UI に表示される言葉(ボタンラベル、ページタイトル、エラーメッセージ)を使い、内部名はエンジニアがコードを見つける手助けになる場合にのみ使います。これでプロダクトの言う「Workspace」と仕様の言う「Org」のような食い違いを避けられます。
最新に保つために所有と頻度を明確にします:
Koder.ai のようなツールを使う場合、スナップショットやロールバックで更新前後を比較でき、特に大きなリファクタ後に便利です。
仕様への信頼を失う最速の方法は、あるべき製品を記述して現状を書かないことです。厳密なルール:すべての記述はコードや実際の画面で指し示せる根拠が必要です。
もう一つの落とし穴は、コードの構造をそのままドキュメントに写すことです。「Controller -> Service -> Repository」と読む仕様は仕様ではなくフォルダマップです。ユーザー向けの用語で書いてください:何がトリガーか、ユーザーは何を見るか、何が保存されるか、エラーはどう見えるか。
権限とロールは往々にして最後にまとめられ、そこで破綻します。早い段階でアクセスルールを追加してください。どのロールが閲覧、作成、編集、削除、エクスポート、承認できるか、およびルールがどこで強制されるか(UI のみ、API のみ、両方)を明記します。
ハッピーパス以外を省略しないでください。本当の振る舞いはリトライ、部分的失敗、期限やクールダウンの時間ルールに隠れています。これらを第一級の振る舞いとして扱ってください。
ギャップを表面化する簡単な方法:
最後に、ギャップリストを動くものにしてください。各ギャップは「unknown」「needs decision」「bug」「missing feature, plan」のいずれかにラベル付けします。ラベル付けされないとリストは停滞し、仕様は再び死んでしまいます。
明確さ、カバレッジ、実行可能性の短いパスを行います。書いた人以外が読んでも今日の機能が何をするか、何が不明か理解できることが目標です。
新しいメンバーが初日に仕様を読んだつもりでチェックします。1 分で機能を要約できれば良い状態です。「どこから始まるか」「ハッピーパスは何か」と聞かれて答えられなければ冒頭を締め直してください。
確認項目:
各ギャップは具体的でテスト可能であるべきです。「エラーハンドリングが不明確」ではなく、「支払いプロバイダが 402 を返したとき、UI は汎用トーストを表示する;希望のメッセージとリトライ挙動を確認する」と書き、次のアクション(プロダクトに確認、テストを追加、ログを調査)と回答者を一人書きます。
1 つの機能領域を選び、時間を 60 分に制限してください。ログイン、チェックアウト、検索、管理画面など小さくて実際的なものにします。一文でスコープを書きます:何が含まれ、何が除外されるか。
ワークフローを一度端から端まで実行します:主要なルート/ハンドラをざっと見て、主要な UI フローを追い、観察可能な振る舞い(入力、出力、バリデーション、エラー状態)を書き出します。詰まったら質問をギャップとして残し、先に進んでください。
終わったらチームがコメントできる場所に仕様を共有し、出荷される振る舞いの変更は同じ納品ウィンドウで仕様も更新するというルールを設けます(たとえ 5 行でも)。
ギャップはバックログと分けておき、「unknown behavior」「inconsistent behavior」「missing tests」にまとめ、週次で簡単に見直して今やるべきかを決めます。
ドラフトや反復が遅く感じるなら、Koder.ai のようなチャットベースのビルダーで最初のバージョンを素早く作ると良いでしょう。機能を説明し、主要なスニペットやルート名を貼り付け、会話で表現を磨いてソースをエクスポートできます。目的は速度と共有された明快さであり、プロセスを大きくすることではありません。
まずは小さく、ユーザーに見える一片(例:「パスワードリセット」や「チーム招待」)から始めます。routes/handlers を読んでルールと結果を取りまとめ、次に UI フロー を読み取ってユーザーが実際に見るもの(無効化状態、エラー、リダイレクト)を拾います。あとは一貫したテンプレートで書き、わからない点は別のギャップリストに記録します。
原則として、現在のコードの挙動を真実と見なしてドキュメント化します。
挙動が偶発的だったり一貫性がない場合は、仕様で直接“直さない”でください。代わりに、証拠(どこでそれを見たか、どう振る舞うか)を添えてギャップとしてマークし、コードか仕様のどちらを更新するか決めてもらいましょう。
地味で繰り返せるものにしてください。実用的なテンプレートの例:
この形式は読みやすく、ミスマッチを見つけやすくします。
コードの注釈ではなく、ユーザー向けの要件として書きます。
例:
エラーが発生するトリガーと、ユーザーがそれをどう見るかも記録してください。
観察可能なものに注目してください:
副作用は他の機能やサポート/運用に影響するので重要です。
UI と API が食い違う場合は、それを確定するまで ギャップ として記録します。
記録する内容:
その後、チームでどちらを採用するか決め、コードと仕様の両方を更新します。
ギャップリストは小さく、証拠に基づくことが重要です。各項目には:
スケジュール化せず、意思決定のための材料として扱います。
明確にドキュメント化してください。特に:
これらはバグや驚きの主要原因です。
短時間で済ませましょう:エンジニア 1 人とプロダクト 1 人で 20〜30 分の読み合わせをします。
文をイエス/ノーで検証する形式にします(例:「このルートにアクセスすると常に 403 を返すか?」)。UI 表示の語彙で揃えると、認識のズレが減ります。
コードの近くに置き、出荷の流れに更新を組み込みます。
実務的なルールの例:
小さな頻繁な編集を目標にしてください。