🐔

【Github Copilot】設計書があるなら、全部Copilotに実装させよう

に公開4

ウォーターフォールモデルに則った大規模開発で設計書があるなら、全部Copilotに実装させよう!をコンセプトに、どうすれば設計書をインプットに、Github Copilotが大規模開発の品質に沿ったコードを生成してくれるか、の完成版です。前回の記事では途中経過でしたが、自分なりに方法が確立したので結果を共有します。

このドキュメントでは、現時点での設計書からCopilotを用いて実装を生成する手順の紹介、使用するツールの紹介、工夫したポイントについて記載します。

結論

で、本当に全部Copilotに実装させられるの?実装スピードはあがるの?と気になる方もいると思うので結論を先に書きます。

Q:本当に全部Copilotに実装させられるの?
A:完璧なものは無理でした。Copilotに実装させて間違った部分を人間が直す必要があります。今回紹介する方法だと、体感で8割程の正解コードを生成してきます。

Q:実装スピードはあがるの?
A:中堅以上のエンジニアで普段から画面開発を担当している方はあまり生産性は上がらないと思います。今回の手法で、テストの実装無しで検索⇒詳細画面の2画面を実装するのに約4時間かかりました。

Q:全部AIに任せられるなら、人間は何もわからなくてもいいの?
A:無理です。設計書をしっかり読みどのように動作すべきかを理解し、バグを解決するためにコードを理解する必要があります。ただ、for文やif文など細かいコードを書けなくても問題なくなったなとは感じます(読める必要はあります)。

こんな人に読んでほしい

  • Github Copilotしか使えない環境にある方
  • Github Copilotをコードの説明や、単純なメソッド生成をさせる程度にしか使用していない方
  • 期待したコードが生成されず、使わなくなってしまった方

前提

今回生成対象とするのは業務レイヤの実装です。WebアプリケーションでのMVCに該当します。システム共通部品や、プロジェクト自体の構築は含まれていません。
既にサンプルが動作する程度のプロジェクトと、共通部品、画面レイアウト(HTML)は実装済みであることを想定しています。

また、検証には設計と実装が揃っている以下のリポジトリを使用しました。今回紹介するInstruction fileやPrompt filesは下記プロジェクトのWeb画面アプリケーション(Springフレームワーク)実装用にチューニングしています。
https://github.com/Fintan-contents/spring-sample-project

今回紹介する方法にはAgentの利用は含まれていません。

環境

  • VSCode
  • Github Copilot Chat(VSCode拡張機能)
  • Markdown Preview Mermaid Support(VSCode拡張機能)

Github copilotではClaude 4 Sonnetを使用します。

VSCodeの設定

github.copilot.chat.codesearch.enabledを有効化します。これを有効化するかしないかで生成されるコードの品質が大きく変わります。

Github Copilot Chatの機能紹介

手順について記載する前に、今回の手法で活用するGithub Copilot Chatの機能について説明します。

Custom instructions

カスタムインストラクションはすべてのチャットリクエストに自動で組み込まれます。カスタムインストラクションに共通のガイドラインやルールを記述することで、特定のコーディング手法やテクノロジースタックに適した応答を得ることができます。

また、インストラクションファイルの先頭にapplyToを設定することで、特定のファイルのコーディング時にのみ適用させることが可能です。
以下の例では、Controller.javaで終わるファイルがチャットに含まれている場合のみ読み込まれます。

---
applyTo: "**/*Controller.java"
---

# コントローラークラスの実装ガイド
・
・
・

注意点としては、Controller.javaで終わるファイルをAIが新規作成する場合は自動で適用されない点です。新規作成する場合は手動でチャットに含めましょう。

.github\instructionsフォルダ内に*.instructions.mdというファイル名で作成することでカスタムインストラクションファイルとみなされます。
もしくはVSCodeの設定ファイル(settings.json)に以下のように設定することができます。

"github.copilot.chat.codeGeneration.instructions": [
    {
        "file": "./web/docs/architecture.md"
    },
    {
        "file": "./web/docs/translation-table.md"
    }
],

https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions

