上个月,我们有个新功能要做:实现一个订单退款接口。
需求不复杂,但涉及金额变更、库存回滚、状态流转,刚好是个能拉开差距的典型场景。
当时团队有点忙,我临时把这个需求分别交给了三个"人":
- 小王,工作1年半的初级开发
- 老陈,工作6年的高级开发
- Cursor(我给它写了一个详细的Prompt)
三份代码我都仔细看过了。
看完之后,我沉默了很久。
第一个:小王(工作1年半)
我把需求文档发给他,他没有追问,当天下午就说:"做完了,测试通过了。"
速度确实快。打开代码一看:
java
@PostMapping("/refund")
@Transactional
public Result<Boolean> refund(@RequestBody RefundRequest request) {
Order order = orderMapper.selectById(request.getOrderId());
if (order == null) {
throw new BusinessException("订单不存在");
}
if (order.getStatus() != OrderStatus.PAID) {
throw new BusinessException("订单状态不支持退款");
}
// 退款金额写回
order.setStatus(OrderStatus.REFUNDED);
order.setRefundAmount(request.getRefundAmount());
orderMapper.updateById(order);
// 调用支付退款接口
payService.refund(order.getPaymentNo(), request.getRefundAmount());
// 库存回滚
inventoryService.rollback(order.getProductId(), order.getQuantity());
return Result.success(true);
}
功能逻辑是对的,基本流程都有,单元测试也过了。
但我看了两分钟,发现了几个问题:
问题1:没有幂等控制。 如果这个接口被重复调用两次(网络超时重试是很常见的),订单会被退款两次,库存回滚两次。这是生产事故级别的bug。
问题2:退款金额没有校验上限。 request.getRefundAmount() 没有和订单实际支付金额做比对,理论上可以传一个比实际支付金额更大的数字。
问题3:payService.refund 失败后没有补偿机制。 调用第三方支付退款失败了怎么办?状态已经改了,但钱没退出去,这个烂摊子谁来收拾?
我去找小王聊。
他说:"我以为测试通过了就行。"
这就是问题所在。他交付的是一个"能跑的功能",不是一个"可以上生产的方案"。
他想到了正常流程,但没有想过任何一个异常流程。
第二个:老陈(工作6年)
老陈拿到需求之后,没有立刻动手。
他先发了一条消息给我,问了3个问题:
- 退款是全额退款还是支持部分退款?
- 退款金额打回原路还是可以退到其他账户?
- 退款后库存需要实时回滚,还是等退款成功回调之后再回滚?
我当时看到这3个问题,愣了一下。
这3个问题,需求文档里都没写清楚。
我去问了产品,产品想了半天,说:"支持部分退款,退回原路,库存等退款成功回调之后再回滚。"
这3个答案,直接影响了整个接口的设计。如果不提前搞清楚,后面改起来成本极高。
老陈花了半天设计,画了一个退款状态机:
PAID → REFUNDING → REFUNDED(退款成功)
→ REFUND_FAILED(退款失败)
然后核心实现加了分布式锁防并发,加了幂等Key防重复提交,支付回调之后再触发库存回滚:
java
@PostMapping("/refund")
@Transactional
public Result<Boolean> refund(@RequestBody RefundRequest request) {
// 幂等校验
String idempotentKey = "refund:" + request.getOrderId() + ":" + request.getRefundNo();
if (!redisLock.tryLock(idempotentKey, 10, TimeUnit.SECONDS)) {
throw new BusinessException("请勿重复提交退款申请");
}
try {
Order order = orderMapper.selectById(request.getOrderId());
// 金额校验:退款金额不能超过实际支付金额
if (request.getRefundAmount().compareTo(order.getPayAmount()) > 0) {
throw new BusinessException("退款金额不能超过支付金额");
}
// 更新状态为退款中
order.setStatus(OrderStatus.REFUNDING);
orderMapper.updateById(order);
// 调用支付退款,异步等待回调
payService.refund(order.getPaymentNo(), request.getRefundAmount(), request.getRefundNo());
return Result.success(true);
} finally {
redisLock.unlock(idempotentKey);
}
}
代码质量明显比小王高了一个档次。
但我也发现了一个问题:他额外设计了一套"退款规则引擎",支持按商品类型配置不同的退款策略。
我问他:"这个规则引擎现在用得上吗?"
他说:"现在用不上,但以后可能会扩展。"
我沉默了一下。
这个功能,我们至少6个月内不会用到。提前设计意味着提前写代码、提前测试、提前维护。这不是严谨,这是过度设计。
他交付的是一个高质量的方案,但在某些地方,超出了当前的实际需要。
第三个:Cursor(AI)
我给Cursor写了一个比较详细的Prompt:
实现一个订单退款接口,要求:
1. 支持部分退款,退款金额不能超过支付金额
2. 加分布式锁防并发,加幂等Key防重复提交
3. 退款成功后的库存回滚由支付回调触发,本接口只负责发起退款
4. 退款状态:REFUNDING → REFUNDED / REFUND_FAILED
5. 技术栈:Spring Boot 3, MyBatis-Plus, Redis
6. 异常用BusinessException,返回值用Result<T>
5分钟后,Cursor生成了一份代码,大约200行。
代码结构清晰,异常处理很全面,甚至考虑了我没有提到的一些边界情况,比如订单不存在、订单已经在退款中等等。
但我发现了一个问题。
它生成的幂等Key是这样的:
java
String idempotentKey = "refund:" + request.getOrderId();
只用了订单ID。
这意味着,同一个订单如果第一次退款50元失败了,第二次想重新发起退款请求,会被直接拦截------因为幂等Key一样。
正确的做法是用"订单ID + 退款单号"作为幂等Key,每次退款请求都有独立的退款单号,互不影响。
这不是一个语法错误,测试也不会暴露它。这是一个业务逻辑错误,只有理解了退款业务流程的人,才能发现它。
除此之外,AI还有一个地方明显不足:它不知道我们公司有一条内部规定------VIP用户的退款走单独的优先通道,需要先判断用户等级。
这条规则在任何文档里都没有,只存在于老员工的脑子里。
AI不知道,所以它没有处理。
它交付的是一段逻辑正确、结构清晰的代码,但不是一个理解了我们业务的方案。
我为什么沉默了
看完三份代码,我坐在那里想了很久。
我沉默,不是因为AI赢了。
也不是因为高级程序员赢了。
我沉默,是因为我意识到一件事:
小王能做的事,AI已经做得更快、更全面了。
小王花了半天写的代码,Cursor用了5分钟,而且覆盖了更多边界情况。如果小王继续只是"接需求、写代码、测试通过就交差",他的竞争对手已经不是其他初级程序员了,而是每个人手里的AI工具。
但老陈的价值,也不在于他写的那200行代码。
他的价值,在于那3个追问。
这3个问题,AI问不出来,因为AI不知道哪些地方需要追问。这3个问题,小王也没想到,因为他的经验还不够。
只有老陈,在拿到需求的第一时间,就知道这里有3个坑,需要先搞清楚再动手。
这才是高级程序员真正值钱的地方。
不是他写代码的速度,不是他记住了多少API,而是他理解业务的深度 ,以及由此产生的判断力------知道哪里有坑,知道哪里要追问,知道哪里可以简化,知道哪里不能省。
这个判断力,是AI替代不了的。
3点真实的建议
做完这个对比,我想说3件具体的事。
第一,如果你是初级程序员,现在最重要的一件事是:养成追问需求的习惯。
拿到需求,先别动手写代码。先问自己:这里有没有没写清楚的地方?有没有异常情况没有覆盖?有没有将来可能变化的部分?
这个习惯,是从初级到高级最关键的跨越。代码写得快不快,不是差距所在。
第二,用AI提升执行效率,但把省出来的时间投入到理解业务上,而不是摸鱼。
AI帮你把写代码的时间从半天压缩到1小时,这1小时不是用来早点下班的,是用来去跟产品聊、去理解这个功能背后的业务逻辑、去想那些代码之外的问题。
第三,学会review AI的代码,重点找那20%它不懂的地方。
AI的代码,功能逻辑通常是对的,结构通常是清晰的。但它不知道你们公司的内部规定,不知道你们业务的特殊场景,不知道这个幂等Key的设计是否符合你们的退款流程。
这20%,就是你的价值所在。
最后说一句让我印象最深的话。
事后我问老陈,你怎么看AI写的代码?
他看了一眼,说:
"代码写得不错。但它不知道我们昨天刚开完会,决定把VIP退款改成异步处理。"
我笑了。
AI让写代码变容易了,但让成为一个好程序员变得更难了。
因为你再也没有借口说"我没时间思考"了。
你们团队有没有类似的经历?欢迎评论区聊聊。
后端AI实验室 不讲概念,只谈实战 代码开源,每周更新