前回、AIで生産性が上がるか、書く時間と保守の話を書きました。今回は、その続きとして、実際に AI とどう協業して開発しているか、現場の流れを書いてみます。
- レビューがボトルネックになる
- 「捨てて作り直す」という選択肢
- AI が「リファクタしましょう」と言わない問題
- テストコードのレビュー
- エッジケースでの振る舞いを定義する
- ハーネスでテストを書かせる
- AIでのテスト駆動の現実
- まとめ
レビューがボトルネックになる
AIの進化とハーネスを育てた結果、従来の2-3倍のアウトプットが出せている実感があります。ただ、これだけスピードが上がると、別の問題が顕在化します。レビューがボトルネックになる。
書く時間が短くなっても、レビューは変わらない(あるいは、AIコードを読むコストで増える)。結果、ボトルネックが「書く」から「レビュー」に移動する。経緯を知らないメンバーが大量のPRをレビューするのは、認知負荷が高い。
これは、システム工学で言う「律速段階の移動」と同じ構造かなと思います。一箇所を高速化すると、別のところがボトルネックになる。
なので、AI と協業する開発では、「書く」だけでなく、「レビューしやすくする」「捨てやすくする」「テストで担保する」といった、書いた後のことが、より重要になってきます。
「レビューしやすくする」工夫の一つが、PR の書き方です。何を、なぜ変えたか、どう動作確認したか── これが PR に整理されていると、レビュワーの認知負荷が下がる。経緯を知らないメンバーでも、判断しやすい。
自分は、PR テンプレートを用意して、「なぜ(Why)」「何を(What)」「動作確認」あたりを、必須の項目にしています。特に「なぜ」は、コードを見ても分からないので、大事。AI に PR を下書きさせるときも、テンプレートがあると、書く項目が安定します。
「捨てて作り直す」という選択肢
AI で書くコストが下がると、「捨てて作り直す」が、現実的な選択肢になります。
クラウドの世界で「Cattle not Pets」という言葉があります。サーバーを、一台一台大事に育てる「ペット」ではなく、いつでも入れ替え可能な「家畜」のように扱う、という考え方。コンテナや ECS、Kubernetes の発想です。
コードも、同じように考えられる部分があります。大事に手入れし続ける「ペット」のコードと、いつでも捨てて作り直せる「家畜」のコード。
実際に、大きなリプレイスをしたことがあります。汎用性を求めすぎた設計、何年も直せないバグ、実装者がもういない、その場しのぎの対応、過度な DRY、巨大なコントローラ、意図が不明なカラム── こういう負債が溜まったコードベースを、作り直しました。
その時の流れは、こんな感じでした。まず、既存の RSpec を充実させて、挙動を固定する。次に、DB 設計を見直す(データの構造から見直せる、貴重な機会)。そして、再設計する。フロントは Nuxt.js で分離しました。
これは、AI がまだいない時代に、手動でやりました。でも、今振り返ると、AI と協業するなら、もっとスムーズにできる部分がある。
実際、その後の機能追加は容易になり、不要な機能のオミット(削除)は、AI との協業でスムーズに進みました。挙動がテストで固定されているから、AI に「この機能を消して」と頼んでも、安心して任せられる。
読みにくいコードは、AI に修正させても、レビューが難しい。だから、捨てて作り直して、人間にも AI にも分かりやすくする価値がある。
AI が「リファクタしましょう」と言わない問題
ただ、ここに難しさがあります。AI は、自分から「リファクタしましょう」とは言わない。
頼んだことは、やってくれる。でも、「このコードベース、そろそろ作り直した方がいい」とは、自発的に言わない。目の前のタスクを、既存の構造の中で、こなそうとする。
これは、スタートアップの経営判断と、同じ構造かもしれません。目の前の機能を作り続けると、知らず知らず技術的負債が溜まる。「立ち止まって作り直す」という判断は、誰かが意図的にしないと、起きない。
シニアエンジニアでも、リファクタは不可欠です。むしろ、「ここはリファクタすべきだ」と気付くこと自体が、シニアの能力。コードを書いた瞬間に完璧、ということはない。書いて、動かして、「あ、ここはこう整理した方がいい」と気付く。その気付きの実装が、リファクタ。
これを、ハーネス(CLAUDE.md や skill)に書いておくこともできます。「リファクタの観点を持て」と。でも、限界があります。「いつ、何を、どこまでリファクタするか」は、文脈とドメインを踏まえた、人間の判断だから。気付きは、人間の役割です。
テストコードのレビュー
AI と協業する開発で、レビューで特に大事なのは、テストコードだと思います。
実装コードは、テストが通っていれば「動く」と分かります。でも、テスト自体が正しいかは、別の問題。テストが間違っていれば、実装が間違っていても通る。AIが実装もテストも書く時代、テストの質が品質の核になっていると思います。
テストコードのレビューで見るのは:
- ケース網羅: 必要なケースがカバーされているか、抜けがないか
- 期待値が正しいか:
expectの値が、本当に正しいか。「動いた結果」を期待値にしていないか - 検証内容が適切か: 何を検証しているか、浅い検証になっていないか、副作用や状態変化を見ているか
AIが書いたテストには、典型的な問題があります。「動いた結果」を期待値にする(実装に合わせてテストを書く)、表面的な検証(expect(result).to be_truthy のような)── これらは、量だけ見ると「網羅率」が高いが、質が低い。
そして、これは AI に限った話ではなくて、人間も同じかなと思います。観点を持ってテストを書いているか── ジュニアも、勝手に振る舞いを決めがち、表面的な検証を書きがち。「テストの観点を持つ」のは、経験で磨かれる深層の能力で、ジュニア時代から鍛えていく必要がある気がします。
エッジケースでの振る舞いを定義する
特に大事だと思うのは、エッジケースでの振る舞いを定義することです。「エッジケースは滅多に起きないから、テストしない」は、危険。どう動くかが分からないのが、最大のリスクな気がします。
エッジケースで、どう振る舞うかは、サービスレベルやビジネス判断によります。例外で止めて確認した方がいい場合(データの整合性が最重要)、ユーザーにはエラーを見せずに、内部で調査可能にする場合(ユーザー体験優先)、データを補正して通す場合(処理継続が重要)── どれを選ぶかは、ケースバイケース。でも、「こういう状況では、こう動く」を、明示的に決めて、テストで担保することが大事だと思います。
「どう動くか分からない」状態が、一番怖い。障害時の対応ができない、影響範囲が予測できない、ユーザーへの説明ができない。逆に、「こういう状況では、こう動く」と決まっていれば、想定の範囲内で対応できる。
これも、AI に限らず、人間も同じ。観点を持って、エッジケースの振る舞いを定義しているか。これは、ジュニア時代から磨く、深層の能力です。
ハーネスでテストを書かせる
テストのカバレッジも、品質の観点の一つです。人間も、カバレッジを見るべきだと思いますが、毎回 PR で、カバレッジレポートを開いて、漏れを探すのは、しんどい。忙しいと、つい飛ばす。
ここは、ハーネスに書くことで楽になります。「テストも書く」とハーネスに書いておけば、AI はカバレッジを意識したテストを書いてくれる。事後に「漏れ」を指摘するのではなく、最初から漏れにくいテストが書かれる。
カバレッジには、line カバレッジ(どの行が実行されたか)と branch カバレッジ(どの分岐が実行されたか)があります。branch カバレッジの方が、品質の指標として強い。条件分岐が網羅される。「branch カバレッジを意識」とハーネスに書くと、テストの質が上がる気がします。
ただし、カバレッジが高い = 良いテスト ではない。カバレッジ100%でも、検証が浅ければ意味がない。カバレッジは目安で、中身は別に見る必要があります。
もう一つ、AI でテストを書く量が増えると、テストの実行が遅くなる問題も出てきます。テストケース数が増える速度が上がるので、let_it_be でテストデータを使い回したり、it をまとめたりして高速化するのが、これまで以上に大事になります。
テストが充実していると、バージョンアップ作業(Ruby、Rails、ライブラリ)でも安心感が増す。「動くだろう」ではなく、「期待通り動くことを確認できる」。これも、AI時代に効いてきます。
AIでのテスト駆動の現実
「テスト駆動開発(TDD)」── テストを先に書いて、それから実装する、というアプローチ。教科書的には推奨されていますが、AIと協業する時代に、これがそのまま使えるか、考えてみました。
正直、AI にテストを完全に先に書かせる(純粋な TDD)のは、向かない気がします。AIは「現在の挙動」を「正」と仮定しがち。「実装を書け」「テストを書け」と言われたら書けますが、「仕様を考えながら、テストで明確にしていく」というプロセスが苦手。結果、実装に合わせたテストになりがち。
そもそも、人間でも完全なテスト駆動は難しい。教科書通りに「テスト先、実装後」を貫くのは、現場では難しい。実装の見通しがないと、適切なテストも書けないことが多い。
自分は、こんな流れで進めています。最初の正常系を一つ、実装で書く(設計の輪郭を作る)。それから、そのテストを書く。以降の正常系・異常系は、テスト駆動(テスト先、実装後)。最初の一つは実装で書いて輪郭を作り、そこからテスト駆動のリズムに入る、というハイブリッドな流れです。
ただ、これは自分の頭の中の進め方で、AIに対しては、別です。AIに「テスト先、実装後」の順番を守らせても、テストの期待値を正しく認識できないことが多い。「期待値、これで合ってる?」を毎回確認するのは、人間の認知負荷が大きい。
現実的には、AIには効率的に生成させて、生成が終わってから、またはレビューでまとめて確認する方が、効果的かもしれません。順番にこだわるより、まとめて評価する方が、人間の認知負荷も少なく、AIの効率も活きる。TDD の本質的な価値(仕様の明確化、振る舞いの定義、品質の担保)は、事後のレビューでまとめて実現する── これが、AI時代の現実的な流れな気がします。
ただし、「まとめて評価」がすべての場面で良いわけではないです。実際、自分もフェーズで使い分けています。
- テスト作成: まとめて評価が良い。「このケースの期待値はこれ?」と都度聞かれると困る。全体を見て判断したい。
- 指摘を続けて完成に持っていく: 修正コードを見て指摘する、都度のやり取りが効く。ペアプロ感がある。
「タスクの性質で生産性が変わる」というのは、戦略の選択でも同じかもしれません。どちらが正解、ではなく、場面で使い分ける。これも、「自分の文脈で判断する」の実践な気がします。
まとめ
AI と協業する開発の流れを、現場の手触りで書いてみました。
- スピードが上がると、レビューがボトルネックになる
- AI で書くコストが下がると、「捨てて作り直す」が現実的な選択肢に(Cattle not Pets)
- でも、AI は自分から「リファクタしましょう」とは言わない。気付きは人間
- レビューで特に大事なのは、テストコード(ケース網羅、期待、検証)
- エッジケースの振る舞いを定義する(どう動くか分からないのが最大のリスク)
- これは AI に限らず、人間も同じ。観点を持ってテストを書くのは、深層の能力
- カバレッジは、ハーネスで書かせる(branch カバレッジ、実行速度にも注意)
- 純粋な TDD は向かない。自分はハイブリッド、AIへはまとめて評価
- まとめて評価か、都度のやり取りか、場面で使い分ける
書くだけでなく、レビューしやすく、捨てやすく、テストで担保する。これも結局、いつものエンジニアリングの延長な気がしています。
