はじめに
著者はAndroidアプリ開発の現場で、AIを使って開発しています。
AIが生成したコードの書き方自体は、ほとんど問題になりません。ビルドも通り、そのまま動くことが多いです。
そのうえでレビューでは何を見るかを、実体験を通して考えたことを本記事にまとめます。
AIが得意なこと
大前提ですが、AIが生成したコードは基本的に良いと感じています。
良いと感じるところは主に次のとおりです。
- Kotlinの書き方が素直で、ビルドが通る
- null安全や Coroutines のスコープ管理など、文法まわりのミスが少ない
- 人が書いて起きがちなタイプミスや、
?.letの付け忘れが減った - ロジック単体は正しく、テストも通り、そのまま動くことが多い
実際、実装は非常にスピーディになっており、AIの凄さを日々実感しています。
現場で繰り返し出た3つの指摘
一方で、AIにはできない(あるいは難しい)ことがあります。
- 新しい処理を既存クラスに足すか・新規クラスにするかを判断すること
- その変更が、どの画面・フローに波及するかを見積もること
- コメントを、「何の処理か」が分かる簡潔な日本語で書くこと
ここはコードが動いても残り続けます。
だからレビューで人が見るしかありません。
実際に何度もレビューで書いた指摘を、3つ挙げます。
記載のコードは実際に業務で使用しているものではなく、あくまで例示となります。
1. コメントに関数名・変数名の横文字がそのまま載っている
いちばん頻度が高いのがこれです。
下記はテストコードのコメントですが、本番コードのKDocでも、同じような形で出てくる時があります。
/**
* ## テスト内容
* getResponse が null の場合、isAllSuccess が false を返すことを確認する。
*
* ## 前提条件
* - getResponse = null
*
* ## 期待結果
* - false が返る
*/
コメントは間違っていません。ただ、getResponse も isAllSuccess も、コードを見れば分かる名前です。コメントを読んでも「何の処理か」は分かりません。 横文字の変数名を日本語の文に埋め込んだだけで、処理の説明になっていないからです。
レビューで知りたいのは、変数名ではなく中身です。
-
getResponseは何のレスポンスか。どのAPIの生データなのか、加工後なのか -
isAllSuccessは何が全部成功したときのフラグか。全APIか、全明細か -
nullのときfalseを返すのは、仕様として正しいのか、異常系なのか
変数名のまま書くと、読み手がコードとコメントを行き来しないと意味が取れません。コメントを読む意味が薄れます。
書き直すとこうなります。
/**
* ## テスト内容
* XXX処理のAPIの生レスポンスが無い場合、全件成功とみなさないことを確認する。
*
* ## 前提条件
* - XXX処理の生レスポンスが null
*
* ## 期待結果
* - 全件成功フラグは false
*/
やったことは1つだけです。横文字を、その変数が表している日本語に置き換えた。 コメント単体で「何を確認しているテストか」が読めるようになります。
■なぜAIはこう書くのか:
AIは目の前にある識別子を、そのまま説明文に埋め込みます。変数名は正確なので、AIから見ると「正しく説明している」ことになります。読み手にとって分かりにくいかどうかは、見ていません。
■レビューでの問い:
このコメントを、コードの識別子を知らない人に渡して通じるか。 一目で何をしているのか、がわかるか。通じなければ、横文字を処理の言葉に直します。
2. 既存クラスに足さず、新しいクラスを勝手に作る
すでに既存で実装されているクラスがあり、責務的にそこに追加すれば良いのに、新規のクラスを勝手に作成しているケースが散見されました。
例:
文字列フォーマットに修正追加する場合、
作成済みのStringFormatter に実装を追加するだけだったのに、
StringFormatUtil という新規クラスを作っていた、など
■なぜAIはこう書くのか:
開いているファイルと、検索でヒットした範囲しか見ていません。既存クラスが見つからなければ「無い」と判断して、新規ファイルを作ります。しかも単体で見ると綺麗なので、差分だけ読んでいると気づけません。
■レビューでの問い:
この新規ファイルは、本当に新規で必要か。同じ役割のクラスが、すでに存在していないか。
3. 影響範囲が大きい場所に、追加実装する
これが実害としては一番大きかったものです。
手を加えなくてもよいはずなのに、さまざまな機能で使用する共通箇所に修正を入れてました。
実際に目にした実例として、アプリ内DBの更新処理に手を加えていました。
差分のコード自体は正しく、今回の画面では動きます。ただしこの更新は他の処理も通るので、意図しないデータが別機能でも書き換わります。
他の機能や画面でも通る場所を安易に書き換えると影響が大きく、バグの原因となりえます。
■なぜAIはこう書くのか:
1か所直せば全部に効く場所を選びます。共通の更新処理は、差分が小さく見えるからです。差分の行数だけで見ると、最適な選択に見えます。
■レビューでの問い:
そこに手を加えるべきなのか。その行が実行されるパスは何本か。
見るべき場所は、だいたい決まっています。
| 種類 | 見るポイント |
|---|---|
| 保存領域の共通更新 | 他の画面・機能も同じ更新を通っていないか |
| 全画面の基底クラス | 継承しているクラス数を数える |
| グローバル状態 | 参照箇所全部が影響先になる |
| 通信の共通処理 | 全APIリクエストに乗っていないか |
この表に載る処理が差分に出てきたら、「どれだけのフローに乗るか」から見ます。
共通していたのは「プロジェクトの事情を知らない」こと
3つの指摘は、見た目は違います。でも原因は共通でした。
AIは、今開いているファイルしか知りません。このプロジェクトの事情は知りません。
学校のテストで教科書の1ページだけ渡されて答えを書け、と言われているイメージです。渡されたページの中では正しい答えを出します。でも「その答えは、別のページに既に書いてある」「その答えをここに書くと、他のクラスが全部壊れる」は、渡されていないページの話なので分かりません。
| 指摘 | AIが知らなかった事情 |
|---|---|
| コメントに横文字が残る | チームではコメントを「処理の説明」として読む。変数名の英単語をそのまま載せても、何の処理かは伝わらない |
| 既存に足さず新規クラスを作る | 同じ役割のクラスが、別の名前ですでに存在する |
| 影響範囲が最大の場所に追加 | そのクラスを触ると、決済フロー全部に波及する |
だから、生成されたコードだけを見て「正しい/間違い」を判定しても足りません。「この書き方以外の選択肢は無かったか」を、レビュアーが自分で並べて比べる必要があります。
たとえば「新しいクラスを作った」という差分に対して、「既存クラスに1メソッド足す」案を自分で想像する。差分の中には、その案は書いてありません。人が持ち出さないと出てきません。
気をつけること:レビューの順番を変えて見落としを減らす
差分を上から読むのをやめました。今はこの順番で見ています。
1. 新規ファイルはあるか。あるなら、既存に同じ役割のクラスが別の名前で無いかを探す
2. 基底クラス・シングルトン・Interceptor・DI Module を触っているか。触っているなら、その行が乗る実行パスを数える
3. コメントに関数名・変数名の横文字がそのまま載っていないか見る。載っているなら、処理の言葉に直す
4. ここでようやく、ロジック本体を読む
要点は、
置き場所と影響範囲を先に見て、ロジックは後に読むこと
です。
差分を上から順に読むと、ロジックの細部に頭を使ってしまいます。そしてAIのロジックは大体正しいので、読み終わったときには「問題なし」の気分になっています。その状態で新規クラスの妥当性を疑うのは、かなり難しいです。
順番を変えるだけで、
2. 既存クラスに足さず、新しいクラスを勝手に作る
と
3. 影響範囲が大きい場所に、追加実装する
の見落としが目に見えて減りました。
まとめると、以下の3つを毎回見ています。
| # | 観点 | 具体的な問い | 見る場所 |
|---|---|---|---|
| 1 | 置き場所 | その新規ファイルは本当に新規で必要か。既存に同義語のクラスは無いか |
git diff --name-status の差分など |
| 2 | 影響範囲 | 修正箇所が他機能でも通らないか | 基底クラス・シングルトン・Interceptor・DI Module・保存箇所など |
| 3 | コメント | 横文字を知らない人に、このコメントは通じるか | KDoc・行コメント・テストコメント |
順番は、この表の上から下です。1と2が構造の話で、事故ったときのコストが大きい。3は読み手のコストの話です。
まとめ
- AIが生成したコードは基本的に良いです。落ちるのは文法やバグではなく、設計判断とプロジェクトの事情の層でした。レビューの当たり所が上にずれます
- 繰り返し出た指摘は3つでした
- コメントに関数名・変数名の横文字がそのまま載っていて、何の処理かがコメント単体では読めない
- 既存クラスに足さず新規クラスを作る。原因は語彙のずれで、検索に引っかからないと「無い」と判断される
- 影響範囲が最大の場所に追加実装する。AIは差分が最小になる場所を選ぶので、構造上ここを選びやすい
- 共通の原因は「AIがプロジェクトの事情を知らない」ことでした。レビューは間違い探しではなく、書かれていない選択肢と比べることになります
- 差分を上から読むのをやめて、新規ファイル → 影響範囲の大きい場所 → コメント → ロジック本体の順に変えると、見落としが減りました
レビューで見ているのは、コードの正しさそのものではなくなりました。AIが選ばなかった選択肢のほうです。そこはコードのどこにも書かれていないので、今のところ人が持ち出すしかありません。
残課題
ただし、これらの指摘を毎回レビューでコメントするのも限界があります。
今後は AIのRule整備など、指摘しなくても済む仕組みを先に渡すほうへ寄せたいです。
プロジェクトの事情を人が毎回補うのではなく、AIが最初から知っている状態にする。
それが次の宿題です。