前回の内容
こちらを参照ください。
https://qiita.com/akiraabe/items/9762582939191c3e4a39
はじめに
この記事は、生成AIによるコードベース解析 というテーマでの実験内容をまとめています。
2回目です。
今日の内容
前回は、コードベース解析を複数のツールで試してみました。
今回は、ツールをGitHub Copilot(Copilot Chat含む)に絞ります。
内容としては、以下を取り扱います。
- Copilot Chatの使い方のTips
- Copilot Chatを用いたコードベース解析
- 機能を把握する
- エラー対応する
- 機能を修正追加する
Copilot Chatの使い方のTips
ここでは、コードベース解析に有用と思われるものをピックアップします!
詳細な内容は、GitHub社の公式のページ1を参照ください。
なお、インストール方法、エディタ上でのコード補完などについては触れません。2
振る舞いをカスタマイズする
Copilot Chatは以下の方法で振る舞いをカスタマイズできます。
-
- 今日使うカスタム指示ファイル
https://github.com/tis-abe-akira/ddd-breakthrough/blob/3967fba17a7d7c0b80247255b3b9dca45ec191bb/.github/copilot-instructions.md - このファイルはきのぴーさんの .cursorrules を参考にしました。(Copilot Chatへの最適化はしていません。)
https://github.com/kinopeee/cursorrules
- 今日使うカスタム指示ファイル
-
プロンプトでその都度振る舞いを指定する
Jobに適したツールを選択する
どういう場合に、Copilot Chatを使うか?というのは、こちらに記載があります。
プロンプトでkeywordを指定する!
プロンプトに、@, /, #
などが使えます。その使い分けについて知っておくと便利です!
#codebase
または @workspace
を指定すると、参照すべきファイルをIndexからCopilotが自動判断してくれます。
ただし、参照すべきファイルが特定できている場合には、#file
で指定した方が高精度な回答を得られる可能性が高いと思います。
Indexingを行う
セマンティックインデックスというものをもとにしたChatの会話が出来ます。
Indexは明示的に作る必要はないようです。(初期には明示的に作る必要があった記憶が…)
また、少し前まではIndexに載せられるファイル数には2000までという上限がありましたが、今はその記述が見当たらず、こちらのZennの記事によると、現在はその制約はなくなっているようです。
Indexが使われているか?については、#codebase
, @workspace
でソースコードをリファレンスしているか?で判別可能です。
Copilot ChatのTips説明は、一旦、以上としますが、この他にも色々と便利な機能がありますので、ぜひ公式ドキュメントを確認ください!!
Copilot Chatを用いたコードベース解析
ここからは、以下の内容をデモとして実施していきます!
- 1.機能を把握する
- 2.エラー対応する
- 3.機能を修正追加する
1.機能を把握する
前回の題材のアプリケーション3で、シンジケートローンのDrawdown(融資の実行だと思ってください)というシナリオがあります。
これに対して、機能追加をしたいので、まずは、現状の機能を把握します。
プロンプトはこちらです!
#codebase DrawdownController#createを起点とした処理の流れをクラス名#メソッド名形式で説明してください。
その際に、基底クラスを漏らさないように注意してください。
生成AIのアウトプット(まずまずの内容でした!)
1. **DrawdownController#create**
- リクエストボディからDrawdownDtoを受け取る
- DrawdownService#createを呼び出す
2. **DrawdownService#create** (Override)
- AmountPieの生成(Drawdown固有の処理)
- AmountPieService#createでAmountPieエンティティを生成
- 生成したAmountPieのIDをDTOに設定
- 親クラスのTransactionService#createを呼び出し
3. **TransactionService#create** (基底クラス)
- DrawdownService#toEntityでエンティティに変換
- DrawdownRepository#saveでエンティティを永続化
- DrawdownService#toDtoでDTOに変換して返却
4. **DrawdownService#toEntity** (Override)
- 基本的なエンティティ情報を設定
- FacilityService#findByIdで関連Facilityを取得
- TransactionService#setBasePropertiesで共通項目を設定
5. **TransactionService#setBaseProperties** (基底クラス)
- PositionService経由でPosition情報を設定
- AmountPieService経由でAmountPie情報を設定
6. **DrawdownService#toDto** (Override)
- 基本的なDTO情報を設定
- TransactionService#setBaseDtoPropertiesで共通項目を設定
- FacilityService#toDtoでFacility情報をDTOに設定
- 残額と利用率の計算結果を設定
7. **DrawdownController#create**(最終)
- ResponseEntityでラップして返却
Mermeid形式のシーケンス図!
2.エラー対応する
今回のシナリオでは、Drawdown(融資実行)する際に、Loanエンティティの永続化が漏れています。
この処理を追加したいのですが、LoanServiceの実装が不完全なため、エラー修正が必要です。
エラーを把握する
まずは、LoanServiceTestを実行します。この処理ではExceptionが発生します。
エラーの事象をCopilot Chatに教えてもらう!
以下のようなプロンプトでエラー原因を教えてもらいます。
以下のようなエラーが発生しました。原因を教えてください。
${error_log}
エラーの説明は的確な内容でした。(StackTraceに情報が豊富だからだと思います)
タスク分析と解決策を考えていくね!
# エラー分析と解決策
## 問題の概要
マジでヤバいエラーが出てるけど、これは `detached entity passed to persist` ってエラーなの!
つまり、デタッチ状態のエンティティを永続化しようとしちゃってるってことだよ!
## 原因
このエラーが起きる主なパターンはこんな感じ:
1. すでにDBに保存されてるエンティティを新規作成しようとした
2. 関連エンティティの状態管理がおかしくなってる
3. IDがすでに設定されてるオブジェクトをpersistしようとした
## 解決策
以下のどれかの対策を試してみてね:
``` java
// 方法1: マージを使う
entityManager.merge(sharePieEntity);
// 方法2: 保存前にIDをnullにする
sharePieEntity.setId(null);
// 方法3: SaveOrUpdateを使う
repository.saveAndFlush(sharePieEntity);
## アドバイス
- エンティティの状態をちゃんとチェックしてね
- cascade設定が正しいか確認してみて
- トランザクション管理がちゃんとできてるか見直してみて
もし具体的なコードを見せてくれたら、もっと詳しくアドバイスできるかも!エンティティクラスとリポジトリの実装見せてもらえたら嬉しいな〜!
エラーの修正に挑む!
※ デモでやってみます(事前にリハーサルしていません!)
こちらはうまくいかなかったので、手直しを入れました。
3.機能を修正追加する
前述の通り、「Drawdown(融資実行)する際に、Loanエンティティの永続化が漏れています」この処理を追加してもらいます。
今回の設計意図は、DrawdownService#toEntity
から LoanService#toEntity
を呼び出すというものですが、それは伝えずに、Copilot Chatに提案してもらいます!
(これでうまくいかない可能性は高いです。その際には、上記の設計意図を伝え誘導します。)
#codebase 現状は、DrawdownService#toEntityを実行しても、Loanエンティティの永続化がされません。
実装がされていないのが原因です。Loanエンティティの永続化をするように、機能追加を行ってください。
Copilot Chatの提案内容(動作確認レベルではOKそうでした!)
1.まず DrawdownService に LoanService を追加
@Service
@Transactional(readOnly = true) // readOnly = trueは不採用(阿部)
public class DrawdownService extends TransactionService<Drawdown, DrawdownDto, DrawdownRepository> {
private final FacilityService facilityService;
private final LoanService loanService; // 追加!
public DrawdownService(
DrawdownRepository repository,
AmountPieService amountPieService,
PositionService positionService,
FacilityService facilityService,
LoanService loanService, // 追加!
InvestorService investorService) {
super(repository, amountPieService, positionService, investorService);
this.facilityService = facilityService;
this.loanService = loanService; // 追加!
}
2.toEntity メソッドを修正
@Override
public Drawdown toEntity(DrawdownDto dto) {
Drawdown entity = new Drawdown();
entity.setId(dto.getId());
entity.setType("DRAWDOWN");
entity.setDate(dto.getDate());
entity.setAmount(dto.getDrawdownAmount());
// Facilityの設定
Facility facility = facilityService.findById(dto.getRelatedFacilityId())
.map(facilityService::toEntity)
.orElseThrow(() -> new BusinessException("Facility not found", "FACILITY_NOT_FOUND"));
entity.setRelatedFacility(facility);
// Loanエンティティの作成と永続化 ※この辺がまるっと抜けていた処理!
LoanDto loanDto = LoanDto.builder()
.amount(dto.getDrawdownAmount())
.totalAmount(dto.getDrawdownAmount())
.borrowerId(facility.getBorrower().getId())
.facilityId(facility.getId())
.startDate(LocalDate.now())
.endDate(facility.getEndDate())
.interestRate(facility.getInterestRate())
.build();
// Loanを永続化して関連付け
LoanDto savedLoan = loanService.create(loanDto);
entity.setRelatedPosition(loanService.toEntity(savedLoan));
// AmountPieの設定
if (dto.getAmountPieId() != null) {
AmountPie amountPie = amountPieService.findById(dto.getAmountPieId())
.map(amountPieService::toEntity)
.orElseThrow(() -> new BusinessException("AmountPie not found", "AMOUNT_PIE_NOT_FOUND"));
entity.setAmountPie(amountPie);
}
entity.setDrawdownAmount(dto.getDrawdownAmount());
entity.setVersion(dto.getVersion());
return entity;
}
まとめ
生成AIも万能ではないので、設計の意図は明確に伝える必要があります。
一方で、現状の把握については、codebase(workspace)の検索機能が追加されたことにより、うまく使うとかなりの精度が出ることが期待されます。
「現状の把握+意図を伝える」
この精度を高めると、それなりの規模のシステムのメンテナンス作業にも使える可能性があると思います。
例えば、現状把握した内容をドキュメントに書き出して、それを後続の作業のプラニングの際に参照するようにする(ある種の長期記憶)といった使い方は有効だと思います。
例:
調査の結果得られた知見をMarkdownの文書にまとめてください。
その際に、ビジネスロジックを追加するとした場合には、
どのクラスを修正するべきか?というのを示してください。