prompt file

プロンプトファイルを作成しておくことで、チャット欄に似たようなプロンプトを毎回コピペする手間を省きます。また、チーム内でのプロンプトの共有も可能にします。
.github\promptsフォルダ内に*.prompt.mdというファイル名で作成することでプロンプトファイルとみなされます。
チャット内では/ + ファイル名で呼び出すことが可能です。例えばtest.prompt.mdというファイル名のプロンプトはチャット内で/testと入力し、エンターキーを押すことでファイル内に記載されているプロンプトが実行されます。

またプロンプトファイルには、以下のように実行する際に使用するModeとAI Modelを指定することもできます。

---
mode: 'ask'
model: Claude Sonnet 4
description: '実装計画のゴールを作成するためのプロンプトテンプレート'
---

渡した設計書を基に、段階的な実装を行うためのゴール分割されたタスクリストを作成してください。

## 要求事項:
1. 各ゴールは独立して実装・テスト可能な単位とする
・
・
・

プロンプトには変数も設定することができます。以下のプロンプトファイルではチャット入力からgoalという変数を受け取っています。実行するときは/プロンプト名 goal=ゴール1と使用します

---
mode: 'edit'
model: Claude Sonnet 4
description: '実装を依頼するプロンプト'
---

# 指示
実装計画の${input:goal}のみを忠実に実装してください。
実装結果はChatに表示するのではなく、ファイルを直接編集してください。
HTMLファイルは既存のレイアウトは変更せず、Thymeleafの構文だけを追加してください。

プロンプトファイル内にMarkdownのリンクの記法で他ファイルを参照することで、プロンプト実行時にリンク先のファイルが読み込まれます。(※askモードでは読み込まれますが、editモードでは読み込まれているところを観測できませんでした)

---
mode: 'edit'
model: Claude Sonnet 4
description: '実装を依頼するプロンプト'
---
・
・
・
# リファレンス 
- [DBスキーマ](../../web/docs/schema.sql)
    DBの操作を実装する際はDBスキーマを参考にすること
- [HTMLの実装ガイド](../../.github/instructions/html.instructions.md)
    HTMLの実装の参考にすること
・
・
・

https://code.visualstudio.com/docs/copilot/copilot-customization#_prompt-files-experimental

Codebase

Codebaseとはユーザープロンプトに基づいてコードベース検索を実行し、関連するコードをコンテキストとしてチャットプロンプトに追加するツールです。
チャット欄に#codebaseと入力することで利用できます。

https://code.visualstudio.com/docs/copilot/reference/workspace-context

上下キー

Copilot Chatの入力欄で↑キーを押すことで、過去の命令を遡ることができます。
同じ命令を何度も使うので、↑キーを活用しましょう。

準備

Instruction fileの用意

以下の内容が記載されたカスタムインストラクションファイルを用意します。

  • プロジェクトのアーキテクチャ
    フォルダ構成、レイヤー構造、クラスの責務配置、使用ライブラリについて記載します。Copilotはこの情報を参照して実装計画を作成し、実装します。

  • 英単語表
    今回、日本語の設計書を使用するためクラス名、メソッド名、フィールド名はAIが設計書から適切な名前を推測して決定します。この時に単語表がないと、AIによる命名に揺れが発生します。例えば「顧客」という単語をclientと英訳するときもあれば、customerと英訳する場合もありコードの可読性が下がります。

  • 実装ルール
    Java、HTML、JavaScriptなどの実装ルールについて記載します。長くなりすぎず、かつ必要なものは洩れなく記載するようにしましょう。最初から完璧なものを作成するのは難しいので、AIによるコード生成を繰り返しながらこの実装ルールも成長させていくのが良いと思います。
    また、実装例を載せる場合はimport文も含めて記載しましょう。AIはimport文間違いがちです。

prompt fileの用意

今回の手順では以下のプロンプトを使用します。詳細は手順の紹介に記載します。

設計書のテキスト化

Copilotに設計書を渡すために、まず設計書をテキストファイルに変換する必要があります。
Office系文書の場合は以下の方法でテキストファイルに変換することができます。

