AIでDev時間を節約する:詳細解説と実行可能な例付き

この記事には短い動画があります:TikTok

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 は作成時間を節約しますが、理解と検証の時間は節約しません。正しい場所で、適切な信頼レベルで使用すれば、毎週何時間もの繰り返し作業を短縮でき、コード品質と引き換えにする必要がありません。

llmspring-aiai生産性java