はじめに
私は新卒でエンジニアになって1年が経ちました。
生成AIに実装を依頼すると、コードを書く時間そのものは大きく短縮できることを実感しています。
一方、私のようなジュニアエンジニアの場合、生成されたコードを理解するまでに時間がかかるため、 期待したほど開発全体は速くならない とも感じます。
そこでまず、生成されたコードを読むためのレビューガイドをAIで作ってみました。
ガイドに沿えば、一つひとつの処理が何をしているのかは確認できます。
しかし、それらがどうつながり、なぜその構成になっているのかまでは理解できませんでした。(一敗)
結局、コードを時間をかけて読み直し、それでも本当に理解できているのか不安なまま先輩にレビューをお願いすることがあります。
実装時間が短くなっても、これでは開発全体を効率化できたとは言えません。
そこで、問題はコードの読み方ではなく、 実装を依頼する前の準備にあるのではないかと考えました。
作るものの要件と処理の流れを先に整理し、自分の言葉で説明できる状態にしておけば、生成されたコードを自分の想定と比較できます。
本記事では、ジュニアである私が AIとの壁打ちで要件と実装範囲を整理し、ADRに残すことで、生成されたコードを理解しやすくした方法 を紹介します。
「コードの出力を読みやすくする」より 「出力を想像できるようにする」 が目標
本記事の内容を実践すると
- コードレビューの件数が明らかに減った
- 先輩の指摘に対して、自分の意図を説明しやすくなった
- AIの出力でもコードの理解ができている感覚がある
- AIとのラリーが減った
- 自分で「なんでこうしたんだっけ」が思い出しやすくなった
- 実装前に考慮すべきポイントがなんとなくわかるようになった
なぜ生成されたコードが理解しにくいのか
では、本題です。
私は、生成されたコードが理解しにくい原因は、AIに実装を依頼する前の段階にあると考えます。
処理の全体像が頭に浮かんでいなければ、どのような順番で処理が進み、各コードがどの役割を担うのかを想像できません。
その状態でコードを読んでも、 目の前の処理を一つずつ確認するだけになり、処理同士のつながりを見失ってしまいます。
実装する範囲が曖昧なことも、コードを複雑にする原因です。
何を実装し、何を実装しないのかを決めないまま雑にAIに依頼すると、 想定していなかった例外処理や抽象化まで含まれることがあります。
しかし、依頼した側も完成形を描けていないため、生成されたコードが自分の想定から外れていても、 その「ズレ」に気づけません。
こうして、必要以上に複雑な実装を受け取ることになります。
「めっちゃAI考え込んでる...」「なんか思ったより長い変更出た...」
全体の流れも実装範囲も曖昧なままでは、生成されたコードが何をしているのかを自分の言葉で説明できません。
コードを読むたびにAIへ「このコードは何してんの」と質問することになり、実装で短縮した時間を理解のために使ってしまいます。
質問を重ねるほど、AIの利用コストも増えていきます。
CSV出力を依頼した場合
例えば次のプロンプトで実装を依頼するとします。
ユーザー一覧をCSVでダウンロードできるようにして
依頼者が想定していたのは、既存の一覧取得処理から必要な項目を取り出し、CSV形式で返すだけの小さな変更です。
しかし、「対応する形式はCSVだけ」「対象件数は少ない」「ファイルの保存や非同期処理は不要」といった範囲を伝えていなければ、AIは将来の拡張まで考慮して実装することがあります。
たとえば、次のような仕組みまで追加されるかもしれません。
- CSVとJSONを切り替えるためのインターフェース
- 出力形式ごとのクラスを生成するFactory
- ファイルを保存するストレージ
- バックグラウンドで処理するためのキュー
- 失敗時の再試行処理
本来は一つの処理で済む変更が、複数のクラスや設定ファイルにまたがる実装へ膨らんでしまいます。
将来、本当に複数の出力形式や大量データへ対応するなら、これらの仕組みが必要になる可能性はあります。
しかし、現在の要件がCSV出力だけであれば、まだ必要のない拡張性です。
現在の要件に対して過剰な仕組みまで作り込むことを、ここではオーバーエンジニアリングと呼びます。
最近のAIが優秀になってきているが故に、ありがた迷惑が多い印象
依頼者が実装範囲を決めていないと、それぞれのクラスが何のためにあるのかはAIに聞けても、そもそも今回必要なのかを判断できません。
結果として、「AIによる実装」と「ユーザーの想定」のギャップが生じることで、生成されたコードの理解が難しくなり、理解にもレビューにも時間がかかるようになります。
解決策の方向性
生成されたコードを理解するには、コードを受け取ってから読み方を工夫するだけでなく、 AIへ依頼する前に自分の想定を明確にしておく必要 があります。
まず、実現したい要件と処理の流れを、自分の言葉で説明できる状態にします。
ここで、実装方法の細部まで決める必要はありません。
実装前に、次の3点を説明できる状態にします。
- 解決すべき課題はなにか
- 何を実現するのか
- 今回は何を扱わないのか
この3点を説明できれば、AIに任せる範囲が見えてきます。
この準備があれば、生成された実装を自分の想定と比較できます。
先ほどのCSV出力の例でも、JSONへの切り替えや非同期処理は今回の範囲外だと判断できます。
必要のない実装を削るようAIに依頼できるため、オーバーエンジニアリングを防ぎやすくなります。
こうすることで、自分で説明できない部分をレビュー前に特定して確認できるため、先輩社員がコードの意図を一から読み解く必要がなくなり、レビューの負担も抑えられます。
私の解決方法
私は、実装前の準備を毎回同じ手順で進められるように、dig-and-write-adrSkillを登録しています。
dig SkillでAIと実装方針を確認し、決めた内容を .md に記録するSkillです。
各Skillについては追って説明します。
---
name: dig-and-write-adr
description: 徹底的な質問を通じて計画や設計を洗練しながら、ADR と用語集も作成する。
disable-model-invocation: true
---
Skill ツールの「dig」を行ない、終了してまとめフォーマットを出力した後、会話した内容をADRとしてmdに記録する。
こちらのgrill-with-docsSkillを参考にしています。
1. 実装前にAIと目線を合わせる(digフェーズ)
AIへ実装を依頼する前に、要件と処理の流れを確認します。
何をどこまで実装するのか、今回は何を実装しないのかを決め、AIと自分が同じ完成形を想定できる状態を目指します。
ただ、経験の浅いジュニアエンジニアが、検討すべき論点を一人ですべて洗い出すのは困難です。
自分では十分に整理できたつもりでも、例外時の動作や既存機能への影響などを見落としていることがあります。
そこで、実装前の壁打ちにdig Skillを使います。
次の形式で今回私が作ったdig-and-write-adrを呼び出すと、digを呼び出せます。
/dig-and-write-adr <question>
すると、AIが実装方針を決めるための質問と4つの選択肢を提示します。
自分で検討項目を一から考える必要はありません。
提示された選択肢から方針を選びながら対話を進められるため、経験が浅くても検討漏れに気づきやすくなります。
質問に答えていくうちに、要件、処理の流れ、実装範囲が明確になり、AIとの認識のずれを減らせます。
例
$ /dig-and-write-adr ユーザー一覧をCSVでダウンロードできるようにしたい
---
### Q1: 「ユーザー一覧」は、既存ドメインのどの一覧を指しますか?
このコードベースには `User` エンティティや認証ユーザー一覧はなく、人物の一覧としては飼い主(`Owner`)と獣医師
(`Vet`)があります。対象を誤ると、CSVの項目・導線・API設計がすべて変わります。
- **A** — 飼い主一覧(`/owners`)
- **B** — 獣医師一覧(`/vets.html`)
- **C** — 飼い主と獣医師の両方
- **D** — 新たに認証ユーザー/利用者の管理機能を追加する
**推奨: A** — 現在のUIで検索可能な人物一覧があり、氏名・住所・市・電話番号というCSV向けの項目も揃っているためです。
CSV出力の対象:
❯ A — 飼い主一覧
B — 獣医師一覧
C — 飼い主と獣医師の両方
D — 新しい認証ユーザー管理
Other (type your answer)
もしわからない単語があっても、その都度質問すればAIが補足してくれます。
$ 既存ドメインってなに?
---
「既存ドメイン」は、このアプリが現在扱っている業務上の対象・概念のことです。Spring Petclinic では、主に **飼い主
(Owner)・ペット(Pet)・診療(Visit)・獣医師(Vet)** が該当します。
つまり先ほどの質問は、「ユーザー」という言葉が、このアプリ内では誰を指すのかを確認しています。
### Q1: CSVでダウンロードしたい人物は誰ですか?
- **A** — 飼い主の一覧
- **B** — 獣医師の一覧
- **C** — 飼い主と獣医師の両方
- **D** — ログイン利用者など、新しい種類のユーザー
**推奨: A** — 現在の「Find owners」から表示する一覧に、CSVダウンロードを自然に追加できるためです。
CSVに出力する人物:
❯ A — 飼い主
B — 獣医師
C — 飼い主と獣医師
D — 新しい種類のユーザー
Other (type your answer)
結構細かく聞いてくれるので若干疲れる
2. 目線を合わせた結果をADRとして記録する(mdへ書き出しフェーズ)
digで決めた内容は、 ADR(Architecture Decision Record) としてMarkdownファイルに記録します。
(私はObsidianで管理しています。Obsidian Skillを入れていれば勝手に関連するファイル同士でリンクしてくれるのでありがたい)
実装することと実装しないこと、採用する処理の流れ、その方針を選んだ理由を残しておけば、後から読み返しても判断の経緯をたどれます。
CSV出力の例であれば、次のような内容を残します。
# ユーザー一覧のCSV出力
## 決定
- 対象は飼い主(`Owner`)とする。
- 既存の一覧取得処理を利用する
- 出力形式はCSVのみとする
...
## 対象外
- ファイルの保存
- 非同期処理
- JSONなど、CSV以外の出力形式
...
## 理由
対象件数が少なく、現時点ではCSV以外の出力形式を求められていないため
etc ...
実装中に迷ったときは、ADRに戻って生成されたコードが決めた方針に沿っているかを確認できます。
AIが会話のコンテキストを失っても、ADRを読み込ませれば、実装範囲と方針を再び共有できます。
レビューする先輩社員にとっても、コードだけでは分からない実装意図や判断理由を確認するための資料になります。
実装前の準備のBefore/After
例: spring-petclinicにユーザー一覧csv出力機能を実装する場合
Before:そのまま実装を依頼する
$ ユーザー一覧をCSVでダウンロードできるようにして
とだけAIへ伝え、そのまま実装を依頼します。
生成されたコードには、複数の出力形式へ対応するためのインターフェースやFactory、ファイルを保存する仕組み、非同期処理などが含まれていました。
しかし、依頼する前に実装範囲を決めていないため、どこまでが現在の要件に必要なのかを判断できません。
それぞれのクラスについてAIへ質問すれば、コードの役割は分かります。
それでも、今回の実装に必要な理由までは説明できないままです。
最終的には、複雑な構成を十分に理解できないまま、先輩社員へレビューを依頼することになります。
「なんでFactoryあるんだ!」
「なんだこの非同期処理!」
「『このコード解説して』」
After:方針を決めてから実装を依頼する
実装を依頼する前にdig-and-write-adrを使い、AIからの質問に答えながら要件と処理の流れを整理します。
$ /dig-and-write-adr ユーザー一覧をCSVでダウンロードできるようにしたい
するとAIから質問がいくつか返ってきます。
### Q3: CSVの1行と列はどう構成しますか?
`Owner` は、ID・名・姓・住所・市・電話番号に加えて複数のペットを持ちます。ペットまで含めると、「飼い主1人=1行」という単純な一覧ではなくなります。
- **A** — 飼い主1人を1行にし、`ID, First Name, Last Name, Address, City, Telephone` を出力
- **B** — 画面表示に合わせ、`Name, Address, City, Telephone, Pets` を出力(複数のペット名は1セルに連結)
- **C** — ペットごとに1行を作り、飼い主情報を繰り返して出力
- **D** — 列を個別指定する
**推奨: A** — 飼い主一覧として正規化された扱いやすいCSVになり、複数ペットの表現規則を持ち込まずに済みます。IDも後続処理で安定した識別子として使えます。
CSVの行・列構成:
❯ A — 飼い主の基本6項目(推奨)
B — 画面と同じ項目+ペット名
C — ペットごとに1行
D — 列を個別指定
Other (type your answer)
対応する形式はCSVだけ、6項目の情報のみ出力する、既存の一覧取得処理を利用する、ファイル保存と非同期処理は行わない、などAIからの質問に回答していきます。
「基本6項目を推奨してるけど、今回の要件的にペット名も必要だなあ」
決めた内容と理由はADRに記録し、そのADRを前提としてAIへ実装を依頼します。
ここでは、ADRが判断基準になります。
生成されたコードに出力形式を切り替える仕組みや非同期処理が含まれていれば、今回の要件には不要だと判断できます。
## コンテキスト
Spring Petclinicには認証用の`User`モデルが存在しない。
人物を表す既存モデルは飼い主の`Owner`と獣医師の`Vet`である。
今回の「ユーザー一覧」は、顧客一覧に相当する`Owner`を指すものと確定した。
...
## 決定要因
- 画面上のページではなく、検索に一致する全件を取得できること
- 数万件でも全件をメモリへ保持しないこと
etc ...
要件と処理の流れを先に理解しているため、生成された各処理が何のためにあるのかを自分の言葉で説明できます。
レビュー時にはコードとADRを一緒に提示できるので、先輩社員も実装意図と判断理由を確認したうえでコードを読めます。
どちらもAIにコードを生成させている点は同じです。
違いは、実装前の判断基準です。
判断基準があれば、生成されたコードを理解し、不要な実装を見抜けるようになります。
まとめ
AIが生成したコードを理解できない原因は、コードの難しさだけにあるわけではないと思っています。
実装前に要件や処理の流れを整理できていないと、生成されたコードが自分の想定どおりなのか、今回の要件に必要なのかを判断できません。
今回の手順を挟むと、AIが生成したコードをただ読むのではなく、自分が決めた方針と照らし合わせながら読めるようになります。
コードの役割だけでなく、「なぜ今回この実装が必要なのか」まで説明できれば、レビューを依頼するときにも実装意図を共有できます。
「コード出力後にどうするか」ではなく、実装前の要件定義から「コード理解難しい」問題を緩和できそうです。