テキストに変換できたらソースコードと同じワークスペース内にて管理します。

さらに、実装に必要な情報は処理が記載されている設計書に集約しましょう。例えば今回使用するspring-sample-projectのWeb画面のシステム設計書には画面のURLや、呼び出すAPIのインタフェース、テーブル定義が記載されていません。別の設計書に記載されていますので、必要な部分をコピーして貼り付けましょう。

手順の紹介

手順の全体像は以下の通りです。

1. GOALを計画

この手順ではステップバイステップでAIと一緒に機能を完成させていきます。
1つの取引(入力⇒確認⇒完了、検索⇒詳細など)に対してどのようなステップで実装していくかを考えます。
今回は画面の実装なので、打鍵確認しやすい順序でGOALを出力するようチューニングしています。APIやバッチの場合はTDDしやすい順序でGOALを出力させるほうが使いやすいでしょう。

GOALの作成にはplan-goal.prompt.mdを使用します。
設計書とcodebaseを追加して実行します。画像は検索画面と詳細画面を実装する場合の例です。mdファイルが設計書です。

実行すると以下のような結果が出力されます。

出力されたGOAL

GOAL

GOAL 1: プロジェクト検索画面の基本レイアウトとルーティング設定

  • 完了条件:
    • /project/search にアクセスして検索条件入力フォームが表示される
    • 事業部・部門のプルダウン、各種入力項目、検索ボタンが配置されている
    • 検索結果一覧領域(空のテーブル)が表示される
    • フォームバインディングはこの時点では不要

GOAL 2: 事業部プルダウンの初期表示とデータ取得

  • 完了条件:
    • 画面表示時に事業部プルダウンに上位組織IDがNULLの組織データが表示される
    • 組織テーブルからのデータ取得がログで確認できる
    • プルダウンの先頭にブランクオプションが表示される
    • 部門プルダウンは初期状態で非活性または空の状態になっている

GOAL 3: コード値マスタ連携(PJ種別・PJ分類のチェックボックス表示)

  • 完了条件:
    • PJ種別チェックボックスにC0300001のコード値が表示される
    • PJ分類チェックボックスにC0200001のコード値が表示される
    • 各コード値の名称が正しく表示される

GOAL 4: 事業部選択時の部門リスト動的更新機能

  • 完了条件:
    • 事業部を選択すると非同期で /project/departments にリクエストが送信される
    • 選択された事業部配下の部門がプルダウンに表示される
    • 部門プルダウンの先頭にブランクオプションが表示される

GOAL 5: 検索条件の単項目バリデーション機能

  • 完了条件:
    • 各項目のドメインバリデーションが動作する(日付形式、金額形式など)
    • バリデーションエラー時に適切なエラーメッセージが表示される
    • エラー発生時に後続処理が停止することが確認できる

GOAL 6: 検索条件の相関バリデーション機能

  • 完了条件:
    • 売上高FROM-TO、開始日FROM-TO、終了日FROM-TOの大小関係チェックが動作する
    • 相関バリデーションエラー時に適切なエラーメッセージが表示される
    • エラー発生時に検索処理が実行されないことが確認できる

GOAL 7: プロジェクト検索処理とデータ表示

  • 完了条件:
    • 検索ボタン押下時にプロジェクトテーブルから条件に応じたデータが取得される
    • 検索結果が一覧テーブルに表示される(事業部・部門・PJ種別・PJ分類・PM・売上高・開始日・終了日)
    • 検索結果なしの場合は適切なメッセージが表示される
    • 検索処理の実行とデータ取得がログで確認できる

GOAL 8: 検索結果の表示形式とソート機能

  • 完了条件:
    • 売上高が3桁区切りのカンマ形式で表示される
    • 日付がyyyy/MM/dd形式で表示される
    • プロジェクトID昇順でソートされて表示される
    • PJ種別・PJ分類がコード値名称で表示される

GOAL 9: ページング機能

  • 完了条件:
    • 検索結果が複数ページに分割表示される
    • ページネーションリンク(<<、<、ページ番号、>、>>)が動作する
    • ページ遷移時に適切なデータが表示される

