在日常开发中,代码 Review 经常会遇到两个问题:

  1. Review 时间不够:业务迭代快,很多 MR/PR 只能看主流程,边界分支容易漏;
  2. Review 质量不稳定:不同开发者经验不同,对异常处理、空值判断、性能风险、安全边界的敏感度也不同。

ChatGPT、Claude、Gemini、DeepSeek 这类大模型,比较适合在代码 Review 前充当“第二视角”:
它不能替代开发者做最终判断,但可以帮助我们提前发现一些明显问题,例如:

  • 空指针风险;
  • 异常处理不完整;
  • 参数校验缺失;
  • SQL 查询性能隐患;
  • 重复逻辑;
  • 命名不清晰;
  • 测试用例遗漏;
  • 接口返回语义不一致。

本文以一个后端接口代码 Review 场景为例,记录一套比较实用的 AI 辅助 Review 流程。


一、本文适合谁

这篇文章适合:

  • Java / Go / Python / Node.js 后端开发者;
  • 需要经常提交 MR/PR 的开发者;
  • 想用 AI 辅助代码 Review,但不想直接复制 AI 结论的人;
  • 技术负责人或团队 Reviewer;
  • 希望比较 ChatGPT、Claude、Gemini、DeepSeek 在开发任务中差异的用户;
  • 正在尝试把 AI 编程助手接入日常研发流程的团队。

本文不会讨论“让 AI 自动合并代码”这种高风险做法,而是重点讲:
如何让 AI 帮我们发现问题,再由开发者验证问题。


二、为什么代码 Review 适合引入 AI

代码 Review 本质上不是“找语法错误”,而是检查代码是否符合业务预期和工程规范。

常见 Review 关注点包括:

  • 代码是否实现了需求;
  • 参数校验是否完整;
  • 异常处理是否合理;
  • 是否存在空值、越界、并发问题;
  • 是否影响性能;
  • 是否破坏已有接口兼容性;
  • 是否有安全风险;
  • 是否补充了必要测试;
  • 是否容易维护。

AI 的优势在于,它可以快速从多个角度扫一遍代码,并输出问题清单。

但 AI 的限制也很明显:

  • 不知道你们项目真实业务规则;
  • 可能误判框架能力;
  • 可能给出“看似合理但不符合项目规范”的建议;
  • 对权限、安全、资金、订单等核心业务不能直接下结论;
  • 无法替代编译、单元测试、集成测试和人工 Review。

所以更合理的定位是:

AI 适合做 Review 前置检查,不适合做最终审批人。


三、本次示例场景:用户地址新增接口

假设有一个新增用户收货地址的接口,代码如下。

@RestController
@RequestMapping("/api/address")
public class AddressController {

    @PostMapping("/create")
    public Result<Long> create(@RequestBody CreateAddressRequest request) {
        Long id = addressService.createAddress(request);
        return Result.success(id);
    }
}

请求对象:

public class CreateAddressRequest {

    private Long userId;

    private String receiverName;

    private String phone;

    private String province;

    private String city;

    private String detailAddress;

    private Boolean defaultAddress;

    // getter/setter 省略
}

Service 代码:

@Service
public class AddressService {

    @Autowired
    private AddressRepository addressRepository;

    public Long createAddress(CreateAddressRequest request) {
        Address address = new Address();
        address.setUserId(request.getUserId());
        address.setReceiverName(request.getReceiverName());
        address.setPhone(request.getPhone());
        address.setProvince(request.getProvince());
        address.setCity(request.getCity());
        address.setDetailAddress(request.getDetailAddress());
        address.setDefaultAddress(request.getDefaultAddress());

        addressRepository.save(address);

        return address.getId();
    }
}

这段代码能跑,但从 Review 角度看,问题不少。

例如:

  • request 可能为 null
  • userId 是否应该从登录态获取,而不是前端传入;
  • 收件人、手机号、地址是否需要校验;
  • defaultAddress 为 true 时,是否需要取消其他默认地址;
  • 是否需要事务;
  • 是否需要限制地址数量;
  • 是否需要防止越权;
  • 是否需要统一错误码;
  • 是否需要记录操作日志;
  • 是否需要补测试用例。

这些问题,AI 可以帮助我们更快列出来。


