はじめに
本記事は、AIコーディングツール「Cursor」を使用してゼロから構築された大規模プロジェクト「FastRender」の開発から得られた汎用的なAIコーディングの知見をまとめたものです。
FastRenderはCursorの並列AIエージェント研究1の一環として開発されたRust製ソフトウェアです。
| 指標 | 規模 |
|---|---|
| コード行数 | 約865,000行 |
| ファイル数 | 5,730個 |
| コミット数 | 29,858 |
| テスト関数 | 18,496個 |
この規模のプロジェクトのソースコードを分析することで、どのプロジェクトにも応用できる普遍的なAIコーディングの知見が見えてきました。
AIコーディングが生み出すコードの特徴
MUSIBUX CodeGraph でコードベースを分析した結果、AIコーディング特有のパターンが明らかになりました。
1. 圧倒的なドキュメント量
| 指標 | 数値 |
|---|---|
| ドキュメントコメント | 29,819箇所 |
| 仕様・リファレンス参照 | 6,705箇所 |
| SAFETYコメント | 593箇所 |
AIは人間よりも一貫してドキュメントを書き続けます。疲れることがないため、すべての関数・モジュールに説明を付ける傾向があります。
//! このモジュールは○○を処理します。
//!
//! # 概要
//! ○○アルゴリズムを実装しています。
//!
//! # 参考
//! - <https://example.com/spec>
/// ○○を計算して返します。
///
/// # Arguments
/// * `input` - 入力データ
///
/// # Returns
/// 計算結果。失敗時はエラー。
pub fn calculate(input: &Data) -> Result<Output> { ... }
応用: AIにコードを書かせる際は、ドキュメントも同時に生成させましょう。後から書くより品質が高くなります。
2. テストとコードの同時生成
| 指標 | 数値 |
|---|---|
| テストモジュール | 1,297個 |
| テスト関数 | 18,496個 |
| 1モジュールあたり平均 | 約14テスト |
AIは「実装→テスト」ではなく「実装とテストを同時に」生成します。これは人間が意識的にTDDを実践するより自然に行われます。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_positive() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, -1), -2);
}
#[test]
fn test_add_zero() {
assert_eq!(add(0, 0), 0);
}
}
3. エラーハンドリングの一貫性
| パターン | 使用数 |
|---|---|
Result<T> 型 |
10,164箇所 |
| 明示的エラー型 | 分類された5種類 |
AIはエラーハンドリングを一貫したパターンで実装します。プロジェクト全体で統一されたエラー型が使われる傾向があります。
/// プロジェクト共通のResult型
pub type Result<T> = std::result::Result<T, Error>;
/// エラーの分類
pub enum ErrorKind {
Parse, // 解析エラー
Validation, // 検証エラー
IO, // 入出力エラー
Network, // ネットワークエラー
Other, // その他
}
4. ファイルサイズの二極化
| 分類 | 行数 |
|---|---|
| 最大ファイル | 77,266行 |
| 上位5ファイル平均 | 45,574行 |
| 全体平均 | 863行 |
AIは機能的凝集度を優先するため、関連コードを1ファイルにまとめます。結果としてコアモジュールは巨大化し、ユーティリティは小さいままという二極化が生じます。
注意点: 巨大ファイルはIDEのパフォーマンスやコードレビューに影響します。適切な分割ルールをAGENTS.mdで指示することが重要です。
AIコーディングの重要な知見
知見1: 成果物への明確なフォーカス
最終成果物を明確に定義し、それ以外を手段として位置づける
AIは「何でも最適化できる」ため、目標が曖昧だと本来の目的から逸脱しやすくなります。
❌ 悪い例: 「コードを改善して」
→ AIがパフォーマンス最適化、リファクタリング、ドキュメント追加を無限に行う
✅ 良い例: 「ユーザーが検索結果を3秒以内に見られるようにして」
→ 明確なゴールがあるため、必要な改善のみ行う
90/10ルールとして定式化できます。
| 割合 | 対象 | 説明 |
|---|---|---|
| 90% | 主目標への直接貢献 | 機能実装、バグ修正、精度向上 |
| 10% | 間接的な改善 | パフォーマンス、インフラ、ツーリング |
FastRenderのAGENTS.mdでは、「何がカウントされるか」 を明確に定義しています。
✅ カウントされる変更
- 新機能: アルゴリズム/機能の実装(回帰テスト付き)
- バグ修正: 動作の修正(回帰テスト付き)
- 安定性: クラッシュ/パニックの排除(回帰テスト付き)
- 終了性: タイムアウト/無限ループの排除(回帰テスト付き)
- 適合性: 意味のあるテストカバレッジの追加
- UX改善: ユーザーに見える品質向上
❌ カウントされない変更(ドリフト防止)
- ツール/インフラのみ: すぐに成果に繋がらないツール改善
- パフォーマンスのみ: タイムアウト修正や精度向上に繋がらない最適化
- ドキュメントのみ: 修正をブロックする混乱を解消しない文書化
「ハーネスを改善」しているだけで実際の動作を変えていないなら、止まって、不足している動作を実装せよ
知見2: 並列AIエージェントのためのワークストリーム分離
複数のAIエージェントを並列で動かす場合、作業領域の明確な分離が必須です。
プロジェクト
├── ワークストリームA(担当: Agent 1)
│ ├── 担当範囲: ○○
│ └── 担当外: ××
├── ワークストリームB(担当: Agent 2)
│ ├── 担当範囲: △△
│ └── 担当外: □□
└── ワークストリームC(担当: Agent 3)
├── 担当範囲: ◇◇
└── 担当外: ☆☆
分離の原則:
- 所有権の明確化: 各ワークストリームが「所有」するファイル/モジュールを定義
- 依存関係の最小化: ワークストリーム間の依存を減らす
- インターフェースの安定化: 境界部分のAPIを安定させる
FastRenderでは9つのワークストリームに分離し、並列開発を可能にしました。
知見3: 絶対に守るべきルール(Non-negotiables)
AIは指示されなければルールを破ります。破ってはいけないルールを明文化することが重要です。
FastRenderのAGENTS.mdから実例を引用します。
## Non-negotiables(破ってはいけないルール)
1. **特定ケース向けハックの禁止**
- ホスト名/セレクタによる特殊処理を書かない
- 1つのサイトのためのマジックナンバーを使わない
2. **仕様違反の「互換性ショートカット」禁止**
- ページが依存する仕様の動作を実装する
- 不完全はOK、間違いはNG
3. **パイプライン順序の遵守**
- parse → style → box tree → layout → paint を守る
- レイアウト後のピクセル調整は禁止
4. **本番コードでのpanic禁止**
- エラーはクリーンに返す
- 作業は制限付きで実行する
5. **実行は必ず制限付き**
- JSエンジンは割り込み/タイムアウトをサポート必須
- 無制限のホスト割り当てを避ける
Non-negotiablesを明文化しないと、AIは「動けばいい」という判断でハックを入れます。
知見4: データ駆動開発メソッド
AIコーディングでも計測可能性が重要です。AIは「良くなった気がする」という曖昧なフィードバックでは学習できません。
FastRenderのAGENTS.mdでは、以下の5ステップをデータ駆動メソッドとして定義しています。
1. Inject — 計測ポイントを埋め込む(ログ、メトリクス、トレース)
2. Trace — データフローを追跡(入力から出力までの変換過程)
3. Collect — 継続的に証拠を収集(自動化されたパイプライン)
4. Understand — パターンと異常を理解(統計、可視化、比較)
5. Systematize — 調査を再現可能にする(スクリプト化、ドキュメント化)
なぜこれが重要か:
| 問題 | データ駆動なし | データ駆動あり |
|---|---|---|
| バグの原因特定 | 「どこかがおかしい」 | 「この関数の出力が期待値と3%異なる」 |
| 改善の検証 | 「良くなった気がする」 | 「処理時間が147ms→98msに短縮」 |
| 回帰の検出 | 「なぜか動かなくなった」 | 「コミットXでテストYが失敗開始」 |
進捗の可視化例(JSON形式):
{
"task_id": "TASK-001",
"status": "completed",
"metrics": {
"duration_ms": 147.05,
"stages": {
"parse": 72.03,
"process": 35.01,
"output": 40.01
}
},
"quality": {
"test_pass_rate": 0.98,
"error_count": 2,
"coverage_delta": "+2.3%"
}
}
応用: AIにタスクを依頼する際は、「完了したら metrics を出力して」と指示しましょう。数値化された結果があれば、次の改善につなげられます。
知見5: 優先度の明確化(トリアージ)
AIに作業を依頼する際、優先度を明示しないと重要でないタスクに時間を使います。
FastRenderのtriage.mdでは、以下の優先度体系を定義しています。
| 優先度 | カテゴリ | 説明 | 対応時間 |
|---|---|---|---|
| P0 | クラッシュ/データ損失 | システムが使用不能 | 即座に |
| P1 | 機能停止 | 主要機能が動作しない | 24時間以内 |
| P2 | 機能不全 | 機能は動くが不完全 | 今スプリント |
| P3 | パフォーマンス | 遅いが動作する | 余裕があれば |
| P4 | 見た目の改善 | 動作に影響なし | バックログ |
| P5 | 新機能 | 未実装の機能 | 計画時に検討 |
なぜ優先度が重要か:
AIは与えられたタスクを「完璧に」やろうとします。優先度がないと:
❌ 悪い例:
「ログイン機能を改善して」
→ AIがUI、パフォーマンス、セキュリティ、ドキュメントを全部やろうとする
→ 本当に必要だった「パスワードリセットが動かない」が後回しに
✅ 良い例:
「P1: パスワードリセットが500エラーになる問題を修正して」
→ 明確な優先度とゴールがあるため、集中して対応
タスク依頼テンプレート:
## タスク依頼
**優先度**: P2
**ゴール**: ○○ができるようになる
**完了条件**:
- [ ] テストが通る
- [ ] ドキュメントが更新されている
**制約**: P0-P1の問題が存在する場合はそちらを優先
**スコープ外**: パフォーマンス最適化、UIの見た目改善
優先度を指定しないと、AIは「面白そうな」タスクから手を付ける傾向があります。P0-P1を最初に解決させましょう。
知見6: 試行錯誤から学んだパターン
FastRenderの開発ログを分析すると、AIが繰り返し失敗するパターンと成功するパターンが明確に見えてきます。
❌ AIが失敗しやすいアプローチ
| パターン | 問題 | 具体例 |
|---|---|---|
| 設定だけでの解決 | システムの制限は設定では解決できない | 「タイムアウトを増やせば直る」→ 根本原因が残る |
| 後処理での修正 | 根本原因を直さず症状を隠す | レイアウト後にピクセルを微調整 → 他の場所で破綻 |
| 複雑な条件分岐 | シンプルな判定の方が信頼性が高い | 10個のif文 → 組み合わせ爆発でバグ増加 |
| 検証なしのバッチ修正 | 問題が蓄積する | 「10ファイル一括修正」→ どれが壊したか不明 |
| 特定サイト向けハック | 他のサイトで破綻する |
if (host == "example.com") → 保守不能に |
✅ AIが成功しやすいアプローチ
| パターン | 理由 | 具体例 |
|---|---|---|
| 処理パイプラインの遵守 | 各段階の責務が明確 | parse→style→layout→paintの順序を守る |
| 早期のデータ準備 | 後から直すより最初に正しくする | 入力検証を最初に行う |
| 再帰的な検出 | 構造を走査して対象を見つける | DOMツリーを再帰的に処理 |
| 一つずつ修正・即検証 | 各変更の影響が明確 | 1つ修正→テスト→次へ |
| 各段階でのログ出力 | 変換過程を追跡可能 | 入力/中間/出力をすべて記録 |
失敗から学ぶ具体例:
// ❌ 失敗パターン: 後処理で調整
fn layout_element(elem: &Element) -> Box {
let mut result = calculate_layout(elem);
// 「なぜか1pxずれる」を後から修正
result.x += 1; // マジックナンバー!
result
}
// ✅ 成功パターン: 根本原因を修正
fn layout_element(elem: &Element) -> Box {
// ボーダー幅の計算を正しく行う
let border = elem.computed_border_width(); // 原因を特定して修正
calculate_layout_with_border(elem, border)
}
知見7: リソース制限の厳格な適用
AIエージェントは暴走する可能性があります。FastRenderのAGENTS.mdは明確に警告しています。
基本ルール: すべてが誤動作する可能性を想定せよ
任意のコードパスが病的になりうる:無限ループ、指数爆発、メモリ爆発、デッドロック、ライブロック、シグナルを無視するハング。
これは妄想ではない — 運用上の現実だ。
実際のコマンド例:
# ✅ 正しい: timeout -k でSIGKILLフォールバックを設定
# (誤動作するコードはSIGTERMを無視できるため -k が必須)
timeout -k 10 600 bash scripts/cargo_agent.sh build --release
timeout -k 10 600 bash scripts/cargo_agent.sh test --quiet --lib
# ❌ 誤り: SIGKILLフォールバックなし
timeout 600 command # プロセスがSIGTERMを永遠に無視可能
# ❌ 致命的: ホストを破壊する
cargo test # 100+バイナリをコンパイル
cargo build --all-targets # RAMを使い果たす
cargo check --all-features # マシンを使用不能に
タイムアウトガイドライン:
| 用途 | 推奨値 |
|---|---|
| 通常のビルド/テスト |
timeout -k 10 600(10分) |
| 単一の集中テスト |
timeout -k 10 120(2分) |
| 完全なハーネス実行 |
timeout -k 10 900(15分) |
設定すべき制限:
| 制限 | 方法 | 理由 |
|---|---|---|
| 時間制限 | timeout -k |
無限ループ防止 |
| メモリ制限 |
prlimit / cgroups |
メモリ爆発防止 |
| ディスク制限 | 定期的なcargo clean
|
target/の肥大化防止 |
| スコープ制限 |
-p <crate> / --lib
|
影響範囲の限定 |
FastRenderでは「スコープなしのcargo testを実行すると、100以上のバイナリをLTOでコンパイルし、数百の並列rustc/moldプロセスを生成し、全RAMを使い果たし、マシンを使用不能にする」と明記されています。例外はありません。
知見8: テストアーキテクチャの原則
AIが生成するテストは適切な場所に配置する必要があります。
src/
├── module_a.rs # 実装
│ └── mod tests {} # ← ユニットテストはここ(内部をテスト)
└── module_b.rs
tests/
└── integration.rs # ← 統合テストはここ(公開APIをテスト)
ルール:
- ユニットテスト →
src/内に配置(内部実装をテスト) - 統合テスト →
tests/に配置(公開APIをテスト) - 内部モジュールを
tests/からimportしている → 設計ミス
回帰テストの哲学(AGENTS.mdより):
まず回帰テストを追加し、その後で修正を実装する
修正が実際に動作を変えたことを証明せよ。
失敗するテストなしに「修正された」は単なる希望だ。
この順序が重要な理由:
- 修正前に失敗を確認 → 問題の再現性を保証
- 修正後に成功を確認 → 修正の効果を証明
- 将来の回帰を防止 → 同じバグを二度と作らない
# ✅ 正しい順序
1. 失敗するテストを書く(バグを再現)
2. テストが失敗することを確認
3. 修正を実装
4. テストが成功することを確認
# ❌ 間違った順序
1. 修正を実装(「これで直るはず」)
2. テストを書く(常に成功するテストになりがち)
知見9: 証拠の要件
「見た目が良くなった」「動くようになった」は証拠ではありません。AIは自信を持って間違ったことを言うことがあります。
証拠のレベル:
| 推奨度 | 証拠の種類 | 説明 |
|---|---|---|
| 🥇 最良 | 自動テスト + 回帰テスト | 機械的に検証可能、将来の回帰も防止 |
| 🥈 許容 | ベースラインとの差分比較 | 画像diff、数値比較、ログ比較 |
| 🥉 最低限 | 手動確認の記録 | スクリーンショット、動画、チェックリスト |
| ❌ 不可 | AIの「改善しました」という報告 | 検証不能、信頼できない |
具体例:
# ❌ 証拠なし
AI: 「パフォーマンスを改善しました」
人間: 「本当に?どれくらい?」
AI: 「良くなったはずです」
# ✅ 証拠あり
AI: 「パフォーマンスを改善しました」
- Before: 処理時間 234ms(tests/perf_baseline.json)
- After: 処理時間 156ms(33%改善)
- 回帰テスト: 全件パス
- ベンチマーク結果: benches/output/latest.json
FastRenderでの実践例:
| 主張 | 必要な証拠 |
|---|---|
| 「バグを修正した」 | 失敗→成功に変わるテスト |
| 「レンダリングが改善した」 | 画像diff + リファレンスとの比較 |
| 「クラッシュを排除した」 | 以前クラッシュした入力でのテスト |
| 「仕様に準拠した」 | Web Platform Testsの結果 |
AIに「改善した」と言わせるだけでなく、検証可能な証拠を必ず要求しましょう。証拠がなければ、その変更は「希望」に過ぎません。
知見10: 開発ループの徹底
AIコーディングで最も危険なのは、複数の変更をまとめて行うことです。
FastRenderの開発では、以下のシングルステップ開発ループを厳格に守っています。
┌─────────────────────────────────────────┐
│ 1. 実行する(現状を確認) │
│ ↓ │
│ 2. 期待結果と比較する │
│ ↓ │
│ 3. 差異を特定する(1つだけ) │
│ ↓ │
│ 4. デバッグ情報を追加する │
│ ↓ │
│ 5. 修正を実装する(1つだけ) │
│ ↓ │
│ 6. 即座に検証する ──→ 失敗なら3へ戻る │
│ ↓ 成功 │
│ 次の問題へ │
└─────────────────────────────────────────┘
なぜバッチ処理が危険か:
# ❌ バッチ処理(危険)
1. 問題A、B、Cを見つける
2. A、B、Cをまとめて修正
3. テスト実行 → 失敗
4. 「どの修正が問題?」→ 分からない
5. 全部元に戻して最初からやり直し
# ✅ シングルステップ(安全)
1. 問題Aを見つける
2. Aを修正
3. テスト実行 → 成功 ✓
4. 問題Bを見つける
5. Bを修正
6. テスト実行 → 失敗
7. Bの修正だけ見直す(原因が明確)
AIへの指示例:
## 作業ルール
1. 変更は1つずつ行うこと
2. 各変更後にテストを実行すること
3. 複数ファイルを同時に変更しないこと
4. 「まとめてやった方が効率的」は禁止
このループは各変更ごとに回します。「5つの修正をまとめて検証」すると、どの変更が問題を引き起こしたか分からなくなり、デバッグ時間が指数的に増加します。
AGENTS.mdの重要性
AIコーディングで最も重要なのは、プロジェクトルールを明文化したAGENTS.mdです。
FastRenderの実際のAGENTS.md構造を参考にした推奨テンプレート:
# AGENTS.md (プロジェクト名)
このファイルには、すべてのワークストリームで共有される**リポジトリ全体のルール**を記載します。
## ワークストリーム
1つのワークストリームを選び、その専用ドキュメントに従ってください。
すべてのワークストリームで**並列に**作業を進められます。
| ワークストリーム | 担当範囲 | 担当外 |
|-----------------|---------|--------|
| `feature_a` | ○○の実装 | △△、□□ |
| `feature_b` | △△の実装 | ○○、□□ |
| `infrastructure` | ビルド、CI/CD | 機能実装 |
## Non-negotiables(破ってはいけないルール)
- **特定ケースのハック禁止**: マジックナンバー、ホスト名チェックを書かない
- **仕様違反の禁止**: 「動くから」でショートカットしない
- **本番コードでのpanic禁止**: エラーはクリーンに返す
- **無制限実行の禁止**: 時間・メモリ・スコープを常に制限
## 哲学と文化
- **成果物にフォーカス**: ○○が最終目標。他はすべて手段
- **90/10ルール**: 90%を主目標、10%をインフラに
- **データ駆動**: 計測、追跡、収集、理解、体系化
- **優先順位**: クラッシュ → タイムアウト → 精度 → 性能 → 新機能
- **虚栄心の仕事禁止**: 測定可能な成果に繋がらない変更は却下
## カウントされる変更
- 新機能(回帰テスト付き)
- バグ修正(回帰テスト付き)
- 安定性改善(クラッシュ排除 + 回帰テスト)
- テストカバレッジ追加
## カウントされない変更
- ツール改善のみ(すぐ使わない場合)
- パフォーマンスのみ(タイムアウト修正でない場合)
- ドキュメントのみ(混乱を解消しない場合)
## リソース制限
- タイムアウト: `timeout -k 10 600`
- メモリ: OS制限必須(prlimit / cgroups)
- スコープ: 常に `-p <crate>` または `--lib` を指定
## テスト組織
- ユニットテスト: `src/` 内に配置
- 統合テスト: `tests/` は公開API専用
- 回帰テスト: 修正より先に追加
AGENTS.mdがないと、AIは毎回異なる判断をします。一貫性のあるコードベースを維持するには、ルールの明文化が不可欠です。
FastRenderのAGENTS.mdは約300行あり、ワークストリーム、Non-negotiables、哲学、リソース制限、テスト組織、回帰哲学など、AIエージェントが守るべきすべてのルールを網羅しています。
まとめ
FastRenderのソースコード分析から得られた汎用的なAIコーディングの知見:
| # | 知見 | 要点 |
|---|---|---|
| 1 | 成果物への明確なフォーカス | 90/10ルールで優先順位を明確化 |
| 2 | ワークストリーム分離 | 並列作業のための領域分け |
| 3 | Non-negotiables | 破ってはいけないルールの明文化 |
| 4 | データ駆動開発 | 計測可能性の確保 |
| 5 | 優先度の明確化 | P0-P5のトリアージ |
| 6 | パターンの学習 | 失敗/成功パターンの蓄積 |
| 7 | リソース制限 | 暴走防止の安全策 |
| 8 | テスト配置 | 適切な場所にテストを置く |
| 9 | 証拠の要件 | 検証可能な成果物 |
| 10 | 開発ループ | 一つずつ検証する |
AIコーディングは強力ですが、ガードレールなしでは暴走します。本記事の知見を活用して、AIの力を最大限に引き出してください。
参考リンク
本記事は、MUSUBIX CodeGraphツールを使用してコードベースを分析し、AIコーディングの特徴を調査した結果に基づいています。