【Github Copilot】設計書があるなら、全部Copilotに実装させよう(途中経過)
ウォーターフォールモデルに則った大規模開発で設計書があるなら、全部Copilotに実装させよう!をコンセプトに、どうすれば設計書をインプットに、Github Copilotが大規模開発の品質に沿ったコードを生成してくれるか、を日々模索しています。
まだまだ課題はあるものの、少しでも開発の役に立てばと思い途中経過を公開します。
このドキュメントでは、現時点での設計書からCopilotを用いて実装を生成する手順の紹介、使用するツールの紹介、工夫したポイント、現時点での課題について記載します。
こんな人に読んでほしい
- Github Copilotしか使えない環境にある方
- Github Copilotをコードの説明や、単純なメソッド生成をさせる程度にしか使用していない方
- 期待したコードが生成されず、使わなくなってしまった方
前提
今回生成対象とするのは業務レイヤの実装です。WebアプリケーションでのMVCに該当します。システム共通部品や、プロジェクト自体の構築は含まれていません。
既にサンプルが動作する程度のプロジェクトと、共通部品は実装済みであることを想定しています。
また、自身の検証には設計と実装が揃っている以下のリポジトリを使用しました。今回紹介するInstruction fileやPrompt filesは下記プロジェクトのWeb画面アプリケーション(Springフレームワーク)実装用にチューニングしています。
準備
環境
- VSCode
- Github Copilot Chat(VSCode拡張機能)
- Markdown Preview Mermaid Support(VSCode拡張機能)
Github copilotではClaude 3.7 SonnetとClaude 3.7 Sonnet Thinkingを使用します。
Instruction file
VSCodeはInstruction fileを用意することで、すべてのチャットリクエストに共通の情報を含めることができます。
今回は以下のInstruction fileを用意し、設定しましょう。
- プロジェクトのアーキテクチャ
フォルダ構成、レイヤー構造、クラスの責務配置、使用ライブラリについて記載します。Copilotはこの情報を参照して実装計画を作成し、実装します。サンプル - 実装ルール
Java、HTML、JavaScriptなどの実装ルールについて記載します。長くなりすぎず、かつ必要なものは洩れなく記載するようにしましょう。最初から完璧なものを作成するのは難しいので、AIによるコード生成を繰り返しながらこの実装ルールも成長させていくのが良いと思います。サンプル(3.5 Formクラスの実装規約、3.6 Controllerクラスの実装規約、12. Thymeleafヘルパー機能部分が該当) - 英単語表
今回、日本語の設計書を使用するためクラス名、メソッド名、フィールド名はAIが設計書から適切な名前を推測して決定します。この時に単語表がないと、AIによる命名に揺れが発生します。例えば「顧客」という単語をclientと英訳するときもあれば、customerと英訳する場合もありコードの可読性が下がります。サンプル
ファイルを用意したら以下のように設定しましょう。基本的にこれらの内容はプロジェクトごとに変わるものですので、グローバル設定ではなくプロジェクト設定として保存することを推奨します。(設定方法はバージョンにより変わる可能性があるので、最新の公式ドキュメントを参照してください)
{
"github.copilot.chat.codeGeneration.instructions": [
{
"file": "./web/docs/architecture.md"
},
{
"file": "./web/docs/translation-table.md"
}
],
}
参考: Customize chat responses in VS Code > Instruction files
Prompt files
今回ご紹介する手順で頻出するプロンプトはプロンプトファイルとしてVSCodeに登録しましょう。これによりコマンドでプロンプトを呼び出せるようになります。
今回使用するのは以下の二つです。これらのプロンプトの役割については後述します。
ダウンロードして.github/prompts
フォルダに保存してください。
これによりChat画面から/ + プロンプト名
で呼び出すことができます。
参考:Customize chat responses in VS Code > Prompt files (experimental)
手順
Copilotに実装させる手順は以下の通りです。アイコンがついてる部分でCopilotを使用します。
1. 設計書をテキストファイルに変換
Copilotに設計書を渡すために、まず設計書をテキストファイルに変換する必要があります。
Office系文書の場合は以下の方法でテキストファイルに変換することができます。
- excel-reader(VSCode拡張機能)
- ファイルの内容をコピーして
以下をMarkdownに変換してください
というメッセージと共にGithub Copilotに張り付ける - Docling
テキストに変換できたらソースコードと同じワークスペース内にて管理します。
2. 設計書をレビュー
続いて、テキストに変換した設計書に実装に必要な情報が十分記載されているかをレビューします。ここで設計に不足があるまま進めてしまうと、AIは実装時に勝手に仕様を決めて実装してしまいます。
例えば今回使用するspring-sample-projectのWeb画面のシステム設計書には画面のURLや、呼び出すAPIの詳細が記載されていません。別の設計書に記載されています。また、手順1の設計書をテキストファイルに変換時に、情報が欠損している可能性もあります。
こういったことをCopilotに検知させ、実装に入る前に修正します。
レビューにはreview.prompt.mdを使用します。
- Copilot Chatを開く
- Askモードを選択
- Claude 3.7 Sonnet Thinkingを選択
- レビューしたい設計ファイルをコンテキストに設定する
-
/review
と入力して実行
実行すると以下のようなレビュー結果が得られます。
レビュー結果
プロジェクト登録画面設計書レビュー結果
1. 設計書レビュー結果
十分な項目:
- プロジェクト情報入力項目の詳細定義(項目名、種別、必須有無等)
- 画面イベント(初期表示、部門リスト取得、顧客検索、確認)の基本フロー
- バリデーション処理の基本的な定義
- DBアクセス(検索条件、取得カラム)の詳細
- 画面間の遷移先情報
不足している項目:
- 画面のURL(エンドポイント)情報
- 各ドメインの具体的なバリデーションルールの詳細
- 顧客管理システムAPIのインターフェース仕様
- エラー発生時の画面表示方法と位置
- クライアント側の処理詳細(特に非同期通信部分)
- 画面レイアウト情報(省略されている)
- 前の画面からの遷移情報
改善点:
- 各入力項目のフォーマットや文字数制限を明確にすると実装がスムーズになる
- API通信エラー時の処理と表示メッセージを定義することで堅牢な実装が可能になる
- クライアント側の入力補助機能(カレンダーピッカーの詳細動作等)を明記すると実装者の理解が深まる
- 画面遷移の全体像(前画面からの遷移を含む)を記述するとユーザーの動線が把握しやすくなる
2. 不足項目の詳細
不足している項目: 画面のURL情報
- 現在の記述: 「WA10201/プロジェクト登録」とタイトルのみ記載
-
不足している内容:
- 具体的なエンドポイント(URL)パス
- HTTPメソッド(GETかPOSTか)
-
改善提案:
画面URL: /projects/create HTTPメソッド: GET(表示), POST(確認処理)
不足している項目: ドメインのバリデーションルール詳細
- 現在の記述: 「単項目バリデーション」として「指定ドメインに応じたバリデーション」と記載
-
不足している内容:
- 各ドメイン(プロジェクト名、売上高など)の具体的な入力制限
- 文字数制限、入力可能文字種
- 数値項目の有効範囲
-
改善提案:
【ドメイン別バリデーションルール】 - プロジェクト名: 最大100文字、必須 - 売上高(千円): 整数のみ、0~999,999,999の範囲、カンマ区切り表示 - 顧客ID: 数値のみ、必須 - プロジェクト開始日/終了日: yyyy/MM/dd形式、過去日も将来日も許容、必須 - 備考: 最大1000文字、任意
不足している項目: 顧客管理システムAPIの仕様
- 現在の記述: 「顧客管理システム.顧客詳細取得APIを実行し顧客名を取得する」
-
不足している内容:
- APIのエンドポイント
- リクエストパラメータの形式
- レスポンスの形式と内容
- エラーケースの処理方法
-
改善提案:
【顧客管理システムAPI仕様】 - エンドポイント: /api/customers/{customerId} - HTTPメソッド: GET - リクエストパラメータ: パスパラメータとしてcustomerId(数値) - レスポンス形式: JSON 成功時: { "customerId": 123, "customerName": "○○株式会社", ... } 失敗時: { "errorCode": "E001", "errorMessage": "顧客が存在しません" } - エラー時の処理: エラーメッセージを画面上部に表示し、顧客選択をクリアする
不足している項目: エラー表示の詳細
- 現在の記述: バリデーションエラー発生時は「後続の処理は実施しない」
-
不足している内容:
- エラーメッセージの表示位置
- 複数エラー発生時の表示順序
- フィールドエラーの表示方法
-
改善提案:
【エラー表示仕様】 - バリデーションエラーは対象フィールドの直下に赤字で表示 - 複数エラー発生時は全てのエラーを表示 - 画面上部に全体エラーメッセージも表示 - エラーフィールドは赤枠で強調表示 - フォーカスは最初のエラーフィールドに自動設定
不足している項目: クライアント処理の詳細
- 現在の記述: カレンダー表示や非同期通信について最小限の記載
-
不足している内容:
- カレンダーピッカーの詳細動作
- 非同期通信中の画面状態(ローディング表示など)
- JavaScriptによる入力支援機能
-
改善提案:
【クライアント処理の詳細】 - カレンダーピッカー: フラットピッカーを使用、日本語表示、曜日色分け対応 - 非同期通信中はボタン部分にスピナーを表示し操作を無効化 - 部門選択プルダウン更新中は「ロード中...」と表示 - 売上高入力時は自動的にカンマ区切りに整形 - PM/PL入力欄にはユーザー名オートコンプリート機能を実装
3.設計書を修正
レビュー結果を参照し、不足している内容を設計書ファイルに追記します。
修正時のポイントは以下の通りです。
- 全てのレビュー指摘に対応する必要はありません。実際に自分が実装する場合、他の設計書に情報を探しに行く、と思ったものだけ対応すればよいです。
- 他の設計書から情報を持ってくる場合、関係のある情報だけを設計ファイルに追記しましょう。例えばテーブル定義の内容が不足している、という指摘に対応するとき全てのテーブル定義をコピペするのではなく、該当の設計書に関連するテーブル定義だけをコピペしましょう。これについても
#file:テーブル定義.md から #file:~設計書.md に関連するテーブルの定義だけを抜き出してください
とCopilotに依頼することができます。
4.実装計画を作成
設計書を修正したら、実装計画を作成します。
実装計画の作成にはplan.prompt.mdを使用します。
- Copilot Chatを開き、新しいセッションに切り替える
- Askモードを選択
- Claude 3.7 Sonnet Thinkingを選択
- 実装した設計ファイルをコンテキストに設定する
- ソースコードフォルダをコンテキストに設定する
-
/plan
と入力して実行
以下のような実装計画を作成してくれます。作成した計画を任意のファイルに保存します。
実装計画
プロジェクト登録機能実装計画
1. 概要
プロジェクト登録機能は、プロジェクト管理システムにおいて新規プロジェクトを登録するための機能です。ユーザーは事業部・部門の選択、プロジェクト名や種別、分類などの基本情報を入力し、顧客情報やプロジェクト期間を設定して登録を行います。入力内容は確認画面で確認してから登録される仕組みです。
2. クラス図
3. シーケンス図
初期表示イベント
部門リスト取得イベント(非同期)
顧客検索イベント
確認イベント
4. 実装計画
初期表示イベント
ステップ 1: Organization Mapperの実装
-
編集対象ファイル:
web/src/main/java/com/example/web/project/mapper/OrganizationMapper.java
(新規作成) - 目的: 初期表示イベントのシーケンス図の(3)〜(4)を実装。組織データを取得するマッパーを実装する
-
内容:
-
OrganizationMapper
インターフェイスを作成する - 上位組織IDがNULLの組織(事業部)を取得する
selectDivisions()
メソッドを定義する -
selectDepartmentsByDivision(String divisionId)
メソッドを定義する(部門リスト取得用) -
selectOrganizationById(String organizationId)
メソッドを定義する(組織名取得用)
-
-
活用するクラス・メソッド:
- MyBatisのアノテーションを使用(
@Select
など)
- MyBatisのアノテーションを使用(
-
ポイント:
- SQL文は適切なWHERE条件を設定し、必要なカラムのみを取得する
-
関連ファイル:
com.example.web.project.model.Organization
ステップ 2: Organization Mapper XMLの実装
-
編集対象ファイル:
web/src/main/resources/com/example/web/project/mapper/OrganizationMapper.xml
(新規作成) - 目的: MapperインターフェースのSQLを実装する
-
内容:
-
selectDivisions()
、selectDepartmentsByDivision()
、selectOrganizationById()
のSQLを実装する
-
-
ポイント:
- XMLの命名空間が正しく設定されていることを確認する
-
関連ファイル:
OrganizationMapper.java
ステップ 3: ProjectServiceの実装
-
編集対象ファイル:
web/src/main/java/com/example/web/project/service/ProjectService.java
(新規作成) - 目的: 初期表示イベントのシーケンス図の(2)〜(5)を実装。OrganizationMapperを利用して事業部一覧を取得するサービスを実装する
-
内容:
-
ProjectService
クラスを作成する -
getDivisions()
メソッドを実装し、OrganizationMapperを使って事業部一覧を取得する -
getDepartments(String divisionId)
メソッドを実装する(部門リスト取得用) -
getOrganizationName(String organizationId)
メソッドを実装する(組織名取得用) -
getClientName(String clientId)
メソッドを実装する(外部APIでの顧客名取得用)
-
-
活用するクラス・メソッド:
com.example.web.project.mapper.OrganizationMapper#selectDivisions()
com.example.web.project.mapper.OrganizationMapper#selectDepartmentsByDivision(String)
com.example.web.project.mapper.OrganizationMapper#selectOrganizationById(String)
-
ポイント:
- 必要に応じてキャッシュを検討する
-
関連ファイル:
OrganizationMapper.java
ステップ 4: ProjectCreateFormの実装
-
編集対象ファイル:
web/src/main/java/com/example/web/project/form/ProjectCreateForm.java
(新規作成) - 目的: プロジェクト登録画面の入力項目を保持するフォームクラスを実装する
-
内容:
- 画面項目定義に従って、フォームクラスのフィールドを実装する
- 必要なバリデーション用アノテーションを追加する
- 日付関係のカスタムバリデーションメソッドを実装する
-
活用するクラス・メソッド:
- javax.validationアノテーション(
@NotEmpty
、@Size
など)
- javax.validationアノテーション(
-
ポイント:
- 日付の大小関係チェックなど、複合的なバリデーションは独自メソッドで実装する
- 関連ファイル: なし
ステップ 5: ProjectCreateViewHelperの実装
-
編集対象ファイル:
web/src/main/java/com/example/web/project/helper/ProjectCreateViewHelper.java
(新規作成) - 目的: 初期表示イベントのシーケンス図の(6)〜(7)を実装。画面表示に必要な補助メソッドを提供する
-
内容:
-
ProjectCreateViewHelper
クラスを作成する -
getDivisionOptions()
メソッドを実装し、事業部のSelectOptionリストを作成する -
getProjectTypeOptions()
、getProjectClassOptions()
メソッドを実装する -
getOrganizationName()
、getClientName()
メソッドを実装する
-
-
活用するクラス・メソッド:
com.example.web.project.service.ProjectService#getDivisions()
com.example.web.common.helper.CodeHelper#getSelectOptions(String, String)
com.example.web.common.helper.CodeHelper#getRadioOptions(String, String)
-
ポイント:
- SelectOptionクラスは既存のものを使用する
-
関連ファイル:
CodeHelper.java
、ProjectService.java
ステップ 6: ProjectCreateControllerの実装
-
編集対象ファイル:
web/src/main/java/com/example/web/project/controller/ProjectCreateController.java
(新規作成) - 目的: 初期表示イベントのシーケンス図の(1)〜(13)を実装。画面の初期表示処理を実装する
-
内容:
-
ProjectCreateController
クラスを作成する -
@Controller
アノテーションとURLマッピングを設定する -
index()
メソッドを実装し、初期表示処理を行う - モデルにViewHelperから取得した各種オプションリストを設定する
-
-
活用するクラス・メソッド:
com.example.web.project.helper.ProjectCreateViewHelper#getDivisionOptions()
com.example.web.project.helper.ProjectCreateViewHelper#getProjectTypeOptions()
com.example.web.project.helper.ProjectCreateViewHelper#getProjectClassOptions()
-
ポイント:
- 画面項目の初期値を適切に設定する
-
関連ファイル:
ProjectCreateViewHelper.java
部門リスト取得イベント(非同期)
ステップ 7: コントローラに部門リスト取得メソッドを追加
-
編集対象ファイル:
web/src/main/java/com/example/web/project/controller/ProjectCreateController.java
-
編集対象のメソッド:
getDepartments()
(新規作成) - 目的: 部門リスト取得イベントのシーケンス図の(1)〜(6)を実装。選択された事業部に基づいて部門リストを非同期取得する
-
内容:
-
@GetMapping("/project/create/departments/{divisionId}")
でURLマッピングを設定する -
@PathVariable
で事業部IDを受け取る -
ProjectService
を使って部門リストを取得する - 部門リストをJSON形式で返却する
-
-
活用するクラス・メソッド:
com.example.web.project.service.ProjectService#getDepartments(String)
org.springframework.http.ResponseEntity
-
ポイント:
- クライアントサイドJavaScriptから呼び出される非同期APIとして実装する
-
関連ファイル:
ProjectService.java
顧客検索イベント
ステップ 8: コントローラに顧客検索メソッドを追加
-
編集対象ファイル:
web/src/main/java/com/example/web/project/controller/ProjectCreateController.java
-
編集対象のメソッド:
openClientSearch()
(新規作成) - 目的: 顧客検索イベントのシーケンス図の(1)〜(2)を実装。顧客検索画面へ遷移する
-
内容:
-
@GetMapping("/project/create/client-search")
でURLマッピングを設定する - 顧客検索画面へのリダイレクトURLを返す
-
-
ポイント:
- ポップアップとして開くためのHTML/JavaScriptも実装する必要がある
- 関連ファイル: なし
確認イベント
ステップ 9: コントローラに確認処理メソッドを追加
-
編集対象ファイル:
web/src/main/java/com/example/web/project/controller/ProjectCreateController.java
-
編集対象のメソッド:
confirm()
(新規作成) - 目的: 確認イベントのシーケンス図の(1)〜(15)を実装。入力内容のバリデーションと確認画面への遷移を行う
-
内容:
-
@PostMapping("/project/create/confirm")
でURLマッピングを設定する - フォームのバリデーションを実行する
- エラーがある場合は入力画面に戻る
- 問題なければ、組織名と顧客名を取得してモデルに設定する
- 確認画面を表示する
-
-
活用するクラス・メソッド:
com.example.web.project.service.ProjectService#getOrganizationName(String)
com.example.web.project.service.ProjectService#getClientName(String)
-
ポイント:
- バリデーションエラー時は入力値を保持して入力画面に戻す
- 確認画面のテンプレートパスを正しく指定する
-
関連ファイル:
ProjectService.java
、ProjectCreateForm.java
ステップ 10: 画面テンプレートの実装
-
編集対象ファイル:
web/src/main/resources/templates/project/create.html
(新規作成) - 目的: プロジェクト登録画面のHTMLテンプレートを実装する
-
内容:
- Thymeleafテンプレートを使用して画面レイアウトを実装する
- 各入力フィールドをProjectCreateFormにバインドする
- 事業部プルダウン選択時に部門リストを非同期で取得するJavaScriptを実装する
- 顧客検索ボタンのポップアップ処理を実装する
-
活用するクラス・メソッド:
- Thymeleafの標準タグ
- jQueryなどのJavaScriptライブラリ
-
ポイント:
- バリデーションエラーメッセージの表示処理を適切に実装する
- ラジオボタンやプルダウンの選択肢を動的に表示する
- 関連ファイル: なし
ステップ 11: 確認画面テンプレートの実装
-
編集対象ファイル:
web/src/main/resources/templates/project/confirm.html
(新規作成) - 目的: プロジェクト登録確認画面のHTMLテンプレートを実装する
-
内容:
- Thymeleafテンプレートを使用して確認画面レイアウトを実装する
- 各項目の入力値を表示する
- 戻るボタンと登録ボタンを配置する
-
活用するクラス・メソッド:
- Thymeleafの標準タグ
-
ポイント:
- 入力値をhiddenフィールドに保持して次の処理に引き継ぐ
- 関連ファイル: なし
ステップ 12: JavaScript実装
-
編集対象ファイル:
web/src/main/resources/static/js/project/create.js
(新規作成) - 目的: 画面の動的な挙動を実装する
-
内容:
- 事業部選択時の部門リスト取得処理を実装する
- 顧客検索画面のポップアップ処理を実装する
- バリデーションエラー表示のための処理を実装する
-
活用するクラス・メソッド:
- jQuery AJAXメソッド
-
ポイント:
- エラーハンドリングを適切に実装する
- 関連ファイル: なし
なぜ実装計画を生成させるのか
設計書を直接実装させずに、実装計画を挟む理由はそのほうが人間がレビューしやすいからです。実装を見て間違いを見つけるより、実装計画を見て間違いを見つけるほうが容易です。また、実装を理解する補助にもなります。
なぜクラス図やシーケンス図を生成させるのか
クラス図やシーケンス図を生成させる理由は、1つは人間が理解しやすいため。もう一つはCopilotの実装に一貫性を持たせるためです。後述の実装では各ステップごとにチャットのセッションを切り替えます。そのため、Copilotは前ステップで何を実装したか覚えていません。クラス図やシーケンス図がないとCopilotはそのセッション内ででっち上げた(実際には実装されていない)クラスやメソッドを呼び出す実装を出力します。
セッションを分けなければいいと思うかもしれませんがセッションの履歴が長くなると、やはり最初のほうに実装したメソッドは忘れてしまいます。
5.実装計画を修正
出力された実装計画を確認し、間違いを修正します。この時点で全く意図しない実装計画になっている場合はInstruction fileを見直して実装計画を生成しなおしましょう。
例えば、手順4実装計画を作成に乗せたサンプルに対しては以下のような修正をしました。
- ControllerからViewHelperを参照していたので、ViewからViewHelperを参照するようCopilotに修正させた
- Formクラスの実装でjavax.validationアノテーションを活用する指示になっていたので削除(自作のDomainアノテーションを活用してほしいことがInstruction fileに記載されている)
- ステップ10とステップ11のhtmlのパスが間違っており、かつ新規作成になっていたので修正
計画全体に影響するような修正はCopilotに、軽微な修正であれば自身で修正するのが効率が良いです。
何度か検証を繰り返していると、Copilotは以下を間違いやすいことに気づきました。
- 既に実装済みの共通部品を無視しがち
- ファイル更新とせずに、新規作成として扱いがち
6.1Stepずつ実装
最後に計画させた内容を実装させます。
- Copilot Chatを開き、新しいセッションに切り替える
- Editモードを選択
- Claude 3.7 Sonnetを選択
- 実装計画ファイルをコンテキストに設定する
-
実装計画のステップ1だけを実装してください
と入力して実行 - 実装の内容を確認し、問題がなければ保存する
- 修正してほしい部分があれば同セッション内で修正を指示する
実装計画の最後まで以上を繰り返します。各ステップごとにセッションは切り替えます。1セッションに与えるコンテキストが少ないほど、修正指示がしやすいためです。
課題
- 実装計画の最後を実装し終えるまで動作確認がきない。例えばまずはHTMLの表示だけを実装、次に初期表示項目の実装、といった段階的に打鍵して確認するような実装計画にできませんでした。最後まで実装してから動くか、動かないかがわかるのでデバックしずらいです。
- プロンプトのチューニングが甘く、未だに1回の実装で完璧なコードが生成されることはありません。
今回Formクラスでは独自で実装したDomainアノテーションを使用してほしいのですが、全然使ってくれません。HTMLについてもレイアウトを変更するな、と実装計画に書いてあるにもかかわらずガンガン変更してきます。
各実装内容ごとに、さらにプロンプトを修正する必要がありそうです。もしくは最近リリースされたAgentモードを使用すると改善するかもしれません。 - 5.実装計画を修正でも記載しましたが、Copilotに共通部品を使用する判断をうまくさせられていません。現在はsrcフォルダごとコンテキストを渡して、流用できるものは流用して、という指示にしていますが、このコンテキストが大きすぎることが原因かと考えています。
- HTMLの表示に必要なModel情報が実行計画上表現されていないため、Controller上で設定するModelの情報と、HTMLの実装に不整合が発生しがちです。
最後に
ここまで手順を紹介しましたが、面倒だな、と感じた方もいるかもしれません。0から100までAIが全て判断して自走する場合はここまで丁寧な指示は必要ありませんが、可読性や性能、セキュリティを考慮したアーキテクチャや実装ルールに従ってもらう場合は細かく指示を出す必要があります。
そして、ここまで手間をかけて生成したにもかかわらず、実装の完成度は体感で6割程度。正常に動作するようにデバックに時間がかかります。
今後、Instruction file等をチューニングし、実装の精度を高めていきたいです。
Discussion