四、ChatGPT、Claude、Gemini、DeepSeek 在代码 Review 中怎么选

不同模型在代码 Review 场景中的侧重点略有不同。

模型 更适合的 Review 任务 使用建议
ChatGPT 通用代码解释、Review 清单、重构建议、测试用例生成 适合快速检查代码质量和边界条件
Claude 长上下文代码、复杂需求文档、PRD 与代码一致性检查 适合输入较长业务背景和多文件代码
Gemini 文档归纳、多来源资料整理、接口说明辅助生成 适合结合接口文档、需求说明一起 Review
DeepSeek 中文开发语境、代码逻辑分析、算法和工程实现检查 适合中文项目说明和代码辅助分析

实际使用时,不建议纠结“哪个模型一定最好”。

更合理的做法是:

  • 小改动:用一个模型做快速检查;
  • 涉及多个模块:用长上下文能力较好的模型;
  • 关键业务改动:用两个模型交叉 Review;
  • 生成测试用例:让模型单独针对测试场景输出;
  • 不确定的建议:必须回到代码、需求和测试中验证。

如果只是为了比较多个模型在同一段代码上的输出差异,也可以使用一些多模型聚合工具,例如 KULAAI 这类支持 ChatGPT、Claude、Gemini、DeepSeek 等模型切换的产品。工具本身不是重点,关键是 Review 流程要可验证。


五、代码 Review Prompt 怎么写

很多人使用 AI Review 代码时,只会问:

帮我看看这段代码有没有问题。

这种问法太宽泛,模型通常会给出一些泛泛建议。

更好的方式是明确:

  • 你的角色;
  • 业务背景;
  • 代码目标;
  • Review 维度;
  • 输出格式;
  • 不要做什么;
  • 哪些地方需要标注不确定性。

可以使用下面这个 Prompt 模板。

你是一名有经验的 Java 后端开发工程师,请帮我对下面代码做 Code Review。

业务背景:
这是一个新增用户收货地址的接口。
用户登录后可以新增地址。
一个用户可以有多个地址,但最多 20 个。
如果本次新增地址设置为默认地址,则需要取消该用户其他默认地址。
userId 理论上应来自登录态,而不是完全信任前端传参。

Review 目标:
1. 找出可能的空指针问题;
2. 找出参数校验缺失;
3. 找出权限或越权风险;
4. 找出事务一致性问题;
5. 找出接口设计不合理之处;
6. 给出可维护性建议;
7. 给出需要补充的单元测试和接口测试。

输出格式:
- 问题类型
- 问题描述
- 风险等级:高 / 中 / 低
- 为什么是问题
- 建议修改方向
- 是否需要人工确认

要求:
1. 不要直接重写完整项目;
2. 不要假设项目中存在未提供的工具类;
3. 如果某个结论依赖业务规则,请标注“需要业务确认”;
4. 代码建议尽量保持简单;
5. 最后输出测试用例清单。

待 Review 代码:
【在这里粘贴 Controller、Request、Service、Repository 相关代码】

这个 Prompt 的重点是:让 AI 按 Review 维度输出结构化结果。


六、AI 可能会发现哪些问题

针对上面的地址新增接口,AI 通常会指出以下问题。

1. 不应该完全信任前端传入的 userId

如果接口直接使用前端传入的 userId

address.setUserId(request.getUserId());

可能出现越权风险。

例如用户 A 登录后,构造请求:

json

{
  "userId": 10086,
  "receiverName": "张三",
  "phone": "13800000000",
  "province": "浙江省",
  "city": "杭州市",
  "detailAddress": "某某路 1 号",
  "defaultAddress": true
}

如果后端不校验登录用户身份,就可能给其他用户新增地址。

更合理的做法是:

Long currentUserId = loginUserContext.getCurrentUserId();
address.setUserId(currentUserId);

或者至少校验:

if (!Objects.equals(currentUserId, request.getUserId())) {
    throw new BizException("无权限操作该用户地址");
}

在真实项目中,更建议从登录态获取用户 ID,而不是依赖客户端传参。


2. 缺少请求参数校验

