代码审查 Prompt 设计——让 AI Review 出真正有价值的问题
代码审查 Prompt 设计——让 AI Review 出真正有价值的问题
适读人群:用 AI 做 Code Review 的开发者 | 阅读时长:约15分钟 | 核心价值:多个 Code Review Prompt 版本的实测对比,找到能发现真正问题的配方
我做过一个测试。
把同一段有问题的 Java 代码,分别用三个不同的 Prompt 让 Claude Review:
- Prompt A:"帮我 Review 这段代码"
- Prompt B:"请从代码质量、可读性、性能等方面 Review 这段代码,给出改进建议"
- Prompt C:我自己设计的专用 Code Review Prompt
Prompt A 给了我5条建议,全是格式和命名风格问题,没有一条是真正的 Bug。
Prompt B 给了我8条建议,多了两条性能优化建议,但代码里真正的并发安全问题和一个潜在的 SQL 注入风险,它没有发现。
Prompt C 发现了那个并发安全问题和 SQL 注入风险,虽然风格建议少了,但找到了真正重要的问题。
这个差别让我花了不少时间研究 Code Review Prompt 的设计。
先说那段测试代码
为了让后面的对比有意义,我先把测试用的代码贴出来:
@Service
public class UserCacheService {
private Map<Long, UserDTO> cache = new HashMap<>();
@Autowired
private UserRepository userRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
public UserDTO getUser(Long userId) {
if (cache.containsKey(userId)) {
return cache.get(userId);
}
UserDTO user = userRepository.findById(userId).orElse(null);
cache.put(userId, user);
return user;
}
public List<UserDTO> searchUsers(String keyword) {
String sql = "SELECT * FROM users WHERE name LIKE '%" + keyword + "%'";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
UserDTO dto = new UserDTO();
dto.setId(rs.getLong("id"));
dto.setName(rs.getString("name"));
return dto;
});
}
public void updateUserStatus(Long userId, String status) {
UserDTO user = getUser(userId);
user.setStatus(status);
userRepository.save(user.toEntity());
}
}这段代码有几个真实的问题:
HashMap在多线程环境下不安全,高并发时会出现数据竞争searchUsers方法直接拼接 SQL,存在 SQL 注入漏洞getUser返回的是缓存里的对象引用,updateUserStatus修改了缓存里的对象,下次getUser返回的就是被修改过的状态——这是一个隐蔽的缓存污染 Bug- 缓存没有过期机制,内存泄漏风险
@Autowired字段注入(风格问题,影响可测试性)
Prompt A 的输出:只看表面
Prompt:"帮我 Review 这段代码"
输出的五条全是:
@Autowired建议改成构造器注入- 方法名
getUser建议改成getUserById更清晰 UserDTO属性可以加 Lombok 简化- SQL 查询建议用驼峰命名别名做字段映射
- 类缺少
@Slf4j日志注解
注意:SQL 注入、并发问题、缓存污染——一个都没提。
这种 Review 不是没用,但如果上了 PR 就过了,可以说是有危险的。
Prompt B 的输出:还是没找到关键问题
Prompt:"请从代码质量、可读性、性能等方面 Review 这段代码,给出改进建议"
比 Prompt A 多了几条:
HashMap建议改成ConcurrentHashMap提升并发性能(提到了并发,但定性为"性能"而不是"安全")- 缓存建议加 TTL 过期机制
- 建议加缓存大小上限防止内存问题
还是没提到:SQL 注入、缓存污染(修改缓存对象引用导致数据被篡改)。
ConcurrentHashMap 那条说的是"性能",但实际上 HashMap 在并发下的问题比性能更严重——它会导致死循环(Java 7)或数据丢失(Java 8+),这是正确性问题不是性能问题。
为什么通用 Prompt 发现不了关键问题
这里我想分析一下原因,不是为了埋怨 AI。
通用 Code Review Prompt 让 AI 做的事情是"找所有可能的问题"。但 AI 在这种开放任务下,倾向于找"容易发现、容易解释"的问题,而不是"严重但需要深入推理"的问题。
格式问题、命名问题、缺少日志——这些从文本表面就能看出来。 SQL 注入——需要 AI 理解这段代码会被什么样的 keyword 参数调用,推断到恶意输入场景。 缓存污染——需要 AI 追踪对象引用的生命周期,理解修改外部引用会影响缓存内容。
后两种问题需要更深的推理,通用 Prompt 不会引导 AI 去做这种推理。
要让 AI 找到这类问题,需要在 Prompt 里明确告诉它去找什么,怎么找。
Prompt C:我设计的专用 Code Review Prompt
这是我打磨了半年的版本:
你是一个资深 Java 工程师,专门做代码安全审查和 Bug 排查。
请对以下代码进行 Code Review,**重点关注以下几类真正有价值的问题**:
## 必须检查的问题类别(严重程度从高到低)
### Level 1 - 安全漏洞(必须修复,不修不能上线)
- SQL 注入:有没有直接拼接 SQL 字符串的地方
- 不安全的反序列化
- 路径遍历漏洞(文件操作时的路径参数)
- 命令注入(Runtime.exec 等)
- 敏感信息泄露(日志里打印密码/token/完整身份证号)
### Level 2 - 并发与线程安全
- 非线程安全的集合(HashMap、ArrayList)在多线程场景下使用
- 静态可变状态
- 锁的粒度是否合适,有没有死锁风险
- @Async 方法的异常是否会被吞掉
### Level 3 - 数据正确性问题
- 对象引用问题:是否有修改了传入参数或返回对象的操作,导致调用方的数据被污染
- 空指针风险:返回 null 但调用方没有判断
- 精度问题:金额用 double/float 计算
- 缓存一致性:缓存更新和数据库更新的原子性
### Level 4 - 资源泄露
- 流/连接没有关闭(非 try-with-resources 的资源操作)
- 缓存没有过期/大小限制,可能的内存泄漏
- 线程池没有关闭
### Level 5 - 可以改但不紧急的问题
- 代码风格、命名规范
- 可读性改进
- 性能优化(非关键路径)
## 你的输出格式
对每个发现的问题:
1. 标注级别(Level 1-5)
2. 指出具体的代码行或位置
3. 解释为什么这是问题(不是什么,是为什么)
4. 给出修改后的代码示例
如果代码没有 Level 1-3 的问题,明确说"未发现严重问题",不要为了显示工作量把 Level 4-5 的问题往前凑数。
代码如下:
[待审查的代码]用这个 Prompt 对同样的代码:
AI 找到的 Level 1 问题:
[Level 1 - SQL 注入]
位置:searchUsers 方法
\`\`\`java
String sql = "SELECT * FROM users WHERE name LIKE '%" + keyword + "%'";
\`\`\`
问题:keyword 参数直接拼接到 SQL 里,攻击者可以传入 `%'; DROP TABLE users; --` 这样的输入。
修复:
\`\`\`java
public List<UserDTO> searchUsers(String keyword) {
String sql = "SELECT * FROM users WHERE name LIKE ?";
String param = "%" + keyword + "%";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
UserDTO dto = new UserDTO();
dto.setId(rs.getLong("id"));
dto.setName(rs.getString("name"));
return dto;
}, param);
}
\`\`\`AI 找到的 Level 2 问题:
[Level 2 - 线程安全]
位置:cache 字段
\`\`\`java
private Map<Long, UserDTO> cache = new HashMap<>();
\`\`\`
问题:HashMap 在多线程并发写入时会出现数据竞争。在高并发场景(多个请求同时调用 getUser),可能丢失缓存数据,甚至在 Java 8 之前版本出现死循环。
修复:改用 ConcurrentHashMap,或者用 Caffeine 等成熟缓存库。AI 找到的 Level 3 问题:
[Level 3 - 对象引用污染缓存]
位置:getUser 和 updateUserStatus 的交互
问题:getUser 直接返回缓存里的 UserDTO 对象引用(不是副本)。
updateUserStatus 调用 getUser 之后,对返回的 user 对象调用 setStatus 修改了对象属性,
这直接修改了缓存里的对象。下次调用 getUser 时,会返回已被修改的状态。
如果缓存里存的是"用户查询到的原始数据",而 updateUserStatus 只是"临时更新状态",
这个污染会导致缓存里的数据和数据库的数据不一致。
修复:getUser 返回时做深拷贝:
\`\`\`java
public UserDTO getUser(Long userId) {
if (cache.containsKey(userId)) {
return cache.get(userId).copy(); // 返回副本
}
UserDTO user = userRepository.findById(userId).orElse(null);
if (user != null) {
cache.put(userId, user.copy()); // 存副本进缓存
}
return user;
}
\`\`\`这就是 Prompt C 和 Prompt A/B 的区别。
几个场景的专用 Prompt 变体
通用版 Code Review Prompt 是基础,但不同场景还需要调整侧重点。
前端 React 代码的 Review Prompt 补充:
前端代码额外检查:
- useEffect 的依赖数组是否完整,有没有闭包问题
- 是否有直接修改 state 对象的操作(应该用不可变方式)
- 事件监听有没有在组件卸载时清除
- dangerouslySetInnerHTML 使用时有没有做 XSS 过滤
- 大列表渲染有没有做虚拟化数据库相关代码的 Review Prompt 补充:
数据库操作额外检查:
- 批量操作是否在事务里
- 大数据量查询是否有分页,有没有全表扫描风险
- 索引使用是否合理(WHERE 条件字段是否有索引)
- MyBatis-Plus 的 updateById 是否有意外更新全字段的风险
- 多表联查是否有笛卡尔积风险(缺少 JOIN 条件)API 接口代码的 Review Prompt 补充:
API 接口额外检查:
- 输入参数是否做了校验(@Valid 注解,自定义校验)
- 接口是否有频率限制(对公网暴露的接口)
- 返回值是否有敏感字段(密码 hash、完整身份证号)
- 错误信息是否泄露了系统内部信息(堆栈 trace 不应该暴露给客户端)
- 文件上传接口是否校验了文件类型和大小一个实用建议:分层 Review
我现在的 Code Review 流程是分两层的:
第一层:自动化工具
在 CI 里跑 SonarQube、Checkstyle、SpotBugs。这层处理格式、基础规范、已知模式的安全问题。
这些工具做的事情是规则匹配,快速、稳定、不需要付费 AI。
第二层:AI Review
CI 过了之后,对重要的改动(新功能、涉及安全的模块、复杂的业务逻辑),再用专用 Prompt 跑一次 AI Review。
这层 AI 做的是推理型分析——对象引用问题、并发场景下的正确性、业务逻辑里的边界情况——这些是规则工具发现不了的。
两层结合,比单独用任何一种都有效。
AI Code Review 不是要代替人工 Code Review。但人工 Review 的时间有限,用 AI 先过滤一遍,能让人工 Review 聚焦在更重要的架构和业务逻辑问题上。
关键是要告诉 AI 去找什么,而不是让它自由发挥。
