AIエージェントとの付き合い方をTDD/BDDから考える

1/30に開催された[関ジャバ 2025 1月度](https://kanjava.connpass.com/event/342465/)にて、AIエージェントのLTをした…のですが端末映らなかったりといろいろ不完全燃焼だったのでこちらに放流します。

https://x.com/dairappa/status/1884934111049163157

## AIエージェントについて
なんか2025になるかならないかくらいから、一気に爆発した感はあります。やはりきっかけは、OSSでつかえるClineでしょうか。

https://github.com/cline/cline

これまでもReplit AgentやらAiderやらv0やら、自律型のコーディングエージェントは存在していたのですが、VS Codeという開発者ツールに統合され、かつOSSで（API利用料金さえ払えば）誰でも使えるというところで、爆発した感はあります。

また、すでに [Roo Code](https://github.com/RooVetGit/Roo-Code)、[Cool Cline](https://github.com/coolcline/coolcline)、[Bao Cline](https://github.com/jnorthrup/Bao-Cline)など様々な派生が出てきているのも面白いポイントだと思いますね。

もちろんOSSだけではなく、プロプライエタリなものも出ています。AIエディターとして有名なCursorにも[Composer]()という機能でAIエージェント機能が追加されていっています。

今回私が使ったのはWindsurfというAI IDEです。

https://codeium.com/windsurf

もともとGithub Copiot風なコード補完VSCode拡張を作っていたCodeiumという会社から出てきたIDEで、世界的にはClineよりも先に注目を浴びていた、なんて話もありますね（というよりこのPostでWindsurfを知った）

https://x.com/kinopee_ai/status/1880284256544702837

## AI時代のコーディングスタイル？
割とAI初期から、「これからは**テストコード**が重要になる」的な（特にQA界隈の方々から）言説があったのかなと思います。
実際それって正しそうに聞こえていて、AIエージェントが頼れる「正解」を定義し、自律的に試行錯誤してもらうには、テストコードが重要になってくる感じはします。(そもそも人間が安心できる準拠ポイントを持ちたいというエゴな気もしますが、それって多分これまでプログラマーに対して外側の人々が思ってきたことなのかも？とふと思いました）

そしてAIエージェントへの入力をテストコードとするのであれば、それって一種のTDDじゃね？と思ったのがきっかけで、超古典的なTDDの型であるBowlingGameKataをAIといっしょにやってみようと思ったのでした。
    
## 実はTDDってしっくりこないんです！！（AIエージェントとやると）
（このネタもう誰がわかるんでしょうね）


まぁそれはともかく、実際にAIエージェントとやってみた様子なんですが…

よし、テストケース書いたからTDD初めてというといきなりコードを変え始めちゃうし、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/4cac7ca7-5a7b-20ea-e032-db04bc43b744.png)

本来ならもっとシンプルな実装でいける「20回すべて一本倒す」の初期実装で、Scoreを都度計算するのではなく、配列に格納してscoreメソッドの中で足し算するように変更しちゃうし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/6ec3af61-9236-cb67-ade6-b9cc450f26aa.png)

と、 **必要最低限の実装** をテンポよく実装していく、なんてことはできません。

いや、もちろん[システムプロンプト的な根底ルールを与える機能](https://github.com/kinopeee/windsurfrules)もあるので、そこで強めにInstructionすれば可能ではあるのでしょうが、**そのように能力を縛る意味があるのか**は割と疑問ではあります。

BowlingGameKataのような粒度のTDDは人間が認知して解きやすい大きさに最適化されているように見えます。現状のAIエージェントの速度では現実の人間のスピードにこの粒度感だと追いついてこず、はっきり言えば自分で書いたほうが早くなってしまいます。

## ではどうすべきか？
TDDの粒度感を大きくする、となるとそれはBDDであったりATDDであったり例示による仕様（Specification by Example）であったり、まぁそういったものです。外部仕様的なものを、実際の振る舞いや動作例を下に受け入れテストとして定義し、それをもとにプロダクトコードを書いていく、ってやつですね。

では、ボウリングゲームの振る舞いを書いてみましょう[^1]。


```gherkin
# 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になる
    かつ ゲームは終了している
```
ではこいつを入力に、プロダクトコードを書かせてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/fe8c2414-4bc8-0aa6-0e1c-5acd091dcc02.png)

そうか、、、さきにもうプロダクトコード書いちゃうのね。
その後にステップ実装を作成して、実行可能にしてくれる感じか。

というわけで、割と最初に苦痛になるStep実装を見事に作成してくれています。
おお、割とこれはイケるのでは？

```java
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プロジェクト作るかー、と思って環境整え始めたときでした。

まず状況を分析して、、、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/c918022b-e3ea-c7d0-6432-3612290b3058.png)

そして、インストールしようとすると、gradleがないことに気づきます。すかさずSDKManを使って環境を整えることを考えて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/f1234b38-7f51-8e5a-4796-1287258b580e.png)

SDKManのインストールスクリプトを叩きますが、これも失敗します（なんとzipコマンドも入っていない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/997c0b23-011d-0ed9-0e82-0347bd3924cb.png)

そしてインストールを試みますがsu権限がなくて失敗。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/d4459376-641e-6568-6b01-80ab19bd9635.png)

どうするのかなと思ったら、こっちにインストールを依頼するモードに。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/05b79ba1-6b36-b695-6bb6-863291a4d1cc.png)

こちらで構築完了したら、すぐさま次のステップを踏んでくれてます。build.gradleを適切に編集してくれましたね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/4d72f6be-c57a-47b0-7046-86f46ed1f1a3.png)


ググっている時間もうプロジェクトの開始ができている、くらいのスピード感に感じました。

しかもプロジェクト名からやることをうっすら推測して、TDDのサンプルファイルまでボウリング題材に作ってくれました。~~そこまでいらんのやけど~~

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/42b84c73-388d-b10e-1b4c-b17986414b3a.png)

この辺の柔軟性を、わざわざ消しちゃう必要はないんかなってのが個人的な意見なので、本文に戻る、って感じですね。



[^1]: 実際にはGeminiに書いてもらってます。一般世界のルールはGeminiのほうが賢いです。