原代码没有检查:

  • request 是否为空;
  • receiverName 是否为空;
  • phone 格式是否合法;
  • provincecitydetailAddress 是否为空;
  • 字段长度是否超限;
  • defaultAddress 为 null 时如何处理。

可以增加基础校验,例如:

private void validateCreateAddressRequest(CreateAddressRequest request) {
    if (request == null) {
        throw new BizException("请求参数不能为空");
    }

    if (isBlank(request.getReceiverName())) {
        throw new BizException("收件人不能为空");
    }

    if (isBlank(request.getPhone())) {
        throw new BizException("手机号不能为空");
    }

    if (!request.getPhone().matches("^1\\d{10}$")) {
        throw new BizException("手机号格式不正确");
    }

    if (isBlank(request.getProvince()) || isBlank(request.getCity())) {
        throw new BizException("省市信息不能为空");
    }

    if (isBlank(request.getDetailAddress())) {
        throw new BizException("详细地址不能为空");
    }

    if (request.getReceiverName().length() > 30) {
        throw new BizException("收件人名称过长");
    }

    if (request.getDetailAddress().length() > 200) {
        throw new BizException("详细地址过长");
    }
}

private boolean isBlank(String value) {
    return value == null || value.trim().isEmpty();
}

如果项目使用 Bean Validation,也可以放到 DTO 上:

public class CreateAddressRequest {

    @NotBlank(message = "收件人不能为空")
    @Size(max = 30, message = "收件人名称过长")
    private String receiverName;

    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1\\d{10}$", message = "手机号格式不正确")
    private String phone;

    @NotBlank(message = "省份不能为空")
    private String province;

    @NotBlank(message = "城市不能为空")
    private String city;

    @NotBlank(message = "详细地址不能为空")
    @Size(max = 200, message = "详细地址过长")
    private String detailAddress;

    private Boolean defaultAddress;

    // getter/setter 省略
}

然后 Controller 中使用:

@PostMapping("/create")
public Result<Long> create(@Valid @RequestBody CreateAddressRequest request) {
    Long id = addressService.createAddress(request);
    return Result.success(id);
}

AI 生成类似建议时,需要结合项目规范选择手写校验还是注解校验。


3. 默认地址逻辑缺少事务

如果新增地址设置为默认地址,常见逻辑是:

  1. 取消该用户原有默认地址;
  2. 新增当前地址为默认地址。

这两个操作应该放在同一个事务中。

示例:

@Transactional(rollbackFor = Exception.class)
public Long createAddress(CreateAddressRequest request) {
    validateCreateAddressRequest(request);

    Long currentUserId = loginUserContext.getCurrentUserId();

    if (Boolean.TRUE.equals(request.getDefaultAddress())) {
        addressRepository.clearDefaultAddress(currentUserId);
    }

    Address address = new Address();
    address.setUserId(currentUserId);
    address.setReceiverName(request.getReceiverName().trim());
    address.setPhone(request.getPhone());
    address.setProvince(request.getProvince());
    address.setCity(request.getCity());
    address.setDetailAddress(request.getDetailAddress().trim());
    address.setDefaultAddress(Boolean.TRUE.equals(request.getDefaultAddress()));

    addressRepository.save(address);

    return address.getId();
}

如果不加事务,可能出现:

  • 旧默认地址已取消,新地址保存失败;
  • 多个默认地址同时存在;
  • 数据状态不一致。

不过这里还要继续考虑并发问题。


4. 并发下可能出现多个默认地址

即使加了事务,如果两个请求同时设置默认地址,也可能出现并发问题。

例如:

  • 请求 A 清除默认地址;
  • 请求 B 清除默认地址;
  • 请求 A 新增默认地址;
  • 请求 B 新增默认地址;

最终可能出现两个默认地址。

解决方式要看项目数据库和业务要求,常见选择包括:

  • 数据库唯一约束;
  • 悲观锁;
  • 乐观锁;
  • 串行化处理;
  • 在用户维度加锁;
  • 业务上允许短暂不一致,通过后续修正。

如果使用数据库唯一约束,可以考虑设计:

-- 示例,仅表达思路,具体语法需根据数据库调整
-- 保证同一用户最多只有一个默认地址
CREATE UNIQUE INDEX uk_user_default_address
ON user_address(user_id, default_address)
WHERE default_address = true;

