AIでDev時間を節約する:詳細解説と実行可能な例付き
1. 概要
この記事は非常に実用的な質問に答えます:AI(具体的には GPT、Claude などの LLM)を使って毎日のコード作業時間を短縮する方法は?ただし、盲目的にコードをコピペするだけの人間になってはいけません。Spring AI を使ってSpring Boot アプリケーションに LLM を統合する方法、AI でテストやJavadoc を生成する方法、そして注意すべき落とし穴が分かります。
読み終わると、Spring Boot でChatClientを使って LLM を呼び出すコードを自分で書けるようになり、タスクごとのプロンプト作成方法(コード生成、レビュー、テスト作成)が分かり、AI が本当に役に立つ時と、逆に遅くなる時の判断ができます。
2. 開発業務で AI が時間を節約できるのはどこか
どこでも AI が自分で行うより速いわけではありません。AI が本当に活躍する仕事は3つのカテゴリーです:
- 繰り返しで明確なパターンがある仕事:EntityからDTOを生成、getterやsetterを生成(IDEでもできますが)、boilerplate テストケースを生成する。
- 情報をすばやく統合する仕事:長いstacktrace を読む、PR を要約する、コメントなしのレガシーコードの説明を理解する。
- 初稿が必要な仕事:Javadoc を書く、README を書く、複雑なSQL 文を書いて後で修正する。
逆に、AI が時間を節約できないケース:問題をよく理解していない(曖昧なプロンプト→曖昧な結果)、会社固有の複雑なビジネスロジック、またはセキュリティ/財務に高い精度が求められるコードで十分に確認できない場合。
注意: AI がコードを素早く生成しても、レビューが遅いと総時間が減らず、むしろ「見た目は正しいが論理が間違っている」コードをデバッグする羽目になり、時間が増える場合もあります。
3. Spring AI でSpring Boot に LLM を統合する
RestTemplateで手動で REST API を呼ぶ代わりに、Spring AI を使って便利なChatClientを利用します。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
application.propertiesに API キーを宣言します:
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini
ChatClientをラップするService を作成します:
@Service
public class CodeAssistantService {
private final ChatClient chatClient;
public CodeAssistantService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String explainStackTrace(String stackTrace) {
String prompt = """
シニアJavaエンジニアとして、以下のstacktrace の原因を簡潔に説明し、
修正方法を提案してください:
%s
""".formatted(stackTrace);
return chatClient.prompt()
.user(prompt)
.call()
.content();
}
}
ChatClient.BuilderはSpring Boot が上記のstarter のおかげでauto-config してくれるので、inject するだけです。prompt().user(...).call().content()はプロンプトを送信して応答テキストを取得し、JSON を手動でパースする必要がありません。explainStackTraceメソッドは長いstacktrace を受け取り、簡潔な説明を返します。これは珍しいエラーをデバッグするたびに5~10分のログ読み時間を節約します。
Controller で試しに呼び出してみます:
@RestController
@RequestMapping("/api/assist")
public class CodeAssistantController {
private final CodeAssistantService assistantService;
public CodeAssistantController(CodeAssistantService assistantService) {
this.assistantService = assistantService;
}
@PostMapping("/explain")
public String explain(@RequestBody String stackTrace) {
return assistantService.explainStackTrace(stackTrace);
}
}
POST /api/assist/explainを呼び出し、body にNullPointerExceptionのstacktrace を入れると、以下のような説明テキストが返ります:
NullPointerExceptionエラーは、変数`customer`が`customer.getEmail()`を呼ぶ前に
初期化されていないために発生します。OrderService を再度確認してください。
おそらくrepository がレコードが見つからない時にnull を返しています。
Optional を使用するか、属性にアクセスする前にnull をチェックすべきです。
4. AI で自動ユニットテストを生成する — 具体例
最も時間がかかる仕事の1つは、ロジックの各分岐のテストケースを書くことです。DiscountCalculatorクラスがあるとします:
public class DiscountCalculator {
public BigDecimal calculate(BigDecimal orderAmount, CustomerTier tier) {
if (orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Order amount must be positive");
}
BigDecimal rate = switch (tier) {
case GOLD -> new BigDecimal("0.15");
case SILVER -> new BigDecimal("0.10");
case BASIC -> new BigDecimal("0.05");
};
return orderAmount.multiply(rate).setScale(2, RoundingMode.HALF_UP);
}
}
LLM にテストを生成させるためのプロンプト例(上記のクラスをプロンプトにコピーします):
DiscountCalculatorクラスのJUnit 5 + AssertJ ユニットテストを書いてください。
ケース:GOLD、SILVER、BASIC、およびorderAmount <= 0がIllegalArgumentExceptionを
スローする場合を含めてください。適切であれば@ParameterizedTest を使用してください。
AI は通常ほぼ正確なドラフトを返すので、チームのスタイルに合わせて調整します:
class DiscountCalculatorTest {
private final DiscountCalculator calculator = new DiscountCalculator();
@ParameterizedTest
@CsvSource({
"GOLD, 100, 15.00",
"SILVER, 100, 10.00",
"BASIC, 100, 5.00"
})
void shouldCalculateDiscountByTier(CustomerTier tier, String amount, String expected) {
BigDecimal result = calculator.calculate(new BigDecimal(amount), tier);
assertThat(result).isEqualByComparingTo(new BigDecimal(expected));
}
@Test
void shouldThrowWhenOrderAmountIsNotPositive() {
assertThatThrownBy(() -> calculator.calculate(BigDecimal.ZERO, CustomerTier.GOLD))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Order amount must be positive");
}
}
@ParameterizedTest+@CsvSourceは3つのtier ケースを1つのテストにまとめ、テストコードの重複を削減します。assertThat(result).isEqualByComparingTo(...)行はBigDecimalを正しく比較します(equalsがscale を区別するエラーを避ける)。- 残りのケースは例外を特定のメッセージでチェックします。
AI が素早くできるのはすべての境界ケースをリストアップすることです(orderAmount <= 0のケースを忘れやすい)。あなたがすべきことは期待値が正しいかを再度確認することです。AI が数値を誤ったり計算に誤りがあることはよくあります。
5. AI で Javadoc を生成し、レガシーコードを要約する
コメントのない古いコードに対して、AI は行ごとに読むより速く要約できます。複雑な処理の一部がある例:
public List<Order> filterEligibleOrders(List<Order> orders) {
return orders.stream()
.filter(o -> o.getStatus() == OrderStatus.PAID)
.filter(o -> o.getCreatedAt().isAfter(LocalDate.now().minusDays(30)))
.filter(o -> o.getItems().stream().anyMatch(Item::isRefundable))
.toList();
}
Prompt: 「このメソッドの Javadoc を書き、3つのフィルタ条件を明確に説明してください。」
AI が返すもの:
/**
* 払戻処理の対象となる注文のリストをフィルタリングします。
* 条件:注文がPAID 状態、過去30日以内に作成、最低1つの払戻可能なアイテムを含む。
*
* @param orders 入力注文リスト
* @return 3つすべての条件を満たす注文リスト
*/
これは新しいメンバーの古いcodebase へのonboarding の時間を節約します。複数のメソッドを貼り付けると、AI が Javadoc のドラフトを返し、ビジネス用語の誤りだけを修正します。
6. CI で AI によるコードレビューを自動化する
パイプラインに AI レビューのステップを追加してdiff をレビューし、人によるレビューの前にエラーを検出できます。
GitHub Actions でスクリプトを使って API を呼ぶ簡単な例:
name: ai-review
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get diff
run: git diff origin/main...HEAD > diff.txt
- name: Call AI review
run: |
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer ${{ secrets.OPENAI_API_KEY }}" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile diff diff.txt \
'{model:"gpt-4o-mini", messages:[{role:"user", content: ("以下のJavaのdiffをレビューし、重大な問題のみを指摘してください: " + $diff)}]}')" \
> review.json
git diff origin/main...HEADはPR で変更されたコード部分のみを取得し、リポ全体を送信しないため(トークン節約、高速化)。jq -n --rawfileは JSON ボディを安全に構築し、コード内の特殊文字のエスケープエラーを回避します。review.jsonの結果は別のステップで PR へのコメントとして投稿できます。
注意: AI レビューはシニアレビューの代替にはなりません。明らかなエラー(null check がない、空のcatch Exception、基本的なSQL injection)のみを検出します。AI が「approve」しただけで PR をマージしないでください。
7. よくあるエラーと、AI を使わない方がいい場合
Java プロジェクトで LLM を使う際の現実的な落とし穴:
- 存在しない API 幻覚:AI は
Order.markAsRefundable()メソッドを呼ぶことを提案するかもしれませんが、実際のクラスにはそのメソッドがありません。信じる前に必ずコンパイルしてください。 - 構文は正しいが、バージョンが間違っている:AI はJava 8スタイル(
new ArrayList<>())とJava 21(List.of(...))を一貫性なく混在させることがあります。チームのコーディング規約に合わせて標準化する必要があります。 - 機密情報の漏洩:シークレット、接続文字列、または実際の顧客データを含むconfig を丸ごとプロンプトに貼り付けて、サードパーティの AI サービスに送信しないでください。
- 曖昧なプロンプト→曖昧な出力:「このクラスのテストを書いてください」は、カバーする必要がある境界ケースを明確に指定したプロンプトより、はるかに粗い結果になります。
- AI を使わない方がいい場合:利息計算、税金計算、またはコンプライアンス関連の部分で、わずかな誤りが法的問題になる場合。AI はフレームワークの作成をサポートすべきで、中核の計算部分はビジネス専門知識を持つ人が実際のテストケースで(AI が自分で考えたテストケースではなく)厳密に確認する必要があります。
AI がコードを生成する際に毎回適用すべき小さなチェックリスト:
1. コードはコンパイルできますか?
2. AI 生成のテストケースは実際の境界ケースを正しくカバーしていますか、それとも「論理的に聞こえる」ケースだけをカバーしていますか?
3. 機密情報がプロンプトに漏れていませんか?
4. コードスタイルは現在のチームの規約に合っていますか?
8. 結論
AI が最も明らかに時間を節約するのは、繰り返しの仕事と素早いドラフトが必要な仕事です:テストケース生成、stacktrace 説明、Javadoc 作成、diff 初期レビュー。Spring AI はChatClientと数行の設定だけで、Java アプリケーションへの LLM 統合をシンプルにします。
重要な点は、すべての出力を注意深く確認する必要があるということです。AI は作成時間を節約しますが、理解と検証の時間は節約しません。正しい場所で、適切な信頼レベルで使用すれば、毎週何時間もの繰り返し作業を短縮でき、コード品質と引き換えにする必要がありません。