「バイブ」にしないエージェント開発:フェーズ分割TDDの実践(2025年後期版)
概要
- エージェントでTDDを適用する方法の紹介である。
- 4フェーズ(型→テストケース→テスト内容→実装)を人間のレビューで刻みながら進める方法の紹介。
- 各フェーズでのレビューに伴う認知負荷が下がることは最大のメリット。
- 最後に各フェーズで求められる能力について書籍とともに紹介する。
導入
はじめに。
erukiti さんの「2025/10/20時点で最良のAIコーディングプロセス」 という記事読んだ。
公開数日で400いいねを超える素晴らしい記事だ。
Claude Code が出始めた今年の春と比べればAIエージェントによるコーディングはかなり進歩した。
それでも 2025年10月現在の AIエージェントはまだまだだ。
まだ嘘つくし微妙な設計や実装もする。
これをどう制御し、品質の高い成果に導くかが重要だ。
氏の記事は、そのための考え方と具体的方法を示しており、実践のためのストイックさが参考になった。(私もこんな記事を書きたいものである)
近頃、『エージェントがコードを書きテストも通るなら、中身を理解する必要はない』という言説を目にする。
私も少し前まではそれもアリな方法なのかなと思っていた。しかし、度重なる失敗を重ね、その考えはとんでもないと痛感した。
(負債をAIに丸投げし、後で直すのか。その「あと」は来ないことが多いし、かなり手間かかるよ)
詳細まで人間が責任を持つ必要がある。したがってコードは理解しないといけないということだろう。
この記事で紹介する方法論
今回は私が個人的に行っているAIエージェントを使用したコーディング方法を紹介する。
作業を4つのフェーズに分け、各フェーズで人間のレビューを挟みながら、TDD(テスト駆動開発)で進めるやり方だ。
認知負荷を軽減し人間によるレビューを効率よく行えるようになる。出力任せのバイブコーディングではなく、エンジニアリングの技術を適用し品質を高めるやり方として提案したい。
TDD Codex や Claude Code でも推奨されている方法。
大きなタスクを分割する。
人間のエンジニアと同様に、Codexは複雑な作業もより小さく集中したステップに分割することで、よりうまく対応できます。小さなタスクの方がCodexにとってテストしやすく、あなた自身もレビューしやすくなります。Codexにタスク分割の手助けを頼むことも可能です。
https://www.anthropic.com/engineering/claude-code-best-practices
テストを書き、コミットする;コードを書き、繰り返しコミットする。
これは、ユニットテスト、インテグレーションテスト、エンドツーエンドテストで簡単に検証できる変更の際にAnthropicで好まれているワークフローです。テスト駆動開発(TDD)は、エージェント的なコーディングと組み合わさることでさらに強力になります。
フェーズ分割TDDの進め方
フェーズ1 型、設計
まずは実装のスコープと責務の境界を決める。
そのために 実装する関数やオブジェクトの型の定義をする。
ここで曖昧な決断を下してしまうと、後工程で面倒なことになるのでしっかり行う。
// Phase 1: 型とインターフェース
export type EventId = string;
export type EventStatus = "processing" | "completed" | "failed";
export interface Event {
id: EventId;
title: string;
startedAt: Date;
status: EventStatus;
// 失敗時のみ理由が生まれるため optional。
// 必然性のない optional を避ける、の原則に照らして根拠を明示する。
errorReason?: string;
}
export interface EventRepository {
beginProcessing(params: {
id: EventId;
title: string;
startedAt: Date;
}): Promise<
| { type: "process"; event: Event }
| {
type: "skip";
event: Event;
skipReason: "already_completed" | "already_failed";
}
>;
markCompleted(args: { id: EventId }): Promise<void>;
markFailed(args: { id: EventId; reason: string }): Promise<void>;
}
レビュー観点
- こちらが意図している内容とスコープで実装しようとしているか型から読み解く。
- 余計なフォールバックや互換性を持たせようとしていないか。(特にオプショナル引数に注意)
- 既存プロジェクトの実装方針に沿っているかを確認する。
フェーズ2 テストケース
要件をかみ砕いて機能要件と非機能要件を自然言語で表現し、テストに落とし込む。
// Phase 2: 仕様としてのテストケース(中身はまだ書かない)
import { describe, it } from "vitest";
describe("EventRepository.beginProcessing", () => {
it("存在しないイベントは processing で新規作成し process を返す");
it("processing の既存イベントは process を返す(継続処理)");
it("completed の既存イベントは already_completed で skip を返す");
it("failed の既存イベントは already_failed で skip を返す");
});
describe("EventRepository.markCompleted / markFailed", () => {
it("markCompleted は status を completed にする");
it("markFailed は status を failed にし、理由を記録する");
});
レビュー観点
- 機能要件/非機能要件を満たしているか確認
- ここで考慮漏れに気づくことも多い。エージェントと壁打ちし、型設計に戻ることも
- テスト内容が網羅的かを確認する。特に境界値テストに弱くなりがちである。
- テストケースが直感的に理解できるかも大事。
フェーズ3 テスト実装
フェーズ2までの抽象レベルが定まったら具体的なコードを書き始める。
テストケースの記述に沿ってテストの具体コードを書かせるのだ。
TDDなので完了後にテスト失敗を確認させることを忘れずに
// Phase 3: テスト内容の作成
import { createInMemoryEventRepository } from "./inmemory-event-repository";
describe("EventRepository.beginProcessing", () => {
let repo: ReturnType<typeof createInMemoryEventRepository>;
beforeEach(() => {
repo = createInMemoryEventRepository();
});
it("存在しないイベントは processing で新規作成し process を返す", async () => {
const result = await repo.beginProcessing({
id: "E1",
title: "kickoff",
startedAt: new Date(0),
});
expect(result.type).toBe("process");
expect(result.event.status).toBe("processing");
});
// ...以下省略
レビュー観点
- 正しいテストが書かれているか。 これにつきる。
- 十分な assertion はあるのか
フェーズ4 実装
フェーズ3まで完了すればやっと実装の準備が整う。
// Phase 4: プロダクションコードの実装
export function createInMemoryEventRepository(): EventRepository {
const store = new Map<EventId, Event>();
return {
async beginProcessing({ id, title, startedAt }) {
const current = store.get(id);
// status による分岐 (省略)
// processing は継続実行
return { type: "process", event: current };
},
async markCompleted({ id }) {
const e = store.get(id);
if (!e) return;
store.set(id, { ...e, status: "completed", errorReason: undefined });
},
async markFailed({ id, reason }) {
const e = store.get(id);
if (!e) return;
store.set(id, { ...e, status: "failed", errorReason: reason });
},
};
}
レビュー観点
- コードから変な匂いがしないかを点検する -> ここは開発者の直感を働かせるべきところ。
- フェーズ3まで筋道よく進められていればコード実装はほぼ問題ない。ここでつまずく場合は、それまでの工程に原因が潜んでいるケースが多い。
- 型とテストで保証しきれない品質 はどこに現れるかを考える。
- パフォーマンスが十分かを確認する。
各フェーズのマトリックス
これら4つのフェーズは抽象度コード対象を軸とした四象限の図で表すことができる。
左上の高抽象度の設計からは始まり、右回りでテストを経由して、実装に至るフローだ。
それぞれのフェーズでのレビュー時に 抽象度と対象が定まる ので認知負荷が低く感じるのだろう。

ルールファイル例
AGENTS.md や CLAUDE.md などルールファイルに以下のようなルールを加えておく良い。
エージェントの作業が各フェーズで止まりやすくなり、人間によるレビューをしやすくなる。
以下は node を利用したプロジェクトの例。
### 手順
**開発手法はTDDを原則とする**
**必ず以下の手順を守ること**
**(重要)特に指示がない限り、1フェーズごとにユーザーのレビューを受けてから次のフェーズに進むこと**
**スコープ外の作業はしないこと**
1. 型やインターフェースの定義作成/変更をする。
- **2に進む前に、必ずユーザーによるレビューをうけること**
2. 仕様としてテストケース表現を記述(it("~") を記述)し保存する
- **3に進む前に、必ずユーザーによるレビューをうけること**
3. テストの内容を作成(修正)する
- 対象テストが `npm run run`(型チェックなし) を実行して **失敗することを確認**
- **4に進む前に、必ずユーザーによるレビューをうけること**
4. 実装する
- `npm run build` を実行してエラーがないことを確認
- `npm run test` を実行して成功することを確認
- `npm run lint` を実行してエラーがないことを確認
- `npm run format` を実行してエラーがないことを確認
- ※ 失敗した部分を修正する
このように「フェーズごとに止まれ、レビューを待て」と強く伝えているつもりだが、たまにレビューをすっ飛ばして実装まで進めようとすることがある。工夫案求む。
メリットデメリット考察
この進め方の一番のメリットは、認知負荷が下がる こと。
フェーズごとに抽象度を固定できるので、レビュー時に見るべき対象が揃う。
型とテストケースは高い抽象度で要件をすり合わせ、テスト内容と実装は具体に触れ現実を確認する。
もう1つは、認知負荷が下がったことによりエージェントによるコーディングに対する違和感をキャッチしやすくなるということ。
不要なプロパティや曖昧な命名、境界のにじみなどに早い段階で気づきやすくなる。
バイブコーディングによる大量のコード生成は人間のレビューがおろそかにしやすい。そこでこのステップバイステップが効いてくる。
これだけ分割しても各フェーズでのコード量が多いのであればそもそも対象タスクを分割した方が良い。このタスク粒度もフェーズ1で気づくことが多い。
デメリットは 速度低下や、長時間ほったらかしでコーディングさせることが難しいこと。
フェーズで止まってレビューを挟むのでどうしてもスピードがでない。
ただ、そもそもこの方法でプログラミングをするというのは、もはや本来の意味のバイブコーディングとは別物である。エージェントのスピードを生かしつつ品質とのトレードオフのバランスを考える。プロなんだから両方こだわりたいところである。
最近話題になった バイブエンジニアリング 、ここにも通ずるものがあると感じた。(以下、記事冒頭の抄訳)
最近、「バイブコーディング」は、AIを使って速く、ざっくばらんで無責任にソフトウェアを構築する方法、つまりプロンプトだけで動き、コードが実際にどう動作するかには全く注意を払わないやり方を指す言葉としてかなり定着したように感じます。これによって、用語のギャップが生まれています。では、熟練のプロフェッショナルたちが、LLMを活用して自分の仕事を加速させつつ、自信と誇りを持って自分たちが作るソフトウェアに責任を持つ…そんなスペクトラムの対極には、どんな言葉を当てはめればよいのでしょうか?
私はこれを「バイブエンジニアリング」と呼ぶことを提案する。少し冗談めかしてはいるが、本気でもある。
各パートで必要な能力
各パートでは異なる視点から人間によるレビューをするが、やはりエンジニアリングの技術に支えられるものである。
どのような技術が必要なのだろうか。書籍とともに紹介してみる。
私が読んだことのある書籍で特に良いと感じたもの中心に列挙していく。
フェーズ1 型・設計
必要な能力
- 要件からドメインのモデリングをする能力。すべてはここから始まる
- 結合と凝集のバランスをとる判断、責務境界の設計(依存の向き/抽象の層/変更容易性など)。
- 言語仕様と型システムの素地(代入・スコープ・評価戦略など)
参考書籍(設計+言語+他言語:計7冊)
- 改訂新版 良いコード/悪いコードで学ぶ 設計入門 — 良し悪しの対比で設計・実装の意思決定を言語化できるようになる一冊。型・責務・依存の設計を現場感で整えるのに効く。悪いコード例がたくさん載っているので良いコードと比較して理解しやすい。
- ソフトウェア設計の結合バランス — “結合”という観点からモジュール化と複雑性管理を徹底解説。抽象度/汎用性と実務への応用でバランスの良い中級者向けの書籍だと思った。
- プログラミング言語の基礎概念 — 型システムの基礎が学べて良い。本格的な型システムの書籍 Types and Programming Languages を読む前の基礎概念整理に役立つ。
- すごいHaskell たのしく学ぼう — 純粋関数型の思考を身につけられる。関数でガチガチに、副作用の管理、モナドってなんぞやと。
- プログラミングRust 第2版 — 所有権/借用など低レイヤーも型で。Result/Option型など近年のプログラミング言語の良いとこ取り。Rust でコンパイルが通れば大体動くを体験することは型の重要性を知ること。
- ゼロから始める Lean 言語入門 — Universe で型の多段的な仕組み(ネスト構造など)を知ることができる。依存型って素晴らしい。静的解析でここまでできるのかと驚かされる言語。
フェーズ2, 3 テスト
必要な能力
- 要件をシナリオで落としこむ。テスト名が仕様になるよう書けること。
- 価値あるテストの見極め(壊れにくさ/コスト/役割分担:ユニット・統合・E2E)。
- テスト設計の基本パターン(Arrange–Act–Assert、境界値、同値分割、状態遷移)。
参考書籍(テスト関連:2冊)
- 単体テストの考え方/使い方 — 良いテストの4本柱やモックの扱いなど、テストの全体象とどう適用するかの勘所をつかむのに。
- 実践プロパティベーステスト — 「性質」に着目してテストするという、抽象度の一段高い品質補償方法を学ぶと考えや手段の幅が広がる。
フェーズ4 実装
必要な能力
- 要件/仕様に沿った正しい実装(意図が伝わる命名・分割・コメント)。
- 仕様で決めた不変条件を コードでどう表現するのか
- 変更容易性を保つエラーハンドリング/ロギング/パフォーマンスの勘所。
参考書籍(実装系:2冊)
- リーダブルコード — 前述の通り、人がレビューするなら「読みやすさは正義」を徹底。基礎がしっかりしてれば、いわゆる「コードの嫌なにおい」を察知しやすくなる。
- 達人プログラマー 第2版 — リーダブルコードに同じ。
番外 LLM
LLMの基礎となるアーキテクチャ、特にトランスフォーマーだけでも押さえておくと LLMにできる限界や指示の勘所、今後の進化を見通すヒントを得られる。ざっと全体像を俯瞰しておくだけでも、日々のAI関連の話題への理解度がずいぶん違うと思っている。
基礎知識0から始めるとかなり難しけど頑張りたいところ。
参考書籍(LLM:2冊)
- 大規模言語モデル入門 — まずは Tokenizer や Transformer の概要レベルだけでもざっと押さえておきたい。
- 原論文から解き明かす 生成AI — 論文を軸にしたアプローチという汎用的スキルも養える。
まとめ
どうせまた近い将来、技術進歩によってAIの常識は変わる。そして具体の方法もそれに追従して全く別物に見えるだろう。
しかし、開発者の基礎やエンジニアリング能力の重要性は、もうしばらく必須な期間が続くと考えている。
当面はAIエージェントとの協業方法をブラッシュアップし続ける必要がある。
そして、2025年後期の現在はエンジニアリングの技術に支えられた人間によるレビューが必須であり、そのやり方は単にバイブコーディングするだけでは難しい。大いに工夫する必要がある。
Discussion