注意:不同数据库对部分索引支持不同,例如 MySQL、PostgreSQL 处理方式不同,不能直接复制上述 SQL 到所有环境。

AI 可以提醒风险,但具体方案必须由开发者结合数据库类型、并发量和业务要求决定。


七、让 AI 生成 Review 清单,而不是只给修改代码

对团队来说,最好沉淀一份通用 Review 清单。以后每次提交代码前,可以先让 AI 按清单检查。

例如:

请根据下面维度对代码做 Review:

1. 功能正确性:
   - 是否满足需求描述;
   - 是否遗漏主流程;
   - 是否遗漏异常流程。

2. 参数校验:
   - 是否校验必填字段;
   - 是否校验长度、格式、枚举值;
   - 是否处理 null。

3. 权限与安全:
   - 是否信任了客户端传入的用户身份;
   - 是否存在越权访问;
   - 是否暴露敏感信息。

4. 数据一致性:
   - 是否需要事务;
   - 是否存在并发问题;
   - 是否需要唯一约束或幂等控制。

5. 可维护性:
   - 命名是否清晰;
   - 方法是否过长;
   - 是否存在重复代码;
   - 异常和错误码是否统一。

6. 性能:
   - 是否有循环查询;
   - 是否可能产生慢 SQL;
   - 是否缺少索引。

7. 测试:
   - 是否覆盖正常用例;
   - 是否覆盖异常用例;
   - 是否覆盖边界条件;
   - 是否覆盖并发或重复请求。

这样的清单比“帮我看看代码”更稳定,也更容易在团队中复用。


八、AI 辅助生成单元测试用例

Review 完代码后,可以继续让 AI 生成测试用例。

Prompt 示例:

请根据上面的地址新增接口,生成单元测试和接口测试用例。

要求:
1. 覆盖正常新增地址;
2. 覆盖必填字段为空;
3. 覆盖手机号格式错误;
4. 覆盖 defaultAddress 为 null、false、true;
5. 覆盖用户已有默认地址时新增默认地址;
6. 覆盖地址数量超过 20;
7. 覆盖越权风险;
8. 标注哪些用例需要 Mock 登录态;
9. 标注哪些用例需要数据库事务验证。

输出格式:
用例名称 / 前置条件 / 输入 / 预期结果 / 优先级 / 备注。

可能得到类似表格:

用例名称 前置条件 输入 预期结果 优先级
正常新增非默认地址 用户已登录 defaultAddress=false 新增成功,返回地址 ID P0
正常新增默认地址 用户已登录,已有非默认地址 defaultAddress=true 新增成功,该地址为默认地址 P0
收件人为空 用户已登录 receiverName="" 返回参数错误 P0
手机号格式错误 用户已登录 phone="123" 返回手机号格式错误 P0
defaultAddress 为 null 用户已登录 defaultAddress=null 按 false 处理或按业务约定处理 P1
用户已有默认地址 用户已登录,已有默认地址 新增 defaultAddress=true 旧默认地址取消,新地址成为默认 P0
地址数量超过限制 用户已有 20 个地址 新增地址 返回地址数量超限 P1
越权传入其他 userId 用户 A 登录 请求中传入用户 B 的 userId 不允许操作用户 B 地址 P0

注意:AI 生成的是测试用例初稿,最终还要根据真实需求补充。


九、示例单元测试草稿

下面是一个 JUnit 5 + Mockito 风格的测试草稿,仅用于说明思路。

@ExtendWith(MockitoExtension.class)
class AddressServiceTest {

    @Mock
    private AddressRepository addressRepository;

    @Mock
    private LoginUserContext loginUserContext;

    @InjectMocks
    private AddressService addressService;

    @Test
    void shouldCreateAddressSuccessfullyWhenRequestValid() {
        CreateAddressRequest request = new CreateAddressRequest();
        request.setReceiverName("张三");
        request.setPhone("13800000000");
        request.setProvince("浙江省");
        request.setCity("杭州市");
        request.setDetailAddress("某某路 1 号");
        request.setDefaultAddress(false);

        when(loginUserContext.getCurrentUserId()).thenReturn(1001L);

        Address savedAddress = new Address();
        savedAddress.setId(1L);

        when(addressRepository.save(any(Address.class))).thenAnswer(invocation -> {
            Address address = invocation.getArgument(0);
            address.setId(1L);
            return address;
        });

        Long id = addressService.createAddress(request);

        assertEquals(1L, id);
        verify(addressRepository, never()).clearDefaultAddress(anyLong());
        verify(addressRepository, times(1)).save(any(Address.class));
    }