GOAL 10: プロジェクト詳細画面の基本表示

  • 完了条件:
    • PJ名リンクをクリックすると /project/detail に遷移する
    • プロジェクトIDがパラメータとして渡される
    • 詳細画面で事業部・部門・PJ名・PJ種別・PJ分類・売上高・顧客・PM・PL・開始日・終了日・備考が表示される
    • フォームバインディングはこの時点では不要

GOAL 11: プロジェクト詳細画面のデータ取得と表示

  • 完了条件:
    • プロジェクトIDに基づいてプロジェクト情報が取得される
    • 組織テーブルとの結合で事業部・部門名が表示される
    • 存在しないプロジェクトIDの場合は汎用エラー画面が表示される

GOAL 12: 顧客管理システムAPI連携

  • 完了条件:
    • 顧客IDをパラメータとして顧客詳細取得APIが実行される
    • 取得した顧客名が詳細画面に表示される
    • API連携エラー時の適切なエラーハンドリングが動作する

GOAL 13: 詳細画面からの画面遷移機能

  • 完了条件:
    • 「戻る」ボタンで検索画面に戻り、検索条件と検索結果が保持される
    • 「更新」ボタンでプロジェクト更新画面に遷移する
    • 各画面遷移時にパラメータが適切に渡される

出力されたものを見て、変更したい部分があれば変更して任意のファイルに保存してください。たとえば上の出力に対しては以下の変更を加えました。

  • GOAL2とGOAL3を入れ替え
  • GOAL10の「詳細画面で事業部・部門・PJ名・PJ種別・PJ分類・売上高・顧客・PM・PL・開始日・終了日・備考が表示される」が検索した詳細情報を表示しろ、と言っているようにも見えるので単に「詳細画面が表示される」に変更
  • GOAL10で前画面からプロパティが引き継がれているか確認したかったので「- 前画面から引き継いだプロジェクトIDがログで確認できる」を追加

このタイミングで注意深く出力結果を確認して修正する必要はありません。「手順3. GOALのための実装計画」の各GOAL実装のタイミングで変更しても問題ありません。

「フォームバインディングはこの時点では不要」と出力させているのは、これがないとGOAL1の段階でフォームバインディングを実装しちゃうためです。

なぜGOALに分割するのか

人間がデバックしやすいようにするためです。
一括で実装を生成させることもできますが、完璧に動作する実装は絶対にでてきません。エラーで画面が表示されないことのほうが多いです。その際、大量のコードをデバックするのはつらいです。GOALごとに実装させることで問題を特定しやすくします。
あとGOALが細かいほうがやる気が出る気がします。

2. クラスとシーケンスの設計

クラスとシーケンスを設計します。

クラスとシーケンスの設計にはplan-sequence.prompt.mdを使用します。
新しいチャットセッションを作成し、設計書とcodebaseを追加して実行します。画像は検索画面と詳細画面を実装する場合の例です。mdファイルが設計書です。

実行すると以下のような結果が出力されます。

出力されたクラスとシーケンス

1. 概要

プロジェクト詳細画面は、プロジェクトIDをパラメータとして受け取り、該当するプロジェクトの詳細情報を表示する機能です。組織情報、プロジェクト基本情報、顧客情報を取得し、整形して画面に表示します。また、戻るボタンでプロジェクト検索画面に戻り、更新ボタンでプロジェクト更新画面に遷移できます。

2. クラス図

