70
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【86万行・29,000コミット】AIが「本気で」コードを書いたらこうなった —— Cursorの大規模実験から学ぶ10の知見

Posted at

はじめに

本記事は、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)
    ├── 担当範囲: ◇◇
    └── 担当外: ☆☆

分離の原則

  1. 所有権の明確化: 各ワークストリームが「所有」するファイル/モジュールを定義
  2. 依存関係の最小化: ワークストリーム間の依存を減らす
  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. 将来の回帰を防止 → 同じバグを二度と作らない
# ✅ 正しい順序
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コーディングの特徴を調査した結果に基づいています。

  1. Scaling Agents - Cursor Blog

70
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
70
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?