    @Test
    void shouldClearOldDefaultAddressWhenCreateDefaultAddress() {
        CreateAddressRequest request = new CreateAddressRequest();
        request.setReceiverName("张三");
        request.setPhone("13800000000");
        request.setProvince("浙江省");
        request.setCity("杭州市");
        request.setDetailAddress("某某路 1 号");
        request.setDefaultAddress(true);

        when(loginUserContext.getCurrentUserId()).thenReturn(1001L);

        when(addressRepository.save(any(Address.class))).thenAnswer(invocation -> {
            Address address = invocation.getArgument(0);
            address.setId(2L);
            return address;
        });

        Long id = addressService.createAddress(request);

        assertEquals(2L, id);
        verify(addressRepository, times(1)).clearDefaultAddress(1001L);
        verify(addressRepository, times(1)).save(any(Address.class));
    }

    @Test
    void shouldThrowExceptionWhenPhoneInvalid() {
        CreateAddressRequest request = new CreateAddressRequest();
        request.setReceiverName("张三");
        request.setPhone("123");
        request.setProvince("浙江省");
        request.setCity("杭州市");
        request.setDetailAddress("某某路 1 号");
        request.setDefaultAddress(false);

        assertThrows(BizException.class, () -> addressService.createAddress(request));

        verify(addressRepository, never()).save(any(Address.class));
    }
}

这类测试代码可以让 AI 生成初稿,但需要人工检查:

  • Mock 对象是否符合项目真实结构;
  • 异常类型是否正确;
  • Repository 方法名是否真实存在;
  • 测试是否覆盖关键业务规则;
  • 断言是否足够明确。

十、AI 输出结果如何验证

AI Review 的结果不能直接当作结论。建议按下面步骤验证。

1. 先区分问题类型

AI 输出的问题可以分成三类:

类型 处理方式
明确代码问题 直接结合代码验证,如空指针、缺少参数校验
业务规则问题 找产品、测试或技术负责人确认
架构建议问题 结合系统复杂度和维护成本评估

例如:

  • “request 可能为空”属于明确代码问题;
  • “defaultAddress 为 null 应该按 false 处理”属于业务规则问题;
  • “建议引入领域模型和策略模式”可能属于过度设计,需要谨慎。

2. 用编译和测试验证代码建议

AI 给出的代码建议必须经过:

  • 编译检查;
  • 单元测试;
  • 接口测试;
  • 静态代码扫描;
  • 代码规范检查;
  • 人工 Review。

尤其注意:

  • AI 可能调用不存在的方法;
  • AI 可能使用项目中没有的工具类;
  • AI 可能忽略事务传播行为;
  • AI 可能生成不符合团队规范的异常处理。

3. 对安全问题单独复核

涉及以下内容时,不要只看 AI 结论:

  • 登录态;
  • 权限校验;
  • 越权访问;
  • 数据脱敏;
  • 资金交易;
  • 订单状态流转;
  • 审计日志;
  • 用户隐私;
  • 文件上传;
  • 外部回调。

这些场景必须由有经验的开发者或安全人员复核。


十一、多模型交叉 Review 的使用方式

对重要代码,可以让多个模型分别 Review 同一段代码。

比较实用的方式是:

  1. 用模型 A 输出问题清单;
  2. 用模型 B 检查模型 A 的结论是否有遗漏或误判;
  3. 让模型 C 根据最终问题清单生成测试用例;
  4. 由开发者做最终判断。

例如第二轮可以这样问:

下面是另一个模型对代码 Review 的结果。
请你不要直接接受这些结论,而是逐条判断:

1. 该问题是否成立;
2. 是否存在误判;
3. 是否还有遗漏;
4. 哪些问题需要结合业务确认;
5. 哪些问题必须通过测试验证。

Review 结果:
【粘贴第一轮输出】

