アンチパターン ── やりがちな失敗と脱出法
仕様駆動開発の失敗は、多くの場合「仕様の書き方」か「プロセスの歪み」に起因する。
この章では実践的によく見られる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を先に |
| 5 | Prior Decisions省略 | セッションごとに実装スタイルが変わる | 「AIが覚えているはず」 | CLAUDE.md + Prior Decisionsの2段構え |
次の章では、個人からチームへのSDDの展開方法を解説する。