用 AI 为开发节省时间:详细解释与实例
1. 概述
本文解决一个非常实际的问题:如何使用 AI(特别是 LLM,比如 GPT、Claude)来节省日常编码工作的时间,同时不让你变成“盲目复制粘贴”的人。你将看到如何使用 Spring AI 将 LLM 集成到 Spring Boot 应用中,如何使用 AI 生成测试、Javadoc,以及需要避免的陷阱。
读完后,你将能够编写自己的 ChatClient 在 Spring Boot 中调用 LLM,了解如何为每个任务编写 prompt(生成代码、代码审查、编写测试),并知道何时 AI 能帮到你,何时 AI 反而让你变慢。
2. AI 在开发工作中可以节省时间的地方
并不是任何地方 AI 都比你自己做快。AI 真正值得使用的有三类工作:
- 重复性工作且模式明确:从 entity 生成 DTO、生成 getter/setter(尽管 IDE 已经可以做),生成模板式的测试用例。
- 需要快速汇总信息的工作:阅读长的 stacktrace、总结 PR、解释没有注释的旧代码。
- 需要快速生成初稿的工作:编写 Javadoc、编写 README、编写复杂的 SQL 查询供你修改。
相反,AI 不会节省时间的情况包括:你还没有清楚地理解问题(模糊的 prompt 导致模糊的结果)、复杂的业务逻辑是公司特有的,或者代码需要很高的安全性/财务准确性,而你没有仔细审查。
注意: AI 生成代码快但审查慢的话,总时间不会减少,有时甚至增加,因为你必须调试“看起来对但逻辑错误”的代码。
3. 将 LLM 集成到 Spring Boot 与 Spring AI
与其手动使用 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
创建一个 wrap 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 自动配置,你只需要注入即可。prompt().user(...).call().content()发送 prompt 并获取返回的文本字符串,不需要手动解析 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,将返回以下说明文本:
由于变量 `customer` 在调用 `customer.getEmail()` 前未初始化,
导致发生 NullPointerException。检查 OrderService —— 当未找到记录时
repository 可能返回 null。应该使用 Optional
或在访问属性前检查 null。
4. 使用 AI 自动生成单元测试 —— 具体示例
最耗时的工作之一是为每个逻辑分支编写测试用例。假设你有一个 DiscountCalculator 类:
public class DiscountCalculator {
public BigDecimal calculate(BigDecimal orderAmount, CustomerTier tier) {
if (orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("订单金额必须为正数");
}
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 生成测试(你将上面的类复制到 prompt 中):
为 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("订单金额必须为正数");
}
}
@ParameterizedTest+@CsvSource将 3 个 tier 的情况合并到一个测试中,减少测试代码的重复。- 一行
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 天内创建,
* 并且至少有一个允许退款的商品。
*
* @param orders 输入订单列表
* @return 满足以上三个条件的订单列表
*/
这节省了新手融入旧 codebase 的时间——你将一批方法粘贴进去,AI 返回 Javadoc 初稿,你只需修正业务术语错误的地方。
6. 在 CI 中使用 AI 自动进行代码审查
你可以在 pipeline 中添加调用 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 中更改代码的确切部分,避免发送整个仓库(浪费 token,速度慢)。jq -n --rawfile安全地构建 JSON body,避免代码中特殊字符的转义错误。- 结果
review.json你可以通过另一个 step 作为注释发布回 PR。
注意: AI 审查不能替代资深人员审查。它只能发现明显的错误(缺少 null 检查、catch Exception 为空、基本的 SQL 注入)。不要仅因为 AI“批准”就合并 PR。
7. 常见错误以及何时不应使用 AI
在 Java 项目中使用 LLM 时的一些实际陷阱:
- 幻觉不存在的 API:AI 可能建议调用
Order.markAsRefundable()方法,但实际上该类没有这个方法。在相信之前始终要编译测试一下。 - 生成的代码语法正确但版本错误:AI 经常混淆 Java 8 风格(
new ArrayList<>())和 Java 21 风格(List.of(...)),不一致 —— 你需要按照团队的编码规范进行规范化。 - 泄露敏感信息:不要将包含密钥、连接字符串或真实客户数据的配置整体粘贴到发送给第三方 AI 服务的 prompt 中。
- 模糊的 prompt 导致模糊的输出:“为这个类写测试”得到的结果比明确指出需要覆盖的边界情况的 prompt 要粗糙得多。
- 不应该使用 AI 的情况:利息计算、税收计算,或任何合规相关的部分,其中差一点点就会产生法律问题 —— AI 应该只在编写框架中提供帮助,核心计算部分必须由具有业务专业知识的人员通过实际测试用例仔细检查,而不是 AI 自己想出的测试用例。
每次使用 AI 生成代码时应该应用的小检查清单:
1. 代码能编译吗?
2. AI 生成的测试用例是否覆盖了真实的边界情况,还是只覆盖了"听起来合理的"情况?
3. 是否有敏感信息泄露到 prompt 中?
4. 代码风格是否符合团队的当前规范?
8. 总结
AI 最能帮你节省时间的地方是在重复性工作和需要快速生成初稿的地方:生成测试用例、解释 stacktrace、编写 Javadoc、初步审查 diff。Spring AI 使得将 LLM 集成到 Java 应用变得简单,只需几行配置和 ChatClient。
关键是你仍然必须仔细审查所有输出 —— AI 节省了编写时间,但节省不了理解和验证的时间。在正确的地方以适当的信任级别使用,你可以减少每周重复的许多小时,而不会牺牲代码质量。