这样可以减少单一模型的偏差,但仍然不能替代人工判断。


十二、团队如何沉淀 AI Review 流程

如果团队希望长期使用 AI 辅助代码 Review,可以从轻量方式开始。

1. 建立通用 Prompt 模板

按语言和场景维护几类模板:

  • Java 后端接口 Review;
  • SQL Review;
  • 前端组件 Review;
  • 单元测试 Review;
  • 接口文档 Review;
  • 异常处理 Review;
  • 安全风险 Review。

2. 在提交 PR 前自查

可以要求开发者在提交 PR 前完成:

  • AI Review 一轮;
  • 修复明确问题;
  • 标注未采纳建议原因;
  • 补充测试用例;
  • 在 PR 描述中说明风险点。

PR 描述可以参考:

本次变更:
- 新增用户地址创建接口;
- 支持设置默认地址;
- 增加参数校验;
- 增加地址数量限制。

已自查:
- 已处理 request 为空问题;
- userId 改为从登录态获取;
- defaultAddress=true 时增加事务处理;
- 已补充手机号格式错误、默认地址切换等测试。

待确认:
- defaultAddress=null 时是否按 false 处理;
- 地址数量上限是否固定为 20;
- 详细地址最大长度是否为 200。

3. 明确哪些场景不能只依赖 AI

例如:

  • 核心交易链路;
  • 权限系统;
  • 数据库迁移;
  • 大规模重构;
  • 性能优化;
  • 安全加固;
  • 用户隐私处理。

这些场景可以使用 AI 辅助整理思路,但必须走团队正式评审流程。


十三、注意事项:不要把敏感信息直接发给 AI

使用 AI 做代码 Review 时,要特别注意输入内容。

不建议直接输入:

  • 公司未公开完整源码;
  • 生产数据库连接;
  • API Key;
  • Token;
  • 用户手机号、身份证、邮箱;
  • 内部服务域名;
  • 访问控制策略;
  • 生产环境完整日志;
  • 商业敏感规则。

如果确实需要分析代码或日志,建议:

  • 只截取最小必要代码片段;
  • 替换真实域名和账号;
  • 删除密钥和 Token;
  • 对用户数据脱敏;
  • 避免上传完整项目;
  • 遵守公司内部安全规范。

十四、常见问题

1. AI 能不能替代人工代码 Review?

不能。AI 适合发现明显问题和补充检查视角,但最终 Review 仍然需要开发者结合业务、架构、测试和上线风险判断。

2. AI Review 适合检查哪些问题?

比较适合检查:

  • 空值风险;
  • 参数校验;
  • 异常处理;
  • 重复代码;
  • 简单性能问题;
  • 测试遗漏;
  • 命名和可读性问题。

对于复杂架构、安全策略、业务状态机,需要人工复核。

3. 代码是不是贴得越多越好?

不是。更好的方式是提供最小相关上下文,包括:

  • 需求背景;
  • 修改目标;
  • 相关代码;
  • 调用关系;
  • 已知约束;
  • 期望输出格式。

过多无关代码会干扰模型判断,也可能带来安全风险。

4. 多模型 Review 有必要吗?

普通代码改动不一定需要。
但涉及权限、订单、支付、库存、并发、数据一致性等场景时,多模型交叉 Review 可以帮助发现遗漏,但仍要以测试和人工判断为准。

5. AI 生成的单元测试能直接用吗?

通常不能直接用。AI 生成的测试代码可能调用不存在的方法,或者断言不符合真实业务。更适合当作测试思路初稿。


十五、总结

AI 辅助代码 Review 的价值,不在于替开发者“拍板”,而在于提前暴露问题。

一套比较稳妥的流程是:

  1. 提供需求背景和最小相关代码;
  2. 用明确 Prompt 约束 Review 维度;
  3. 让 AI 输出结构化问题清单;
  4. 区分明确问题、业务问题和架构建议;
  5. 对代码建议进行编译、测试和人工验证;
  6. 让 AI 辅助生成测试用例;
  7. 把 Prompt 和 Review 清单沉淀为团队规范。

对开发者来说,AI 最适合做“辅助 Reviewer”。
它可以提高 Review 覆盖面,但不能替代工程责任。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