目次を表示する

仕様駆動開発(SDD)入門 ── AI時代の「正しい作り方」

アンチパターン ── やりがちな失敗と脱出法

アンチパターン ── やりがちな失敗と脱出法

仕様駆動開発の失敗は、多くの場合「仕様の書き方」か「プロセスの歪み」に起因する。

この章では実践的によく見られる5つのアンチパターンと、その脱出法を解説する。


アンチパターン1: 仕様の肥大化(Specification Bloat)

症状

仕様が「実装の設計書」になっている。

❌ 悪い例:

## Tasks
1. `src/services/wishlistService.ts` を作成
2. `WishlistService` クラスを定義
3. `constructor(private repo: WishlistRepository)` を追加
4. `addItem(userId: string, productId: string): Promise<WishlistItem>` メソッドを追加
5. `this.repo.countItems(wishlist.id)` でアイテム数を取得
6. `if (count >= 100)` で上限チェック
7. `throw new WishlistLimitError()` でエラーをスロー
...(50行続く)

根本原因

「AIに任せると違うものができる」という不安から、実装の細部まで仕様に書き込もうとする。

脱出法

仕様は「何を達成するか」を書く場所であり、「どう実装するか」はAIに委ねる。

✅ 良い例:

## Constraints
- ウィッシュリストの上限は100件(超えたら409 Conflict を返す)
- 認証にはsrc/middleware/auth.tsを使用すること
- エラーフォーマットは既存の { error: { code, message } } に準拠

## Tasks
1. ウィッシュリストの商品追加エンドポイント(POST /api/wishlist/items)
2. 上限チェックのビジネスロジック
3. 統合テスト

判断基準:「この仕様を別のエンジニアが読んで、実装方法に選択肢の余地があるか?」余地があれば仕様は適切。余地がゼロになるほど細かければ書きすぎだ。


アンチパターン2: 曖昧なOut of Scope(スコープのあいまい除外)

症状

❌ 悪い例:

## Out of Scope
- 高度な機能は後で
- 検討中のものはここに含まない
- OAuth(maybe)

根本原因

「後で判断すればいい」という先送り。しかしAIは「maybe」を「やったほうがいい」と解釈することがある。

脱出法

除外する理由と時期を明記する。

✅ 良い例:

## Out of Scope ── 確定除外
- 複数ウィッシュリスト管理(次フェーズ、Q3 2026以降に検討)
- OAuth連携(依存チームのAPI準備待ち、Jira: TICKET-4512)
- 2FA(セキュリティ要件確定まで保留、2026年7月予定)
- ゲストユーザーのウィッシュリスト(認証設計の方針が未確定)

除外理由の種類

  • 「次フェーズ」─ 将来やる予定がある
  • 「○○チームが担当」─ 責任が別にある
  • 「未確定」─ 要件がまだ決まっていない
  • 「MVP外」─ 初期リリースには含めない決定をした

アンチパターン3: テスト不在の完了(Verification省略)

症状

❌ 悪い例:

## Verification
- 手動でUIを確認する
- 動作を確認したら完了

または、Verificationセクションが存在しない。

根本原因

「自分で見ればわかる」という感覚。しかし手動確認は環境依存バグを見逃し、回帰テストが機能しなくなる。

脱出法

Verificationは「テストをPASSすれば完了」の形で書く。

✅ 良い例:

## Verification
- [ ] 統合テスト: tests/wishlist.integration.test.ts の全テストがPASS
- [ ] 未認証リクエストに401が返る(テストで自動確認)
- [ ] 100件超で409が返る(テストで自動確認)
- [ ] GET /api/wishlist のレスポンスタイムが100ms以内(テストで計測)
- [ ] カバレッジ: services/wishlistService.ts が80%以上

人手による確認が必要な場合も、自動テストを補完として使う:

- [ ] 自動テスト: 全項目PASS(上記)
- [ ] 手動確認: 実際のブラウザでウィッシュリストボタンが表示される(UIテスト環境で)

アンチパターン4: 仕様のドリフト(Spec Drift)

症状

実装が進むうちに、コードがSPEC.mdと乖離し始める。

初期 SPEC.md:
  「ウィッシュリストは1ユーザーにつき1つ」

実装中に変更:
  AIが「複数リスト対応のほうが便利では?」と提案
  エンジニアが承認して実装を変更

結果:
  SPEC.mdには「1つ」と書いてあるが
  コードは複数リスト対応になっている
  テストは中途半端に両方をカバーしていて混乱

根本原因

仕様変更をSPEC.mdに反映せずにコードだけを変更してしまう。またはAIの提案を素直に受け入れてしまう。

脱出法

「コードよりSPEC.mdが正しい」を徹底する。

変更フロー:

仕様変更したい場合:
  1. SPEC.mdを先に修正する
  2. 変更内容をチームでレビューする
  3. 承認後にAIに実装を依頼する
  4. テストを再実行して全てPASS確認

AIが「仕様外の改善」を提案してきた場合:
  - 次のSPEC.mdのIssueとして記録する
  - 今回の実装には含めない

Gitを使う場合、SPEC.mdをProtected Branchの変更対象にしてPRレビューを必須にする運用も有効だ。


アンチパターン5: Prior Decisions省略(前提の暗黙化)

症状

SPEC.mdにPrior Decisionsがなく、AIが毎回「どのライブラリを使うか」「エラーはどう返すか」を自分で判断する。

セッション1での実装:
  APIのエラーは { error: { code, message } } で返される

セッション2での実装(Prior Decisions未記載):
  AIが別の判断をして { message, statusCode } で返す

結果:
  フロントエンドのエラーハンドリングが壊れる

根本原因

「コンテキストはAIが覚えているはず」という誤解。AIのセッションはリセットされる。

脱出法

CLAUDE.md(プロジェクト全体の常設仕様)と、SPEC.mdのPrior Decisionsの2段構えで前提を記録する。

# CLAUDE.md(全機能共通の前提)
## エラーフォーマット
{ error: { code: string, message: string } }
## 認証
JWT、src/middleware/auth.tsを使用
## DB
PostgreSQL + Prisma、直接クエリ禁止

# SPEC.md(機能固有の前提)
## Prior Decisions
- ユーザーの商品テーブルはproducts(id, name, price, imageUrl)
- 既存の wishlistテーブルはまだないので新規作成
- 共有トークンはUUID v4

CLAUDE.mdは「プロジェクトの憲法」として常に存在し、SPEC.mdの Prior Decisions はその機能に固有の追加情報だけを書く。


アンチパターンまとめ

#アンチパターン症状根本原因脱出法
1仕様の肥大化仕様が50行以上の実装設計書になるAIへの不安「何を達成するか」に絞る
2曖昧なOut of Scope「後で」「maybe」が多い先送り癖除外理由と時期を明記
3テスト不在の完了手動確認で終わり「自分で見ればわかる」Verificationはテスト基準で
4仕様のドリフトコードとSPEC.mdがズレる変更をSPECに反映しない仕様変更はSPEC.mdを先に
5Prior Decisions省略セッションごとに実装スタイルが変わる「AIが覚えているはず」CLAUDE.md + Prior Decisionsの2段構え

次の章では、個人からチームへのSDDの展開方法を解説する。