classDiagram
    namespace com.example.web.project.controller {
        class ProjectSearchController {
            +detail(projectId: Integer, model: Model): String
            +back(projectSearchForm: ProjectSearchForm): String
        }
    }

    namespace com.example.web.project.service {
        class ProjectSearchService {
            +getProjectDetail(projectId: Integer): ProjectDetailDto
        }
    }

    namespace com.example.web.project.mapper {
        class ProjectSearchMapper {
            +selectProjectDetailByProjectId(projectId: Integer): ProjectDetailDto
        }
    }

    namespace com.example.web.project.dto {
        class ProjectDetailDto {
            +Integer projectId
            +String projectName
            +String projectType
            +String projectClass
            +LocalDate projectStartDate
            +LocalDate projectEndDate
            +Integer organizationId
            +String organizationName
            +String divisionName
            +Integer clientId
            +String clientName
            +String projectManager
            +String projectLeader
            +Integer sales
            +String note
            +Long versionNo
        }
    }

    namespace com.example.web.project.form {
        class ProjectSearchForm {
            +Integer divisionId
            +Integer organizationId
            +String[] projectType
            +String[] projectClass
            +String projectName
            +String clientName
            +LocalDate projectStartDateFrom
            +LocalDate projectStartDateTo
            +LocalDate projectEndDateFrom
            +LocalDate projectEndDateTo
            +Integer sales
            +String projectManager
        }
    }

    namespace com.example.web.common.client {
        class ClientApiClient {
            +getClientDetail(clientId: Integer): ClientDetailDto
        }
    }

    namespace com.example.web.common.client.dto {
        class ClientDetailDto {
            +Integer clientId
            +String clientName
            +String industryCode
            +Long versionNo
        }
    }

    namespace com.example.web.project.helper {
        class ProjectViewHelper {
            +getOrganizationName(organizationId: Integer): String
            +getProjectTypeName(projectType: String): String
            +getProjectClassName(projectClass: String): String
            +formatSales(sales: Integer): String
            +formatDate(date: LocalDate): String
        }
    }

    namespace com.example.common.generated.model {
        class Project {
            +Integer projectId
            +String projectName
            +String projectType
            +String projectClass
            +LocalDate projectStartDate
            +LocalDate projectEndDate
            +Integer organizationId
            +Integer clientId
            +String projectManager
            +String projectLeader
            +Integer sales
            +String note
            +Long versionNo
        }
    }

    ProjectSearchController --> ProjectSearchService : uses
    ProjectSearchController --> ProjectViewHelper : uses
    ProjectSearchService --> ProjectSearchMapper : uses
    ProjectSearchService --> ClientApiClient : uses
    ProjectSearchMapper --> ProjectDetailDto : returns
    ClientApiClient --> ClientDetailDto : returns
    ProjectSearchService --> ProjectDetailDto : returns

3. シーケンス図

初期表示イベント

sequenceDiagram
    participant Client
    participant ProjectSearchController
    participant ProjectSearchService
    participant ProjectSearchMapper
    participant ClientApiClient
    participant ProjectViewHelper
    participant Model

    %% ① クライアントがプロジェクト詳細画面にアクセス
    Client ->> ProjectSearchController: (1) GET /project/detail?projectId={projectId}

    %% ② プロジェクト詳細情報を取得
    ProjectSearchController ->> ProjectSearchService: (2) getProjectDetail(projectId)

    %% ③ プロジェクト詳細をデータベースから取得
    ProjectSearchService ->> ProjectSearchMapper: (3) selectProjectDetailByProjectId(projectId)
    ProjectSearchMapper -->> ProjectSearchService: (4) ProjectDetailDto

    alt (4) プロジェクトが見つからない場合
        ProjectSearchService -->> ProjectSearchController: (5) BusinessException (汎用エラー)
        ProjectSearchController ->> Client: (6) 汎用エラー画面
    else (4) プロジェクトが見つかった場合
        %% ⑤ 顧客情報を外部APIから取得
        ProjectSearchService ->> ClientApiClient: (7) getClientDetail(clientId)
        ClientApiClient -->> ProjectSearchService: (8) ClientDetailDto

        %% ⑥ 顧客名をプロジェクト詳細DTOに設定
        ProjectSearchService -->> ProjectSearchController: (9) ProjectDetailDto (顧客名含む)

        %% ⑦ ビューヘルパーでコード値変換とフォーマット
        ProjectSearchController ->> ProjectViewHelper: (10) getProjectTypeName(projectType)
        ProjectViewHelper -->> ProjectSearchController: (11) プロジェクト種別名

        ProjectSearchController ->> ProjectViewHelper: (12) getProjectClassName(projectClass)
        ProjectViewHelper -->> ProjectSearchController: (13) プロジェクト分類名

        ProjectSearchController ->> ProjectViewHelper: (14) formatSales(sales)
        ProjectViewHelper -->> ProjectSearchController: (15) フォーマット済み売上高

        ProjectSearchController ->> ProjectViewHelper: (16) formatDate(projectStartDate)
        ProjectViewHelper -->> ProjectSearchController: (17) フォーマット済み開始日

        ProjectSearchController ->> ProjectViewHelper: (18) formatDate(projectEndDate)
        ProjectViewHelper -->> ProjectSearchController: (19) フォーマット済み終了日

        %% ⑧ モデルに詳細情報を設定
        ProjectSearchController ->> Model: (20) addAttribute("projectDetail", projectDetailDto)
        ProjectSearchController ->> Model: (21) addAttribute各種フォーマット済み情報

        %% ⑨ プロジェクト詳細画面を表示
        ProjectSearchController -->> Client: (22) project/detail.html
    end

