AIコーディングエージェントをコントロールするために必要な3つのこと
AIコーディングエージェント[1](以下エージェント)の使い方がなんとなくわかってきたので, 自分用のメモとしてどのようにアプローチしていけばよいかをまとめる. あくまで自分用のメモなので, これをベストプラクティスとして他人に押し付けるつもりはない(ので文体も自分に向ける). エージェントを使ったプログラミングはあくまでそういうスタイルのプログラミングなので, 通常のプログラミングと同様にある程度の身体性を伴っており万人が同じやり方で満足できるとは思わない. ただしアンチパターンはあるはずなので, それは先人の知恵を活用すべき.
私が思うエージェントを使ったプログラミングをするうえで重要な考え方となりそうな3点は以下の通り.
- 完了の定義
- HOWの学習
- 確率分布のコントロール
アンチパターンに言い換えると以下のようになる.
- ゴールを提示していないのでエージェントが止まらない. 例えば「リファクタリングして」という指示を出したら, エージェントが次から次へといろんなファイルをリファクタリングし続ける.
- やり方を提示していないのでエージェントが毎回失敗する. 例えばエージェントが場当たり的なデバッグをして延々とコードが直らない.
- 要点を提示しない・理解していない. そもそも自分で考えて作れないものはエージェントを使ってもコントロールできない.
つまり、ゴミを入れると、ゴミが出てきます。[2]
次にそれぞれのプラクティスについて具体的に掘り下げる.
完了の定義
エージェントにタスクを依頼するときには完了の定義を決めて, タスクを依頼すべき. チームで仕事をするときも同じだよね.
結論から言うと手法としてふるまい駆動開発(BDD)[3] がやりやすい. エージェントと対話してストーリーと Gherkin 記法にのっとったフィーチャーを作成する. あとはそのフィーチャーに従ったテストとプロダクトコードを書かせるだけ. フィーチャーに対して実装量が多くなりそうな場合は先にエージェントにどんなファイルやモジュール, クラスを作成するか確認する. タスクを単体テストの単位に落としてタスクを依頼する. フィーチャーがすでにあるので単体テストもだいたい書ける.
テスト駆動開発の Red, Green, Refactor だとステップが小さすぎてエージェントが無視しがちになる. またテスト駆動開発という用語自体が濫用されてしまったため, LLMそのものがテスト駆動開発の定義[4]を理解していない可能性が高い.
ピンポンペアリング[5] みたいにテストは自分で書いてプロダクトコードをエージェント書いてもらうのもうまくいく.
Java や C++ のように関数宣言と実装が分離できるなら関数宣言だけ自分で書いて, あとはエージェントに書かせるということもできる. 検証がいまいちでテストコードもエージェントに書かせると実装が通るようなテストを書いてしまう. 人間がすべきことは品質保証なので, コード構造のような保守性に関する品質だけを気にするのは片手落ちだ. AIは外部品質を保証してくれない.
ただしピンポンペアリングや関数宣言を自分で書く場合は自分がコードを書く速さがボトルネックになってしまう. なのでBDDが最もやりやすい(私が慣れているだけかもしれない). エージェントに対して自然言語を使って指示する必要があるため, 事前条件と事後条件を考えるうえで必要十分なフォーマットが Gherkin 記法だからである.
HOWの学習
すでに自分が確立している方法をエージェントきちんと教えてやる必要がある. エージェントは git を使って満足にコミットすることもできない. なぜなら git status
を使って確認するという方法を知らないからだ. そのレベルから教える必要がある. npm-srcipts や Makefile は積極的に定義したほうがよい. テストやコンパイルに延々と失敗しているのを眺めている暇があったら, デバッグのやり方を手取り足取り教えてやるべきだ[6].
具体例としてプロダクトコードやテストコードを提示してやってもよい. 同じようなパターンで書くことができるコードであればエージェントを使ってすぐに完成する. One-shot prompting や Few-shot prompting のエージェント版みたいなものかもしれない.
ちなみにWHATを伝える必要はない. 無駄にトークンを消費してしまうからだ. 必要な情報は必要なときにエージェントに取りにいかせればよい. WHATの代わりにWHEN(いつ必要になるか)とWHERE(どこにあるか)を教えてやればよい.
例えば以下のように指示する.
- コミットするときは gitmoji https://gitmoji.dev/ を使ってください.
- テストは Test Sizes の定義に従って分割します. テストに関して small, medium, large や size などの用語が出たら https://testing.googleblog.com/2010/12/test-sizes.html を確認してください.
確率分布のコントロール
LLMは入力された文章に対して可能性の高い文章を返しているに過ぎない. そのためあいまいな表現を使うと分散が大きくなる. 例えばリファクタリング, 統合テストのように広く使われていて, 人によって答えが変わるような用語を使ってしまうとエージェントがどんなことをするのか予測できなくなってしまう. 成果物に関しても同じだ. あいまいな要件でタスクを依頼するのではなく, きちんとプロダクトを理解し要件を掘り下げていかなければならない. Design Doc や Architecture Decision Record を書くのも良いだろう.
またすでに広く使われている用語を独自の定義で置き換えようとしても, その独自の定義は忘却してしまう. もし手法を定義したかったり, ドメイン駆動開発のようにユビキタス言語を定義したかったら, 既存の用語とは離れた造語を作ったほうが良い.
エージェントを使いながら壊れないプロダクトを作るには, 完了の定義をし, 境界を区切り変更する範囲を決めて, 決められた範囲の中で動くまで粛々と直していくだけだ. これまでのソフトウェアエンジニアリングと何ら変わらない. ソフトウェアエンジニアリングのやり方は『レガシーコードからの脱却』や『Googleのソフトウェアエンジニアリング』[7], 『継続的デリバリーのソフトウェア工学』[8]に書いてある.
-
コード補完だけではなく, 自律的に動作してコード生成やツールを使うタイプのツール. 例えば Cursor Composer や Cline, Devin など. 私は GitHub Copilot agents を主として試した. ↩︎
-
Running Lean 第3版 ―リーンキャンバスから始める継続的イノベーションフレームワーク, Ash Maurya, 角 征典, Eric Ries, 2023, オライリージャパン https://www.oreilly.co.jp/books/9784814400263/ ↩︎
-
Behaviour-Driven Development | Cucumber http://cucumber.io/docs/bdd/ ↩︎
-
【翻訳】テスト駆動開発の定義 - t-wadaのブログ https://t-wada.hatenablog.jp/entry/canon-tdd-by-kent-beck ↩︎
-
レガシーコードからの脱却 ―ソフトウェアの寿命を延ばし価値を高める9つのプラクティス, David Scott Bernstein, 吉羽 龍太郎, 永瀬 美穂, 原田 騎郎, 有野 雅士, 2019, オライリージャパン https://www.oreilly.co.jp//books/9784873118864/ ↩︎
-
デバッグ方法について詳細に言語化されている大変貴重な記事: 先輩社員がどうやって不具合を解決しているのか #Java - Qiita https://qiita.com/opengl-8080/items/56d31fe80b45773dc808 ↩︎
-
Googleのソフトウェアエンジニアリング ―持続可能なプログラミングを支える技術、文化、プロセス, Titus Winters, Tom Manshreck, Hyrum Wright, 竹辺 靖昭, 久富木 隆一, 2021, オライリージャパン https://www.oreilly.co.jp/books/9784873119656/ ↩︎
-
継続的デリバリーのソフトウェア工学 もっと早く、もっと良いソフトウェアを作るための秘訣, David Farley, 長尾 高弘, 榊原 彰, 2022, 日経BP https://bookplus.nikkei.com/atcl/catalog/22/12/01/00531/ ↩︎
Discussion