はじめに
最近の AI コーディングをやっていて、こんな感覚はないだろうか。
- AI に「いい感じに作って」と言うと、技術的には正しいが意図とズレたコードが返ってくる
- レビューで「いや、こうじゃなくて……」と差し戻し続ける
- 同じプロジェクト内で、AI が出力するコードの方針が日によってブレる
これらの問題は、コード以前に「仕様」が言語化されていない ことに起因することが多い。
ここで効くのが、最近名前を聞くようになってきた 仕様駆動開発(Specification-Driven Development, SDD) だ。OpenSpec などのフレームワークも整理されてきており、AI コーディング時代の品質保証手法として定着しつつある。
本記事では、SDD を 3 層モデルで整理し、AI コーディングの現場で実装するときの設計原則を共有する。
SDD の3層モデル
SDD を実プロジェクトに導入するとき、以下の 3 層で仕様を分けて管理するのが扱いやすい。
┌─────────────────────────┐
│ Layer 3: 受け入れ仕様(Acceptance) │ ← 検証可能・テストに変換できる
├─────────────────────────┤
│ Layer 2: 振る舞い仕様(Behavior) │ ← Given/When/Then で書ける
├─────────────────────────┤
│ Layer 1: ドメイン語彙(Vocabulary) │ ← 用語と概念の定義
└─────────────────────────┘
Layer 1: ドメイン語彙(Vocabulary)
最下層は 用語と概念の定義 だ。
例えば EC サイトを作るなら、こんな用語を定義する。
# domain.yml
vocabulary:
product:
description: 顧客が購入できる単一のアイテム
states:
- draft # 編集中、購入不可
- listed # 公開中、購入可
- archived # 過去販売、購入不可
order:
description: 顧客が複数 product を1回でまとめて購入した単位
states:
- pending
- paid
- fulfilled
- cancelled
ここを最初に固めておくと、AI に渡すコンテキストの「言葉」が揃う。
よくある失敗: product と item、order と cart を曖昧に使い分けてしまうと、AI が両方のパターンで出力してくる。気付いた頃には DB スキーマもコードもブレている。
Layer 2: 振る舞い仕様(Behavior)
中間層は Given / When / Then で書ける振る舞い。
## US-021: 在庫切れ商品を購入できない
Given: 商品 X の在庫数が 0
When: 顧客が商品 X をカートに入れて購入ボタンを押す
Then: 「在庫切れです」エラーが返り、order は作成されない
この粒度の仕様を AI に渡すと、実装方針がぐっと安定する。AI は「在庫切れ判定はどのタイミングでやるか(カート追加時 / 購入確定時)」を勝手に決めなくて済むからだ。
Layer 3: 受け入れ仕様(Acceptance)
最上層は 検証可能な受け入れ条件。
振る舞い仕様をそのまま e2e テスト or 結合テストに変換できる粒度にする。
// __tests__/order.acceptance.test.ts
describe("US-021: 在庫切れ商品を購入できない", () => {
it("在庫0の商品をカートに追加→購入確定で OutOfStockError", async () => {
const product = await fixture.createProduct({ stock: 0 });
const customer = await fixture.createCustomer();
await expect(
placeOrder({ customer, items: [{ product, qty: 1 }] })
).rejects.toThrow(OutOfStockError);
});
});
ここまで来ると、AI に対して「この受け入れテストを通すように実装して」という依頼が可能になる。
なぜ AI コーディング時代に SDD が効くのか
3 つの理由がある。
理由①: AI への入力の質が上がる
AI コーディングの精度は、入力プロンプトの質に強く依存 する。「いい感じに作って」では揺らぐが、Layer 2 の振る舞い仕様+ Layer 1 の語彙を渡すと、出力は劇的に安定する。
理由②: AI 出力の評価が機械化できる
Layer 3 の受け入れ仕様を最初に書いておくと、AI が出した実装が「合っているかどうか」をテスト実行で判定できる。レビューで毎回「これ動くんですか?」と聞かなくて済む。
理由③: 仕様の変更履歴が、AI が読めるドキュメントになる
仕様を Git で管理しておけば、AI に「過去にこういう経緯で archived 状態を追加した」というコンテキストを渡せる。複数ターンに渡る開発で、AI が過去の決定を忘れずに済む。
実装するときの設計原則
SDD を実プロジェクトに導入するとき、以下の 4 つを守ると失敗しにくい。
原則1: 3 層を1ファイルに混ぜない
ドメイン語彙・振る舞い仕様・受け入れ仕様は 別のファイル / 別のディレクトリ で管理する。
spec/
├── vocabulary/
│ ├── product.yml
│ └── order.yml
├── behaviors/
│ ├── US-001-product-listing.md
│ ├── US-021-out-of-stock.md
│ └── ...
└── acceptance/
├── product.acceptance.test.ts
└── order.acceptance.test.ts
混ぜると、AI への入力で「どの層の話をしているか」がブレる。
原則2: 振る舞い仕様 1 つ = テスト 1 つ
US-021 のような 1 つの振る舞い仕様には、1 つ以上の受け入れテストを必ず紐付ける。「仕様はあるけどテストがない」状態は、SDD の最大のアンチパターン。
原則3: AI 用のコンテキスト集約ファイルを置く
AI(Claude Code など)に渡す統合コンテキストファイルを spec/CONTEXT.md のようなパスに置く。Layer 1 の語彙を要約しつつ、現在進行中の振る舞い仕様へのリンクを並べておくと、毎回 AI への説明コストが減る。
原則4: 仕様変更は PR で議論する
コード変更だけ PR で議論しがちだが、SDD では 仕様変更の PR が一級市民。spec/ ディレクトリの変更は、コード変更とは別 PR にして、レビュアーは仕様の妥当性を議論する。
どこから始めるか
既存プロジェクトに SDD を後付けするなら、こう進めるのがおすすめだ。
- まず Layer 1(語彙)を 1 ファイル書く。プロジェクト内で頻出する 10 個の名詞を定義するだけでOK
- 次に Layer 3(受け入れテスト)を 3 件書く。重要機能の e2e/結合テストでよい
- 最後に Layer 2(振る舞い仕様)を、新規機能から書き始める
上から書こうとすると挫折するので、「下から始めて 1〜2 ヶ月で 3 層揃える」が現実的だ。
まとめ
SDD は新しい概念ではないが、AI コーディング時代に再評価されるべき手法だ。
- Layer 1(語彙)で AI の言葉を揃える
- Layer 2(振る舞い仕様)で AI の判断を安定させる
- Layer 3(受け入れテスト)で AI の出力を検証できる
この 3 層を揃えるだけで、AI コーディングの品質は底上げされる。
未経験者向けの講座を運営しています
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全20セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md / Skills 設計までセットで含みます。
- 無料体験版(git clone してすぐ動く・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- 教材完全版+月5,500円〜のCTOメンタリング(全20セッション+チャット質問し放題)→ https://menta.work/plan/20251?ref=qiita
- YouTube『AIエンジニア情報局』(AI×開発ニュースを1本5分でキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita
※ Qiita 読者の方には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。