この記事は生成AIとの対話をもとにまとめたものです。
の別バージョンとしてFalbe5と相談して作成したものです。
はじめに
ウォーターフォール開発では、画面一覧・画面遷移図・項目定義書といった設計書が成果物として残ります。そして実装工程では、その設計書を見ながら人間が画面を1枚ずつ作ります。似たような画面を何十枚も。
この記事では、その実装工程を次のように変える方法を紹介します。
- 設計書を「人が読む文書」ではなく「機械が読めるデータ」として管理する
- そのデータから、画面の HTML・入力チェック・Controller を 自動生成 する
- 生成AIには画面を自由に書かせない。AIの出番は「データを書く相棒」と「生成の仕組みの修理」に絞る
対象は、Spring Boot + Thymeleaf でのマルチページアプリケーション(画面遷移のたびにサーバがHTMLを返す、昔ながらの構成)です。初級エンジニアの方にも読めるよう、専門用語には説明を付けます。
用語ミニ辞典
この記事で使う、一般的でない(または人によって意味がぶれる)用語を先にまとめます。読みながら戻ってきてください。
| 用語 | 意味 |
|---|---|
| IR(中間表現) | Intermediate Representation。設計書とソースコードの中間に置く、機械が読める形式のデータ。この記事では YAML で書いた「画面定義データ」のこと |
| 正本(せいほん) | 「どれが正しいか」の唯一の基準になるもの。正本以外は、正本から作られたコピーとして扱う |
| 決定論的 | 同じ入力を与えたら、必ず同じ出力が出ること。サイコロの要素がないこと |
| 生成器(ジェネレータ) | IR を読み込んで、Java や HTML のファイルを書き出すプログラム。AIではなく普通のプログラム |
| UIカタログ | 画面で使ってよい部品(テキストボックス、一覧表、確認画面レイアウトなど)を集めた、閉じた部品集 |
| フラグメント | Thymeleaf の部品化機能。HTML の一部を別ファイルに切り出して、th:replace で埋め込めるようにしたもの |
| ゴールデン画面 | 手作業で丁寧に作った「お手本の画面」。生成器がこれと同じものを作れるかの基準になる |
| ゲート | 合格・不合格を機械的に判定する検査。テストが全部通る、コンパイルが通る、など |
| CI | Continuous Integration。コードを変更するたびに、ビルドやテストを自動で走らせる仕組み(GitHub Actions など) |
| ポート | 画面側から業務ロジックを呼び出すための「窓口」となるインターフェース。画面チームと業務ロジックチームの境界線になる |
| PRG | Post-Redirect-Get。フォーム送信(POST)の後にリダイレクトさせる定石。ブラウザ更新での二重送信を防ぐ |
| Bean Validation | Java の入力チェックの仕組み。@NotNull や @Size(max=80) のようなアノテーションで宣言する |
| ループエンジニアリング | AIに毎回指示を出すのをやめ、「ゴール・合否の測り方・やめどき」を先に決めて、繰り返しを仕組みに任せる考え方 |
| グラフエンジニアリング | 複数の作業者(AI・スクリプト・人)を配置し、「次は誰か」「失敗したらどこへ戻るか」を線でつなぐ考え方 |
全体像
先に完成形を見せます。中心に IR(画面定義データ)があり、すべてがそこから生成されます。
IR(画面定義データ)= 正本
│
├─ 生成器 ─→ 画面HTML・Form・Controller ← 人は触らない
├─ 生成器 ─→ 設計書(Excel / PDF) ← 納品物。人は触らない
└─ 生成器 ─→ ポート(業務ロジックの窓口) ← 実装は人+AIが書く
ポイントは矢印の向きです。設計書からコードを作るのではなく、IR から設計書とコードの両方を作ります。 同じ正本から出るので、設計書とコードは定義上ずれません。
ここから、この形にたどり着くまでを順番に説明します。
ステップ1:設計書を IR にする
なぜ設計書から直接生成しないのか
「設計書(Excel)を生成AIに読ませて、画面を作らせればいいのでは?」と思うかもしれません。実際に動くものは出てきます。しかし2つの問題があります。
1つ目は 揺れ です。生成AIは同じ設計書を読ませても、毎回微妙に違うコードを出します。昨日と今日で出力が変わると、テストが落ちたとき「設計書が悪いのか、AIの気まぐれか」を切り分けられません。
2つ目は 検査のしにくさ です。Excel のセル結合や表記ゆれ(「必須」「○」「Yes」が混在するなど)をAIがどう解釈したか、機械的に確かめる方法がありません。
そこで、設計書の内容を一度、機械が読める形式に落とします。これが IR です。
screenId: customer-edit # 画面ID
path: /customers/{id}/edit # URL
layoutId: form # カタログ上のレイアウト種別
fields:
- name: name # 項目名
widgetId: text # 使う部品
required: true # 必須
maxLength: 80 # 桁数
- name: email
widgetId: text
required: true
actions:
- id: confirm
next: customer-confirm # 押したら確認画面へ
handler: customerEdit.confirm # 業務ロジックの窓口(後述)
docOnly: # 人間が読む用。生成器は無視する
業務説明: |
顧客基本情報の編集画面。営業担当のみ更新可能。
備考: |
メールアドレス変更時は確認メールを送信する。
IR が正本、設計書は生成物
ここが従来の発想と逆になるところです。IR を正本にして、Excel の設計書は IR から生成します。
設計書が納品物として必要なことは変わりません。ただ「人が Excel を書き、機械が読む」のではなく、「人が IR を書き、機械が Excel を書く」に変えます。設計レビューは生成された Excel を見て行い、直したいことがあれば IR を直して再生成します。
こうする理由は単純で、正本が2つあると必ずずれるからです。Excel と IR の両方を編集できる状態にすると、数週間で「どっちが新しいんだっけ?」が始まり、生成物への信頼が崩れます。
人間用の情報も IR に残す
設計書には、機械が使わない情報も書いてあります。業務説明、画面レイアウトのイメージ、備考などです。これを捨ててはいけません。
先ほどの YAML の docOnly セクションがその置き場です。生成器は無視しますが、設計書を生成するときはそのまま出力します。画面仕様と人間向けの説明が同じファイル・同じ画面IDで束ねられるので、迷子になりません。
すでに設計書がある場合:一度だけ変換して、乗り換える
既存プロジェクトには人が書いた設計書がすでにあります。この場合は、次の手順で移行します。
- AIを使って、既存の Excel から IR を起こす(変換は一度きりなので、AIの揺れは許容できる)
- 人が IR を承認する
- 承認した画面から、IR を正本に切り替える。元の Excel は凍結する(編集したら CI でエラーにする)
ここで問題になるのが「300画面分の YAML を人が全部読むのか?」です。読みません。逆翻訳チェック をします。
元のExcel → AI変換 → IR → 生成器 → 再生成したExcel
└──────────── 差分を比較 ────────────┘
IR から設計書を再生成して、元の Excel と突き合わせます。差分がなければ、変換で何も失われていないので承認してよい。人が読むのは 差分が出た画面だけ です。
移行は全画面一斉ではなく、承認できた画面から1枚ずつ切り替えます。
| 状態 | 正本 | 元のExcel |
|---|---|---|
| 未着手 | 元の Excel | 編集してよい |
| 変換済み・未承認 | まだ元の Excel | 編集されたら変換やり直し(この状態を長く放置しない) |
| 承認済み | IR | 凍結。編集は CI で拒否 |
ステップ2:UIカタログを作る
部品集を「閉じる」
生成AIに画面を自由に書かせると、画面ごとに微妙に違うHTMLができます。ボタンの位置、エラーの出し方、余白。1枚ずつは正しそうでも、全体ではバラバラです。
そこで、使ってよい部品をカタログとして固定 します。
- レイアウト:一覧画面、入力画面、確認画面、完了画面(日本の業務システムの定番4点セット)
- ウィジェット:テキストボックス、セレクトボックス、日付、一覧表、ボタン……
実体は Thymeleaf のフラグメント(部品化したHTML)と、それに紐づく CSS / JS です。ここで大事な一線があります。CSS と JS の所属先は部品であって、画面ではありません。 「この画面だけ見た目を調整したい」が始まると、カタログを閉じた意味がなくなります。
カタログは「お手本」から抽出する
カタログを机上で設計すると、たいてい現実に合いません。おすすめは逆順です。
- 代表的な画面を3枚だけ、手作業で丁寧に作る(一覧、編集+確認、完了)
- その3枚から共通部品を切り出して、カタログにする
- この3枚は「ゴールデン画面」として保存し、生成器がこれと同じ画面を作れるかをテストにする
例外の逃げ道も先に作る
業務システムには、カタログに収まらない画面が必ず出ます(特殊な集計表など)。そのたびに部品を追加していると、承認待ちで開発が止まります。
そこで 拡張点 を用意します。IR に「この画面には拡張がある」と宣言した画面だけ、決められた場所に手書きのHTML部品を差し込めるようにします。宣言なしの手書きは禁止です。例外を禁じるのではなく、例外の置き場所を1か所に決めて、数を数えられるようにします。拡張点が増え続けたら、カタログが現実に合っていない信号です。
ステップ3:生成器で決定論的に作る
生成器はAIではなく、コンパイラ
生成器は「AIが毎回画面を書く仕組み」ではありません。javac が .java から .class を作るように、IR から画面一式を作る 普通のプログラム です。同じ IR を入れれば、必ず同じファイルが出ます。
| IR の内容 | 生成されるもの |
|---|---|
| 項目名・部品・必須・桁数 |
CustomerEditForm.java(Bean Validation 付き) |
| URL・ボタン・次画面 |
CustomerEditController.java(PRG の遷移付き) |
| レイアウト種別と項目一覧 |
customer-edit.html(カタログ部品の組み合わせ) |
| メッセージキー | messages.properties |
| handler の宣言 | ポートのインターフェース(後述) |
生成される HTML は「完成した画面」ではなく、カタログ部品への参照リストです。
<th:block th:replace="~{_catalog/layouts/form :: layout}">
<div th:replace="~{_catalog/widgets/text :: field(field='name')}"></div>
<div th:replace="~{_catalog/widgets/text :: field(field='email')}"></div>
</th:block>
見た目を変えたければカタログを直す。項目を増やしたければ IR を直す。組み立てがおかしければ生成器を直す。直す場所が3つに分かれていて、生成されたファイルを直接直すことは決してしない ── これがこの方式の一番大事なルールです。
Controller は画面ごとに生成する
Controller は「汎用の1個を作る」か「画面ごとに作る」かの選択があります。サブシステムごと・機能ごとにチームが分かれる現場では、画面ごとに生成する のがおすすめです。パッケージをサブシステム単位で分けられ、スタックトレースが画面名で読めて、デバッグが楽です。
ただし「画面ごとに作る」は「画面ごとに手で書く」ではありません。生成器が1画面1Controllerを書き出します。個別ファイルがあるとつい手を入れたくなりますが、そこは我慢です(次の節で理由を説明します)。
生成物はコミットして、差分をレビューする
生成されたコードは Git にコミットします。理由は、生成器を直したときに 影響が差分として見える からです。型紙を1行変えたら300画面分の差分が出る。その差分こそが、生成器修正のレビュー資料になります。
なぜ「生成されたコードを直してはいけない」のか
テストが落ちたとき、一番やりたくなるのはこれです。
生成された HTML に足りない項目を1行足して、テストを通す
その画面は動きます。しかし、工場で不良品を1個だけヤスリで削って出荷するのと同じです。金型(生成器)は直っていないので、明日も同じ不良が出ます。しかも次に再生成したら、手で足した1行は消えます。
欲しいのは「この画面が動いた」ではなく「次からも同じ失敗をしない」です。だから失敗したら、原因が次の4つのどれかを見極めて、上流を直してから再生成します。
| 分類 | 意味 | 直す場所 |
|---|---|---|
| 仕様の不備 | IR に項目が足りない・矛盾している | IR |
| 生成器の不備 | IR には書いてあるのに、生成器が落としている | 生成器 |
| 部品が足りない | 使いたい部品がカタログにない | カタログ(人の承認つき) |
| 画面の話ではない | 割引計算のような業務ロジックの問題 | 画面の外(ポートの実装側) |
うれしいことに、この分類はほとんど スクリプトで機械的に判定できます。
- 部品IDがカタログにない → 部品が足りない
- IR に項目があるのに生成物にない → 生成器の不備
- IR に項目がない → 仕様の不備
AIの判断が要るのは、どれにも当てはまらない残りだけです。そして同じ原因で300画面が落ちても、原因は1つなので、調べるのも直すのも1回で済みます。
ステップ4:業務ロジックとの境目 ― ポート
画面は自動生成できても、業務ロジック(金額計算、承認判定など)は自動生成できません。ここに明確な境界線を引きます。
IR のアクションに handler: customerEdit.confirm のように窓口の名前を宣言させ、生成器がその インターフェース(ポート) を生成します。
// 生成されるインターフェース。実装は人+AIが書く
public interface CustomerEditPort {
/** 顧客基本情報の編集。営業担当のみ更新可能。
メールアドレス変更時は確認メールを送信する。 */
ConfirmResult confirm(CustomerEditForm form);
}
画面側(生成されたController)はポートを呼ぶだけ。実装は業務ロジックのチームが書きます。ここが画面チームと業務チームの分業の境界線です。
備考や業務説明は、どうコードに反映するのか
設計書の備考欄には、いろいろなことが書かれています。これを「機械が作るものを変えるか?」で仕分けます。
| 備考に書かれがちな内容 | 行き先 |
|---|---|
| 「Aを入力したらBは必須」(相関チェック) | IR のルールに昇格 → Form に自動反映 |
| 「◯◯の場合のみ表示」(表示条件) | 同上 |
| エラー文言・案内文 | メッセージ定義へ |
| 項目の並びやグループ | IR の構造情報へ |
| 「与信が◯◯なら承認が必要」(真の業務ルール) | ポートの契約へ(下記) |
| 純粋な補足説明 |
docOnly のまま。設計書に出力される |
最後の「真の業務ルール」は、文章のままポートに引き継ぎます。具体的には、ポートの Javadoc に業務説明の原文を載せ、さらにルール化できる備考は 未実装のテストの骨子 として生成しておきます。
@Test
@Disabled("未実装")
@DisplayName("メールアドレス変更時は確認メールを送信する")
void sendConfirmationMailWhenEmailChanged() { }
ポートを実装する人(またはAI)は、このテストを緑にすることがゴールになります。備考が「設計書に書いてあったが誰も読まない文章」ではなく、実装のチェックリストに変わります。
回し方:ループとグラフ、でも実体は CI
ループエンジニアリングの考え方
生成AIとの開発でよくあるのは、こういう往復です。
- 「この画面を作って」
- 結果を見る
- 「ここが違う、直して」
- また見る
これは人間が手で回しているループです。疲れると「だいたい良さそう」で終わります。
ループエンジニアリングは、この往復を仕組みにします。人が先に決めるのは3つだけです。
- ゴール:何ができたら成功か(「いい感じの画面」はダメ。「テストが全部通る」ならOK)
- ゲート:誰がどう合否を測るか。作ったAI本人に「できた?」と聞くのは、自分の答案を自分で採点するのと同じで、いつも満点になります。テストのような機械の判定を使います
- やめどき:合格したら、同じ失敗が2回続いたら、5周したら人を呼ぶ。「うまくいくまで」は止まりません
グラフエンジニアリングの考え方
失敗したときの「直す場所」が1つなら、ループ(もう一周)で足ります。しかしこの仕組みでは、直す場所が4つ(IR・生成器・カタログ・ポート)に分かれています。「失敗の種類によって、次に誰が動くか」を線でつないだ配線図が必要で、これがグラフエンジニアリングです。
とはいえ、実体は CI で十分
ここまで聞くと「専用のAIエージェント基盤が必要そう」に見えますが、この構成では違います。生成もゲートも決定論的なスクリプトなので、回すのは普通の CI で足ります。 push したら、生成 → コンパイル → テストが走り、赤ならスクリプトが原因を分類する。それだけです。
AIが働く場所は3つに絞られます。
| 作業台 | 頻度 | やること |
|---|---|---|
| IR を書く | 毎日 | エディタで IR を編集。スキーマチェックが即座に赤線を出し、プレビューで実画面を確認しながら書く。AIは「この画面設計を IR にして」の相棒 |
| 生成器を直す | CI が赤のとき | 分類が「生成器の不備」なら、生成器の型紙を直して再生成 |
| 移行する | 一度きり | 既存 Excel → IR の変換。終わったら退役 |
判断が必要な場所を減らすほど、AIは安全に速く働けます。 AIを最大限活用する設計とは、AIに全部任せる設計ではなく、AIの判断が要る場面を絞り込む設計です。
やってはいけないことリスト
| やりがちなこと | 何が起きるか |
|---|---|
| 設計書から直接 HTML を生成させる | 画面ごとに微妙に違うHTMLができ、検査もできない |
| 生成された画面を人が「ちょっと」直す | 次の再生成で消える。生成器のバグも残る |
| 画面ごとに CSS や JS を足す | カタログが崩壊し、目視QAの世界に戻る |
| Excel と IR の両方を編集可能なままにする | どちらが正しいか分からなくなり、全体が信用を失う |
| AIに「完成するまで自分で直して」と丸投げする | 作る役と採点役が同一人物になり、生成物を直して「できました」と言い出す |
小さく始める
いきなり300画面に適用してはいけません。まず次の4つを確かめます。ダメだったときの撤退基準もセットで決めておきます。
- カタログの当たり判定(机上でOK):実際の画面30枚の部品を数えて、8割がカタログ案に収まるか。収まらないなら、この方式自体を再考する
- 垂直スライス:3画面(一覧・編集+確認・完了)が、2週間で IR から動くか
- 変更の往復:IR に項目を1つ足すとテストが赤になり、再生成だけで(手を入れずに)緑に戻るか
- 作り手:生成器を設計できる人が1人いるか
この4つが通れば、あとは画面の数を増やすだけです。
まとめ
- 設計書は構造化データ(IR)にする。IR が正本で、設計書もコードも IR から生成される
- 人間用の情報(業務説明・備考)も
docOnlyとして IR に残す。捨てない - 画面は、閉じた UIカタログの部品を組み合わせて 決定論的に 生成する。生成器はAIではなくプログラム
- 生成されたコードは絶対に直接直さない。失敗したら原因を4分類して、IR・生成器・カタログのどれかを直して再生成する
- 業務ロジックとの境界は ポート で引く。備考の業務ルールは、ポートの Javadoc とテスト骨子として引き継ぐ
- 回す仕組みは CI で十分。AIは「IRを書く相棒」「生成器の修理」「一度きりの移行」の3か所に絞る
生成AIを最大限生かす、と聞くとAIに多くを任せる話に見えますが、実際は逆でした。同じ入力から同じものが出る土台を作り、AIの判断が必要な場所を絞り込むこと。 それが、人が極力手を出さずに回り続ける開発プロセスの正体だと思います。