1/30に開催された関ジャバ 2025 1月度にて、AIエージェントのLTをした…のですが端末映らなかったりといろいろ不完全燃焼だったのでこちらに放流します。
AIエージェントについて
なんか2025になるかならないかくらいから、一気に爆発した感はあります。やはりきっかけは、OSSでつかえるClineでしょうか。
これまでもReplit AgentやらAiderやらv0やら、自律型のコーディングエージェントは存在していたのですが、VS Codeという開発者ツールに統合され、かつOSSで(API利用料金さえ払えば)誰でも使えるというところで、爆発した感はあります。
また、すでに Roo Code、Cool Cline、Bao Clineなど様々な派生が出てきているのも面白いポイントだと思いますね。
もちろんOSSだけではなく、プロプライエタリなものも出ています。AIエディターとして有名なCursorにもComposerという機能でAIエージェント機能が追加されていっています。
今回私が使ったのはWindsurfというAI IDEです。
もともとGithub Copiot風なコード補完VSCode拡張を作っていたCodeiumという会社から出てきたIDEで、世界的にはClineよりも先に注目を浴びていた、なんて話もありますね(というよりこのPostでWindsurfを知った)
AI時代のコーディングスタイル?
割とAI初期から、「これからはテストコードが重要になる」的な(特にQA界隈の方々から)言説があったのかなと思います。
実際それって正しそうに聞こえていて、AIエージェントが頼れる「正解」を定義し、自律的に試行錯誤してもらうには、テストコードが重要になってくる感じはします。(そもそも人間が安心できる準拠ポイントを持ちたいというエゴな気もしますが、それって多分これまでプログラマーに対して外側の人々が思ってきたことなのかも?とふと思いました)
そしてAIエージェントへの入力をテストコードとするのであれば、それって一種のTDDじゃね?と思ったのがきっかけで、超古典的なTDDの型であるBowlingGameKataをAIといっしょにやってみようと思ったのでした。
実はTDDってしっくりこないんです!!(AIエージェントとやると)
(このネタもう誰がわかるんでしょうね)
まぁそれはともかく、実際にAIエージェントとやってみた様子なんですが…
よし、テストケース書いたからTDD初めてというといきなりコードを変え始めちゃうし、
本来ならもっとシンプルな実装でいける「20回すべて一本倒す」の初期実装で、Scoreを都度計算するのではなく、配列に格納してscoreメソッドの中で足し算するように変更しちゃうし
と、 必要最低限の実装 をテンポよく実装していく、なんてことはできません。
いや、もちろんシステムプロンプト的な根底ルールを与える機能もあるので、そこで強めにInstructionすれば可能ではあるのでしょうが、そのように能力を縛る意味があるのかは割と疑問ではあります。
BowlingGameKataのような粒度のTDDは人間が認知して解きやすい大きさに最適化されているように見えます。現状のAIエージェントの速度では現実の人間のスピードにこの粒度感だと追いついてこず、はっきり言えば自分で書いたほうが早くなってしまいます。
ではどうすべきか?
TDDの粒度感を大きくする、となるとそれはBDDであったりATDDであったり例示による仕様(Specification by Example)であったり、まぁそういったものです。外部仕様的なものを、実際の振る舞いや動作例を下に受け入れテストとして定義し、それをもとにプロダクトコードを書いていく、ってやつですね。
では、ボウリングゲームの振る舞いを書いてみましょう1。
# language: ja
機能: ボウリングゲームのスコア計算
シナリオ概要: 1フレームのスコアを計算できる
前提: 新しいゲームを開始する
もし <投球>回、<倒したピンの数>本倒す
ならば スコアは<スコア>になる
例:
| 投球 | 倒したピンの数 | スコア |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| 2 | 1,2 | 3 |
シナリオ概要: すべてガターだった場合のスコアを計算できる
前提: 新しいゲームを開始する
もし 20回、0本倒す
ならば スコアは0になる
かつ ゲームは終了している
シナリオ概要: パーフェクトゲームのスコアを計算できる
前提: 新しいゲームを開始する
もし 12回、10本倒す
ならば スコアは300になる
かつ ゲームは終了している
シナリオ概要: ストライクの場合、次の2投分のスコアが加算される
前提: 新しいゲームを開始する
もし 1回、10本倒す
もし 1回、3本倒す
もし 1回、4本倒す
もし 17回、0本倒す
ならば スコアは24になる
シナリオ概要: スペアの場合、次の1投分のスコアが加算される
前提: 新しいゲームを開始する
もし 1回、5本倒す
もし 1回、5本倒す
もし 1回、3本倒す
もし 17回、0本倒す
ならば スコアは16になる
シナリオ概要: 最終フレームでストライクの場合、2回追加で投げられる
前提: 新しいゲームを開始する
もし 18回、0本倒す
もし 1回、10本倒す
もし 1回、10本倒す
もし 1回、10本倒す
ならば スコアは30になる
かつ ゲームは終了している
シナリオ概要: 最終フレームでスペアの場合、1回追加で投げられる
前提: 新しいゲームを開始する
もし 18回、0本倒す
もし 1回、5本倒す
もし 1回、5本倒す
もし 1回、10本倒す
ならば スコアは20になる
かつ ゲームは終了している
シナリオ: 現実的なスコアのゲームの計算
前提: 新しいゲームを開始する
もし 1回、1本倒す
もし 1回、4本倒す
もし 1回、4本倒す
もし 1回、5本倒す
もし 1回、6本倒す
もし 1回、4本倒す
もし 1回、5本倒す
もし 1回、5本倒す
もし 1回、10本倒す
もし 1回、0本倒す
もし 1回、1本倒す
もし 1回、7本倒す
もし 1回、3本倒す
もし 1回、6本倒す
もし 1回、4本倒す
もし 1回、10本倒す
もし 1回、2本倒す
もし 1回、8本倒す
もし 1回、6本倒す
ならば スコアは133になる
かつ ゲームは終了している
ではこいつを入力に、プロダクトコードを書かせてみましょう。
そうか、、、さきにもうプロダクトコード書いちゃうのね。
その後にステップ実装を作成して、実行可能にしてくれる感じか。
というわけで、割と最初に苦痛になるStep実装を見事に作成してくれています。
おお、割とこれはイケるのでは?
package com.dairappa.bowling.steps;
import com.dairappa.bowling.BowlingGame;
import io.cucumber.java.ja.かつ;
import io.cucumber.java.ja.ならば;
import io.cucumber.java.ja.もし;
import io.cucumber.java.ja.前提;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BowlingGameSteps {
private BowlingGame game;
@前提(": 新しいゲームを開始する")
public void startNewGame() {
game = new BowlingGame();
}
@もし("{int}回、{int}本倒す")
public void rollMany(int times, int pins) {
for (int i = 0; i < times; i++) {
game.roll(pins);
}
}
@もし("{int}回、{string}本倒す")
public void rollSequence(int times, String pinsSequence) {
String[] pins = pinsSequence.split(",");
for (int i = 0; i < times && i < pins.length; i++) {
game.roll(Integer.parseInt(pins[i].trim()));
}
}
@ならば("スコアは{int}になる")
public void verifyScore(int expectedScore) {
assertEquals(expectedScore, game.score());
}
@かつ("ゲームは終了している")
public void verifyGameIsOver() {
assertTrue(game.isGameOver());
}
}
とはいえこれも実装が先行してしまっているので、そもそもAIの能力に制限かけている気がしないでもないですが、結局何らかの形で人間が検証できるデバッグポイントを設けないといけなくて、それとAIに与える自由度のバランスというところなんだと思います。
完全にフリーだとやっぱり業務では使いづらくて、細かく規定しすぎるとAIの手足を縛り過ぎで一体何をしたいのかわからなくて、という感じになるのかなと。
まぁそんな感じで、とりあえず面白いのでしばらくは色々やり取りしながらものを作っていこうかなと思います。
おまけ -それはともかくAI Agentはかなり優秀
という議論はともかくとして、個人的に一番感動したのは久々にJavaプロジェクト作るかー、と思って環境整え始めたときでした。
そして、インストールしようとすると、gradleがないことに気づきます。すかさずSDKManを使って環境を整えることを考えて
SDKManのインストールスクリプトを叩きますが、これも失敗します(なんとzipコマンドも入っていない)
どうするのかなと思ったら、こっちにインストールを依頼するモードに。
こちらで構築完了したら、すぐさま次のステップを踏んでくれてます。build.gradleを適切に編集してくれましたね。
ググっている時間もうプロジェクトの開始ができている、くらいのスピード感に感じました。
しかもプロジェクト名からやることをうっすら推測して、TDDのサンプルファイルまでボウリング題材に作ってくれました。そこまでいらんのやけど
この辺の柔軟性を、わざわざ消しちゃう必要はないんかなってのが個人的な意見なので、本文に戻る、って感じですね。
-
実際にはGeminiに書いてもらってます。一般世界のルールはGeminiのほうが賢いです。 ↩