戻るイベント

更新イベント

出力結果を手順1の結果を保存したファイルに保存します。
クラスとシーケンスは時間をかけてレビューしてください。後々楽になるよう気合をいれてレビューしましょう。
確認するポイントは以下です。

  • アーキテクチャのレイヤ構造とあっているか
  • クラス名は期待したものか
  • クラスの参照順序はあっているか
    ↑の例だと、ControllerからViewHelperを参照していますが、実際はHTMLがViewHelperを参照すべきです
  • FormやModelのフィールドの型はあっているか
    型を正しく推論させるためにも設計書にはテーブル定義を含めたほうが良いです
  • 既存のメソッドを使用しているか
    ↑の例だとProjectViewHelper#getProjectTypeNameを定義してますが、これは他のクラスで実装済みです。

手動でもAIを駆使してもいいので間違いを直します。

ここまで終わるとこのようなファイルが出来上がります。

なぜクラスとシーケンスを設計させるのか

クラス図やシーケンス図を生成させる理由は、1つは人間が理解しやすいため。もう一つはCopilotの実装に一貫性を持たせるためです。後述の実装では各GOALごとにチャットのセッションを切り替えます。そのため、Copilotは前GOALで何を実装したか覚えていません。クラス図やシーケンス図がないとCopilotはそのセッション内ででっち上げた(実際には実装されていない)クラスやメソッドを呼び出す実装を出力します。
セッションを分けなければいいと思うかもしれませんがセッションの履歴が長くなると、やはり最初のほうに実装したメソッドは忘れてしまいます。
また、クラス図には必ずパッケージ名を出力させます。そうすることでAIがimport文を正しく出力してくれます。

3. GOALのための実装計画

続いて、GOALを達成するための実装計画を作成します。

GOALのための実装計画にはplan-step.prompt.mdを使用します。
新しいチャットセッションを作成し、手順2で作成したファイル、設計書、codebase、各種実装ガイドを追加して実行します。画像はGOAL1の実装計画を作成する場合です。

実行すると以下のような計画が出力されます。

GOAL1の実装計画

GOAL 1 詳細実装計画

GOAL 1: プロジェクト検索画面の基本レイアウトとルーティング設定

  • 完了条件:
    • /project/search にアクセスして検索条件入力フォームが表示される
    • 事業部・部門のプルダウン、各種入力項目、検索ボタンが配置されている
    • 検索結果一覧領域(空のテーブル)が表示される
    • フォームバインディングはこの時点では不要

ステップ 1: プロジェクト検索コントローラーの作成

  • 編集対象ファイル: web/src/main/java/com/example/web/project/controller/ProjectSearchController.java(新規作成)
  • 目的: プロジェクト検索画面へのルーティングを設定し、初期表示処理を実装する
  • 内容:
    • ProjectControllerと同じパッケージに新しいコントローラーを作成
    • @Controller@RequestMapping("/project/search")アノテーションを付与
    • search(ProjectSearchForm form)メソッドを作成し、@GetMapping/project/searchパスにマッピング
    • メソッドではProjectSearchFormオブジェクトをバインドし、"project/search/index"テンプレートを返却
  • ポイント: 既存のProjectControllerを参考にし、同様のアノテーション構成にする

