はじめに
Bobを試す機会をいただきました。
AI駆動開発パートナーとして、普通のことを普通にこなしてくれる、そのつもりでした。
試してみると、それとは少し違う活用のコツみたいなものを知る機会となりました。
この記事はその旅路の記録です。Bobの失敗の話ではありません。
この記事は、IBM Community に投稿した記事の詳細版です。
1. 最初の依頼 — Bobに有名Javaサンプルアプリのリファクタリングの相談をしたら、なんと15項目も提案された!
対象は Spring PetClinic(src/main 1,608行 / src/test 1,895行)。Spring公式のリファレンス実装です。
Claudeには「お手本として整えられているから、直すところなどないでしょう」と言われていました。
投げた依頼
このコードをリファクタリングするとしたら、
軽微なものから大きな構造変更まで、段階を分けて提案してもらえますか?
各段階がどういう基準なのかも教えてください。
Bobから返ってきた提案
まさか、こんなに返ってくるとは思っていませんでした。
| 段階 | どれくらいの変更か | 件数 | 例 |
|---|---|---|---|
| Lv.1 | 1ファイルの中で終わる | 5 | 古い書き方が残っている(Java 16以降の短い書き方が使える) |
| Lv.2 | 複数ファイルに触るが、動きは変わらない | 4 | 到達しないコードが残っている(このあと問題になる) |
| Lv.3 | 役割の割り振りを変える。テストも直す | 3 | 3つのコントローラーが同じ処理を持っている |
| Lv.4 | 構成そのものを組み替える | 3 | 1つのコントローラーが画面用とAPI用を兼ねている |
コードを読めない私としては、Bobなかなかやるなあ、という感想をもちました。
2. 気になる項目 — Bob曰く「この"IDチェック"ロジックはデッドコードです。削除できます」
15項目のうちのひとつです。飼い主情報の更新処理にある、5行ほどの箇所を指していました。
Bobが指摘したコード
if (!Objects.equals(owner.getId(), ownerId)) {
result.rejectValue("id", "mismatch", "The owner ID in the form does not match the URL.");
redirectAttributes.addFlashAttribute("error", "Owner ID mismatch. Please try again.");
return "redirect:/owners/{ownerId}/edit";
}
フォームから来たIDとURLのIDを比べて、食い違っていたらエラーにして編集画面に戻す、という処理です。
Bobがデッドコードとした根拠
このコントローラーには、フォームから来た id は細工されて送られてくることがあるので無視する、という設定があります。そして各処理の前に必ず動くメソッドが、URLのIDを使ってデータベースからOwnerを読み込んでいます。
つまりOwnerのIDは、フォームに何を入れてもURLのIDのまま。この2つが食い違うことはないので、if文の中には入りません。書いてあるけれど実行されない、だから削除できる、という根拠でした。
3. 意見が割れる — Claudeに見せたら「それはデッドコードではありません」と言い出した。
Bobの提案をClaudeにも見せました。直すところなどないと言っていたのに15項目出てきたよ、と。
するとClaudeは、前出のデッドコード評価の箇所を別の読み方で解釈しはじめました。
Claudeがデッドコードではないとした根拠
フォームから来た id が無視されるなら、Ownerに入るIDは空のままになるのではないか。空とURLのIDは一致しないので、この2つは常に食い違う。
つまりif文の中には毎回入ることになる。デッドコードどころか、飼い主情報の更新が一切保存できないバグだ、という主張でした。
"デッドコード"部分についてのBobとClaudeの主張
| 結論 | 理由 | |
|---|---|---|
| Bob | 削除できる | 絶対に成立しない |
| Claude | バグなので直すべき | 常に成立する |
結論が異なる、理由も正反対。私にはどちらが正しいか判断する手立てがありません。
4. 聞き直した — Bob「テストで確認しました。ご指摘ごもっともです、必要でした」と自ら訂正
判断できないので、Bobに聞き直しました。
問い方は素朴に、本当にそうなのか、とだけ。
投げた依頼
提案書の 2-D の「デッドコード」について、
「本当にデッドコードなのか?」という意見が出ています。
まずはコードを読み直して再確認してください。
Bobから返ってきた見出し
再確認結果:2-D の「デッドコード」主張は 誤り です
Bobが確かめたこと
コードを読み直したあと、Bobは自分から src/test を検索し、IDが食い違ったときの動作を確かめるテストを見つけました。そのうえで、IDチェックに至る経路を3つに分けて示しました。
| 経路 | IDチェックは働くか |
|---|---|
| ブラウザから普通にフォームを送る | 働かない |
| プログラムから直接オブジェクトを渡す | 働く |
| テストやAPIから細工したリクエストを送る | 働く |
フォームから来た id を無視する設定は、フォーム経由のみの防御でした。テストが示していたのは、それ以外の経路でも狙われうる、ということでした。
このIDチェックとテストコードは、セットで書かれたものだったのでしょう。
Bobの結論
IDチェックについて、最後にこう書かれていました。
このチェックはデッドコードではありません。
提案書の 2-D は「通常フォーム投稿しか考慮していない」という分析の見落としがあり、削除を推奨すべきではありません。むしろこのチェックは IDOR(Insecure Direct Object Reference)攻撃への防御として意味のある安全弁と解釈できます。
自分の見落としがどこにあったかまで言語化して、そのうえで、あのIDチェックが何を守っていたかを特定しました。コードを理解できない私から見ても、これは十分な仕事だと思いました。
Bob、やっぱりすごいかもしれない。
5. 元に戻ってしまう — 聞き直しを4項目まとめたら、また「削除できますよ」。・・・あれ?
1項目でうまくいったので、同じ聞き方をまとめて試しました。IDチェックを含む4項目です。
サブエージェントを4体、1項目に1体ずつ割り当てて並列で走らせました。
投げた依頼(実際にはファイルパスや保存先、サブエージェントを指定しています)
実際のコードを読んで検証してください。
指摘が正しいか、前提が間違っていないかを確認してください。
Bobから返ってきた判定
| 項目 | 判定 |
|---|---|
| エラーメッセージの外部化 | 一部不正確 |
| 重複バリデーションの整理 | 正確 |
| Stream API化 | 誤り |
| IDチェックの削除 | 正確(コードは不要) |
IDチェックは「正確」と判定されて戻ってきてしまいました。理由は最初の提案書と同じでした。これを担当したサブエージェントは、ツール起動が1回で前回より簡易的に見えました。
「コードを読んで」と確認依頼すればよいものと解釈して、疑問の意思や姿勢を伝えなかったのが失敗でした。
6. 取組姿勢を指南 — 「厳格なレビュアーとして、反証を探して」に変えたら、今度は「必要」。・・・よし!
単発で聞き直したときは訂正されたのに、まとめて検証させると戻ってしまう。何か解決方法がないかと悩んだ末に、Bobに役割を与えてみることにしました。プロンプトエンジニアリングという言葉が現れたころに、よく使われたあの技です。
変えたのは3つです。
- 役割を与えた — 「あなたは厳格なコードレビュアーです」
- 求める行為を伝えた — 「各提案を承認してよいか判断してください」
- 進め方を指定した — 「反証を探す立場で調べ」「問題ないと言い切れるか」
対象のコードも、モードも、並列するサブエージェントの数も同じです。テストのことは書いていませんし、「検証」の回で何と判定されたかも伝えていません。
投げた依頼(実際にはファイルパスや保存先、サブエージェントを指定しています)
あなたは厳格なコードレビュアーです。
各提案を承認してよいか判断してください。
実行すると壊れるものがないか、反証を探す立場で調べ、
削除や変更をして問題ないと言い切れるかどうかを結論として述べてください。
Bobから返ってきた判定
| 項目 | 判定 |
|---|---|
| エラーメッセージの外部化 | 条件付き承認 |
| 重複バリデーションの整理 | 条件付き承認 |
| Stream API化 | 修正必須 |
| IDチェックの削除 | 否決(コードは必要) |
IDチェックの削除について、Bobは具体的なテストコードを示し「『デッドコード』判断は誤り。このチェックが機能するケースをテストが直接証明している」と判断していました。
Bobがサブエージェントに渡した指示が変わっていた
走っている途中で見えたのですが、「反証を探す立場で」と頼んだときは、Bobがサブエージェントに渡す調査項目に「テストや他のコードが壊れる可能性を調べる」が加わり、読ませるファイルにもテストクラスが入っていました。
私が渡したのは役割と姿勢だけで、確認項目はBobが自分で組み立てています。求めていたのは、これでした。
7. 最初からそうすれば — 最初のリファクタリング依頼を書き直したら、もうIDチェック削除は提案してこなかった
自分の最初の依頼を読み返しました。「リファクタリングを提案してもらえますか」。役割も、進め方も、何も指定していません。コードを書かない私に、そこまで考えて指示を書くことはできませんでした。
足した指示
あなたは厳格なコードレビュアーです。
(リファクタリングの依頼は1節と同様)
ただし、各提案について、実行すると壊れるものがないかを自分で確認してから挙げてください。
テストコードや設計意図も踏まえ、本当に安全に実行できるかを判断し、
安全でないもの、条件が必要なものは、その条件を明記してください。
Bobが読んだもの
全ソースファイルを19件読み込んだあと、Bobは「次にテストコードと残りのファイルを読み込みます」と言って、さらに14件を読みました。提案書の冒頭には、こう書かれています。
コードを全件精読し、テストコード・JPA制約・Spring MVCの動作仕様と照合した上で各提案の安全性を判定しています。
Bobから返ってきたもの
19項目。規模の段階とは別に、安全性でも分類されていました。
| 安全性 | 件数 |
|---|---|
| 安全(即実施可能) | 10 |
| 条件付き安全(事前確認が必要) | 7 |
| 危険(単独実施非推奨) | 2 |
そして発端になったあのIDチェックは、19項目のどこにもありませんでした。
Bob、やっぱりすごかった。
8. プロンプト強化でも — 立派な依頼文ができたのに、テストは読まずに削除を提案
依頼文を丁寧に書けば結果が変わる。それは分かりました。ただ、私のような素人が、技術のわかったような依頼文を毎回書けるとも思えません。
そこで思い出したのが「プロンプト強化」です。入力した依頼文をBob自身が書き直してくれる機能があります。これを使えばいいのではないか。
プロンプト強化にかけた文
src/main/java 配下のJavaコードのリファクタリング候補を提案して
出てきた依頼文に入っていたもの
- 観点が5分類
- 「メソッド20行超」「ネスト3段以上」といった閾値
- 出力形式の指定
- 優先度の定義
- 「一般論の羅列は不要」という注意
私が手で書いたものより、はるかに整っています。これで依頼しました。
Bobから返ってきたもの
| 項目数 | 17 |
| テストコード | 読んでいない |
| IDチェック | 「常に失敗するロジック矛盾」として削除を提案(優先度:高) |
デッドコードとは言いませんでしたが、削除の提案には変わりありません。しかも理由は、3節でClaudeが言ったものとほぼ同じでした。テストコードも読まなかったようです。
期待した結果になりませんでした。
依頼内容と結果一覧 — 頼み方を変えると、何が変わったか
| 節 | 頼み方(要約) | デッドコードか | テストを読んだか | Bobcoins |
|---|---|---|---|---|
| 1 | 「リファクタリングを提案してもらえますか」 | はい | いいえ | 0.54 |
| 4 | 「本当にデッドコードなのか?」 | いいえ | はい | 0.45 |
| 5 | 「実際のコードを読んで検証してください」 | はい | いいえ | 0.41 |
| 6 | 「厳格なレビュアーとして反証を探して」 | いいえ | はい | 1.74 |
| 7 | 「安全か確認してから挙げて」 | いいえ | はい | 1.03 |
| 8 | プロンプト強化が生成した依頼文 | ━ | いいえ | 1.14 |
Bobがテストコードを開かなかった回は、いずれもデッドコードと判定しました。
参考でBobcoinの消費量も記載しました。6回分の試行を経て、もっとも安く済む頼み方(7節;1.03)が、ようやく自分のものになりました。
おわりに
Bobは、よくできたパートナーでした。聞けば調べてくれるし、間違えたら自分で直してくれる。コードを読めない私でも、ここまで付き合ってもらえるとは思っていませんでした。
ただ、そのBobに何度も同じことを聞いて、返ってくるものが毎回違ったのも事実です。そして、もっともらしく見えたものが正しいとはかぎりませんでした。
Bobに限った話ではないと思います。今回、Claudeも間違えました。
Bob公式ドキュメントの よくある落とし穴を避ける には、こう書かれています。
| 避けること | 代わりにすること |
|---|---|
| 「もっと良くして」のような曖昧な指示 | 明確で具体的なガイダンス |
| 結果の検証をスキップ | Bobの出力が要件を満たしていることを確認 |
この「具体的」も「確認」ももちろん認識しています。でも、今回のリファクタリングというタスクにおいてそれがどういうことなのかは、6回のやり取りを通して、初めて体験することができました。
使い方しだいで結果が変わることも、出てきたものを確かめないといけないことも、たぶん当たり前のことなのだと思います。それを、自分の手で確かめた記録になりました。
本記事は、一連の検証と執筆にあたり Anthropic Claude を利用し、その出力を参考にしています。