ステップ 2: プロジェクト検索フォームクラスの作成

  • 編集対象ファイル: web/src/main/java/com/example/web/project/form/ProjectSearchForm.java(新規作成)
  • 目的: 検索条件の入力値を保持するフォームクラスを作成する
  • 内容:
    • ProjectCreateFormと同じパッケージに新しいフォームクラスを作成
    • 以下のフィールドを定義:
      • Integer divisionId(事業部ID)
      • Integer organizationId(部門ID)
      • List<String> projectType(PJ種別)
      • List<String> projectClass(PJ分類)
      • Integer salesFromInteger salesTo(売上高範囲)
      • LocalDate projectStartDateFromLocalDate projectStartDateTo(開始日範囲)
      • LocalDate projectEndDateFromLocalDate projectEndDateTo(終了日範囲)
      • String projectName(プロジェクト名)
    • 各フィールドにgetterとsetterを実装
  • ポイント: バリデーションアノテーションは後のゴールで追加するため、この段階では基本的なJavaBeansとして実装

ステップ 3: プロジェクト検索画面テンプレートの作成

  • 編集対象ファイル: index.html(新規作成)
  • 目的: プロジェクト検索画面のHTMLテンプレートを作成する
  • 内容:
    • templates/project/search/index.htmlを参考に、完全なThymeleafテンプレートを作成
    • 既存のHTMLの静的な要素を以下のようにThymeleaf化:
      • <form> タグにth:object="${projectSearchForm}"を追加
      • 事業部・部門のselectタグにth:field="*{divisionId}"th:field="*{organizationId}"を追加
      • PJ種別・PJ分類のcheckboxに適切なth:field属性を追加
      • 各入力項目にth:field属性を適切に設定
      • 検索ボタンのformにth:action="@{/project/search/search}"method="post"を設定
    • 検索結果一覧領域は現状のまま静的なテーブルとして残す
  • ポイント: html.instructions.mdの規約に従い、適切なBootstrapクラスとThymeleaf属性を使用

ステップ 4: コントローラーのルーティング確認とテスト

  • 編集対象ファイル: なし(動作確認)
  • 目的: 作成したコントローラーとテンプレートが正しく動作することを確認する
  • 内容:
    • アプリケーションを起動し、http://localhost:8080/project/searchにアクセス
    • 検索条件入力フォームが表示されることを確認
    • 各入力項目(プルダウン、チェックボックス、テキスト入力、日付入力)が配置されていることを確認
    • 検索ボタンが表示されることを確認
    • 検索結果一覧の空のテーブルが表示されることを確認
  • ポイント: この段階ではフォームの機能は動作しなくても良く、画面の表示のみ確認する

出力結果を手順1のファイルに保存します。ざっと確認してGOALに対して過剰に実装していないか、もしくはGOALに対して不足していることがないか確認しましょう。上の例では「フォームバインディングはこの時点では不要」と言っているにもかかわらず、フォームバインディングの実装を提案してきてますね(th:field ~の部分)。
GOALに対して過剰に実装している部分を修正するかはどうかは、好みの部分が大きいです。修正しなかったとしても、そこまで実装スピードに影響が出ることはないと思います。
「確認とテスト」のステップもAIが実施するわけではないので、無くて大丈夫です。コンテキスト量削減のため毎回消しています(残していても問題はない気がします)。

また、この時点で全く想定外のコードが出力された場合はアーキテクチャファイルや開発ガイドを見直しましょう。そちらを修正したのち、再度実装計画を出力させてください。
さらに、この時点でクラス名やメソッド名のミスに気づいたらクラス図やシーケンス図を修正して、再度実装計画を出力させてください。
出力の元になった情報を直すことで、AIが同じ間違いをしにくくなります。

ここまで終わるとこのようなファイルが出来上がります。

4. 実装

GOAL2の実装計画に入る前に、GOAL1の計画を実際に実装します。

実装にはimpl.prompt.mdを使用します。
新しいチャットセッションを作成し、実装計画、各種実装ガイド、更新対象のファイル、を追加して実行します。画像はGOAL1を実装する場合の例です。実装時はcodebaseは指定しません、codebaseを指定してしまうと時間がかかり再実行する気が失せます。

実行すると以下のように実装計画の通り実装してくれます。

Controllerクラス

Formクラス

HTML

フォームバインディングいらないって言ってるのに結局実装されちゃいましたね。

出力後に実装計画のミスに気づいたら、恐れず全ての実装を破棄し、実装計画を修正してから再度実装させるのが良いです。

出力されたものを全て受け入れて、動作確認をします。
動作確認でエラーが出た場合、実装と同じセッションのチャットにエラーを張り付けると原因特定の確立が上がります。また、エラーを読んだ時点で原因がわかる場合はAIに頼らず、自分で直してしまったほうが早いです。

更新対象のファイルを含む理由

実装時に更新対象のファイルを添付しないと、不要な変更が多分に入り込みます。
以下は、GOAL1の実装チャットにindex.htmlを添付しなかった例です。

おそらく、Copilot ChatのEditモードは編集前に既存のファイルを読み込んでいないのでしょう。

実装をチャットに出力してきたら即中断

AIに実装をさせていると、以下のようにファイルではなくチャットに実装を出力してくることがあります。この場合は即座に生成を中止し、新しいチャットセッションを開き全く同じプロンプトを実行(↑キーを押して実行)しましょう。

5. コミット

動作確認がおわったら成果物を全てコミットします。
VSCodeにはコミットコメントを自動生成してくれる機能があり、便利です。

https://youtu.be/vD0mPvh_II0

次のGOALの【手順3.GOALのための実装計画】を実施します。
全ての実装が終わるとこのようなファイルが出来上がります。

まとめ

この手順でスムーズに実装するためのポイントをまとめます。

  • 開発ガイドのコード例にはimport文も記載する
  • 処理詳細が記載された設計書に実装に必要な設計情報を集約する
  • クラス図とシーケンス図はしっかりレビュー
  • クラス図にはパッケージ名も出力する
  • 実装プロンプトには更新対象ファイルをチャットに含める
  • 全く想定外の実装をしたらガイドを見直す
  • クラス名や呼び出すメソッドを間違えていたらクラス図、シーケンス図を直す

最後に

インストラクションファイルが出そろって、AIがいい感じに実装してくれるようになるのは大体3機能一緒に開発してからです。それまでは自分で実装したほうが早いです。なので、似た方式の機能が少ないプロダクトでは使えないですね。
 今回紹介した方法では、あるGOALは自分で実装して、あるGOALはAIに実装させるという開発も可能です。重要なのはクラス図とシーケンス図なので、この二つが実装と合致していればAIも混乱することなく続きを実装してくれます。数行程度の実装は自分で実装してしまったほうが効率的です。
 細かい待ち時間が多く、手は空きますが他のタスクをやる余裕はないといった状況のため、生産性の向上はそこそこといった結果になりました。ただ自分のように画面の実装が久しぶりでふんわり覚えている、程度の人には有用だと思います。
 検証をしている間にAgentモードが正式リリースされました。Agentモードを使用すれば、今回紹介した方法もさらに簡単になると思うので、今後はAgentを使用した開発も検証してみたいです。

個人的にはマルチタスクにならない、これくらいの使い方が性に合っている

Discussion

AhoxaAhoxa

非常に参考になりました!
AgentMode編も楽しみにしています!

icezuki7878icezuki7878

カスタムインストラクションって結構無視される感じある
たまーに思い出したかのように守ってくれるけど…

ayaaya

そうですね。なので、段階的に計画⇒実装をさせて、大きな間違いを計画段階で検知してつぶしてる感じです。実装も細かい部分をまちがえていたりするので、やはり人間がインストラクション理解してレビューしてあげないといけないと思ってます。計画が完璧なら実装は人間のチェック不要、となると楽でいいんですが。