
目录一、一句话需求为什么换不来一次合并一能编译、能演示、能合并是三件事二失控的四种典型现场三五段闭环把生成关进流程里二、开工之前给AI一个有边界的工作台一示例功能与验收标准二隔离的工作区分支与工作树三写进仓库的规则记忆文件与命令清单三、第一段分析代码——只读优先一分析提示词明确要什么、禁止什么二四样交付物清单、链路、复用点、疑问三怎么判断AI真的读懂了四分析报告也要落盘四、第二段制定计划——把方案钉进文件一计划模式的机制价值二一份可执行计划长什么样三人工审计计划的三个焦点1、隐藏范围2、测试设计是否证明行为3、业务规则谁来定四计划落盘从聊天记录到需求文件五、第三段实现功能——在边界内执行一实现提示词的硬约束二核心代码状态机、幂等、金额与并发1、幂等入口请求号先行2、金额账本BigDecimal与超额校验3、并发防线条件更新4、审计闭环谁在什么时候退了多少三实现期的纠偏小步反馈而不是推倒重来六、第四段运行测试——让证据代替感觉一两层运行先定向、后全量二AI补测试的清单三修复回路与叫停条件七、第五段审查差异——最后一道人工闸门一先看改了什么再看改得对不对二第二视角让另一个会话做只读复核三提交、PR与可追溯性八、从一次跑通到长期可用一验证成本决定委托边界二风险分级不是所有环节都值得自动化三常见失败模式与对策四给团队的落地清单九、结语参考资料干货分享感谢您的阅读把一句话需求丢给编码智能体几分钟后拿回一屏幕新代码——这件事在今天已经不稀奇。稀奇的是这些代码里有多少能在当天通过评审、合并进主干、并且三个月后没有人骂它。很多工程师的体感是割裂的AI生成代码的速度是自己手写的十倍但从生成完毕到可以合并之间的距离有时反而比自己从头写还要长。差距不在模型能力而在过程控制——生成是一瞬间的事合并是一个流程的事。这回我们用一个完整的Java功能作为贯穿案例把用AI做完一个功能拆成五个可验证的阶段分析代码、制定计划、实现功能、运行测试、审查差异。每个阶段都回答三个问题这一步让AI做什么、禁止它做什么、人在哪个位置做什么判断。思考后结合自己以往的工作经历这次案例选择的是订单系统里一个足够真实的需求——管理员对已支付订单发起部分退款。它牵扯状态机、金额精度、幂等、并发和审计日志小到一个人一天能做完又复杂到足以暴露直接让AI生成会踩的所有坑。一、一句话需求为什么换不来一次合并一能编译、能演示、能合并是三件事评价一段AI生成的代码存在三个从低到高的门槛。第一个门槛是能编译类型对得上、依赖找得到、构建不报错。当前主流编码智能体跨过这个门槛的成功率已经相当高因为编译器是最便宜也最严格的验证器模型可以拿着报错信息自己迭代。第二个门槛是能演示正常路径跑得通接口调用返回预期结果。这一层多数时候也能达到因为正常路径恰恰是需求描述里写得最清楚的部分。真正的分水岭是第三个门槛能合并。能合并意味着代码尊重项目既有的架构分层、复用了已经存在的基础设施、没有绕过团队的业务约束、覆盖了异常与并发路径的测试、并且差异范围干净到评审者愿意为它签字。这三个门槛考察的是完全不同的东西——前两个考察模型的生成能力第三个考察的是整个开发过程有没有被组织好。一句话需求之所以换不来一次合并是因为它只为第一、二个门槛提供了信息第三个门槛需要的信息全部缺席。二失控的四种典型现场把给订单系统加一个部分退款功能直接丢给编码智能体常见的失控现场有四种每一种都对应一类缺失的过程控制。第一种是平行发明。项目里已经有统一的异常体系、审计日志组件和支付渠道客户端AI没有找到它们于是自己发明了一套新的异常基类、手写的日志表插入、重新封装的HTTP调用。代码本身没有错但它是一座孤岛评审者要么全部推翻要么接受项目里从此有两套做法。这对应的缺失是分析阶段——没有人要求AI先读懂项目再动手。第二种是边界扩张。任务是加退款diff里却出现了对既有支付方法的顺手重构、对公共DTO的字段调整、以及一批与需求无关的格式化改动。每一处单看都说得过去合在一起让评审无从下手。这对应的缺失是计划阶段——没有一份事先圈定允许修改哪些文件的清单。第三种是快乐路径工程。生成的代码在演示时完美工作但没有回答这些问题同一笔退款请求重放两次会不会退两次钱两个管理员同时操作同一订单会发生什么退款金额恰好等于剩余可退金额时状态该落在哪里金额用了double会不会在第七位小数上亏一分钱这对应的缺失是测试阶段——没有人把异常与并发场景变成必须通过的用例。第四种是无人审计的合并。测试全绿之后直接提交三周后才发现AI在实现里悄悄放宽了一个状态校验或者把带着敏感信息的入参原样打进了日志。这对应的缺失是差异审查阶段——绿色的测试只能证明测过的行为是对的不能证明没测过的改动是无害的。三五段闭环把生成关进流程里四种失控现场对应的解法合起来就是本文的主线把一个功能的交付拆成五段每段有明确的输入、输出和验收标准段与段之间设置人工确认点。第一段是分析代码AI只读不写交付物是文件清单、调用链、可复用组件和待确认的业务问题。第二段是制定计划AI基于分析产出一份文件级的实施方案人来审计范围、依赖和测试设计确认后落盘为需求文件。第三段是实现功能AI在计划圈定的边界内修改代码硬约束写在提示词里。第四段是运行测试先跑定向用例快速反馈再跑全量构建防回归失败进入有限次数的修复回路。第五段是审查差异人以及一个只读的第二视角逐行看diff确认改动与计划一致后才允许提交。这个结构不是为了仪式感。它的每一段都在回答同一个工程问题这一步的产出用什么办法验证分析的产出用人的项目知识验证计划的产出用业务规则验证实现的产出用编译器和测试验证最终的差异用人的评审验证。验证手段齐备的环节可以放心交给AI迭代验证手段缺失的环节必须由人把守——这一条原则会贯穿全文并在第八章展开成一个可操作的判断框架。二、开工之前给AI一个有边界的工作台一示例功能与验收标准先把贯穿全文的需求钉死。业务方的原始诉求是一句话客服处理售后时管理员要能给已支付的订单退一部分钱。在进入任何工具之前第一件事是人来把这句话翻译成可验证的验收标准仅PAID已支付与PARTIALLY_REFUNDED已部分退款状态的订单允许发起退款单笔退款金额必须大于零且累计退款总额不得超过订单实付金额退款后若累计退款等于实付金额订单状态迁移为REFUNDED已全额退款否则迁移为PARTIALLY_REFUNDED调用方必须携带全局唯一的请求号requestId同一请求号重复提交按幂等处理返回首次受理结果不重复打款两个并发请求操作同一订单时最多只有一个成功另一个必须得到明确的冲突错误而不是静默覆盖每次退款记录操作人、退款原因、金额与时间写入既有审计日志体系金额一律使用BigDecimal保留两位小数不修改既有表结构退款流水使用项目中已存在的refund_record表该表对request_id建有唯一索引。这份清单是整个流程的锚点。后面每一段的提示词都会引用它最后的差异审查也拿它当对照表。值得强调的是这一步没有任何AI参与——哪些状态可退、超额怎么处理、幂等语义是什么这些是业务决策AI最多能帮你发现这里有一个没定义的边界不能替你定义它。二隔离的工作区分支与工作树AI修改代码之前先给它一个可以整体丢弃的空间。最低要求是一个独立分支git checkout -b feature/order-partial-refund分支是安全边界。它保证无论这次实验产出什么都可以用一次git branch -D干净地回退主干不会沾染任何未经验证的改动。如果你需要在同一台机器上并行处理多个任务——比如一边让AI做退款功能一边自己修一个线上问题——git的工作树机制可以为每个任务提供物理隔离的目录git worktree add ../order-refund-wt feature/order-partial-refund两个检出各自独立文件互不干扰从根上消除了两个会话改了同一个文件的冲突。主流编码智能体已经把这一步产品化例如Claude Code的--worktree参数会自动创建并管理工作树多智能体框架则默认为每个任务分配隔离工作区。手段可以不同原则只有一条AI的每一次实验都应该发生在可以整体作废的空间里。三写进仓库的规则记忆文件与命令清单复杂项目里有大量每次都要交代的背景构建命令、架构分层、编码红线、禁止触碰的区域。与其每个会话重新解释不如写成一份仓库内的记忆文件按工具约定命名为CLAUDE.md、AGENTS.md或规则文件让智能体每次启动自动加载# 项目速览 Spring Boot 3 订单服务Java 21MyBatis MySQLJUnit 5 Mockito。 ## 构建与测试 - 定向测试mvn -DtestOrderRefundServiceTest test - 全量校验mvn clean verify ## 架构边界 - Controller 只做参数校验与编排业务规则在 service 层 - 订单状态变更必须走条件更新校验旧状态 - 金额一律 BigDecimal禁止 float/double - 审计日志统一走 OrderAuditLogRepository禁止自建日志表 ## 修改纪律 - 未经确认不改表结构、不加第三方依赖、不动公共异常格式 - 不自动执行 git commit / push - 不做与任务无关的格式化与顺手重构这份文件的写法有讲究放进去的应该是长期稳定的规则与命令而不是某一个需求的临时说明——临时性的内容属于第四章要讲的需求文件两者混在一起会让规则文件迅速膨胀失效。一个有效的检验标准是文件里的每一条是否在任何任务中都成立成立的留下不成立的挪走。三、第一段分析代码——只读优先一分析提示词明确要什么、禁止什么工作台就绪后第一条指令不是实现退款而是读懂项目。给分析阶段的提示词要同时做到两点列出必须回答的问题明确禁止修改任何文件。请分析当前订单服务项目本阶段禁止修改任何文件。 需要确认 1. 订单状态枚举的定义位置与现有状态迁移规则 2. 支付成功后的实付金额存储在哪个字段精度如何处理 3. refund_record 表对应的实体、Mapper 与现有读写位置 4. 支付渠道退款接口的客户端封装及其幂等语义 5. 审计日志的统一写入方式与既有事件类型 6. 管理端接口的权限校验是如何组织的 7. 项目中是否已存在部分退款、取消或逆向流程的类似实现 8. 与订单状态和退款相关的现有测试及其运行方式。 输出相关文件清单含路径、主要调用链、可直接复用的组件、 需要人工决策的业务问题。暂时不要给出任何代码修改建议。禁止修改不只是嘴上说说。多数编码工具提供了机制级的只读保障Claude Code的计划模式plan mode下模型可以读文件、搜代码但任何写操作都会被拦截直到人批准方案其他工具也有类似的只读会话或权限模式。用机制保证只读比在提示词里恳求请不要改代码可靠得多——这也是一个通用原则能用权限约束的事情不要依赖模型的自觉。另一个实用技巧是委托检索。分析大项目会产生海量的文件读取这些内容会挤占主会话的上下文窗口。支持子智能体subagent的工具可以把翻文件外包出去子智能体在自己的上下文里读几十个文件只把结论带回主会话。主会话保持干净后续计划与实现阶段的质量都会受益。二四样交付物清单、链路、复用点、疑问分析阶段合格的产出是四样东西缺一样都不该进入下一段。第一样是文件清单与本次需求相关的实体、服务、Mapper、配置和测试的具体路径。它是后面圈定修改边界的原材料。第二样是调用链从管理端接口到订单落库、从支付回调到状态迁移的完整路径让人能判断新功能应该挂接在哪一层。第三样是可复用组件这个项目里已经有的东西——统一异常体系、条件更新的Mapper方法、审计日志仓储、支付渠道客户端。这份清单直接决定AI会不会平行发明。第四样是待确认问题这往往是最有价值的部分。一次合格的分析应该能提出这样的问题refund_record表有status字段取值PENDING/SUCCEEDED/FAILED退款是同步成功还是要等渠道回调累计已退金额应该统计SUCCEEDED还是包含PENDING——这个问题直接影响超额校验的口径答案只能由人给出。三怎么判断AI真的读懂了分析报告本身也需要验证验证者是人的项目知识。有效的办法是抽查你已经知道答案的问题项目里明明有一个定时任务在扫描超时订单报告有没有提到它支付客户端的重试逻辑写在拦截器里报告有没有定位到如果AI遗漏了你知道的关键事实说明它对项目的理解存在盲区此时进入实现阶段的风险极高——它大概率会在盲区里平行发明。正确的动作是补充提示请再看一下scheduler包下的超时处理让它修正报告直到分析质量过关。这一段多花十分钟后面能省一小时。四分析报告也要落盘和后面的计划一样确认过的分析报告值得保存为仓库内的文件例如docs/analysis/order-refund-context.md。它有两个后续用途一是实现阶段开新会话时把它和需求文件一起作为起点新会话不必重新读一遍项目二是三个月后另一个人或另一个智能体接手逆向流程相关的需求时这份退款上下文地图可以直接复用。分析是全流程里最贵的智力投入之一让它以文件形式沉淀下来而不是随会话结束蒸发是这套流程从一次跑通走向长期可用的关键一环。四、第二段制定计划——把方案钉进文件一计划模式的机制价值分析确认后让AI输出实施计划此时依然不允许它改代码。很多工具把这一步做成了显式的工作模式模型给出完整方案人批准后才解锁写权限。这个批准闸门的价值在于它把决策时刻前置了——在计划阶段否决一个方案的成本是几句话在实现之后否决它的成本是几百行diff和已经花掉的时间。经济学上这叫把昂贵的分歧放到便宜的时刻去解决。二一份可执行计划长什么样给计划阶段的提示词要把验收标准完整带上并规定输出结构基于前面的分析为管理员部分退款生成实施计划暂不修改代码。 业务约束此处粘贴第二章的八条验收标准 输出格式 - 需求理解用你的话复述暴露理解偏差 - 拟修改/新增的文件清单每个文件的具体改动内容 - 事务边界与失败回滚策略 - 并发与幂等的处理方案 - 计划新增的测试用例列表含异常与并发场景 - 明确不做的事情Out of Scope - 仍不确定、需要人拍板的问题一份合格的计划应该具体到文件级看起来像这样1. OrderRefundController.java新增 - POST /admin/orders/{id}/refunds接收金额、原因、requestId - 复用 AdminAuthInterceptor 既有权限校验 2. OrderRefundService.java新增 - 幂等检查 → 状态校验 → 超额校验 → 落流水 → 条件更新状态 → 调渠道退款 → 写审计日志整体一个事务 3. OrderMapper.java修改 - 增加 updateStatusIfMatch(orderId, fromStatus, toStatus) 4. RefundRecordMapper.java修改 - 增加 findByRequestId 与 sumSucceededAmount 5. OrderRefundServiceTest.java新增 - 正常部分退款 / 恰好退完转 REFUNDED / 超额拒绝 / 幂等重放 / 非法状态拒绝 / 并发条件更新失败 / 金额精度 不做退款审批流、批量退款、渠道回调对账另立需求 待定渠道退款失败时流水表状态标 FAILED 后是否自动重试计划的七个要素里最容易被忽略的是明确不做的事情。范围蔓延多数不是从大改动开始的而是从顺手把审批流也加了开始的。把Out of Scope写进计划等于给了后续差异审查一把量尺。三人工审计计划的三个焦点计划到手人的审计聚焦三件事。1、隐藏范围逐条检查文件清单有没有计划之外的公共API改动有没有新增第三方依赖有没有触碰表结构特别警惕为了实现A顺便调整了B式的措辞——那通常是范围扩张的起点。本例中如果计划提出给Order实体加一个refundedAmount冗余字段以免每次求和就触碰了不改表结构的红线必须打回累计金额从refund_record表实时求和性能问题留给未来有数据支撑时再议。2、测试设计是否证明行为看计划里的测试列表是不是在证明验收标准而不是在锻炼刚写的函数。一个危险信号是测试用例与实现结构一一对应每个私有方法一个测试却没有覆盖同一请求号重放不重复打款这种行为级承诺。测试应该锚定在需求上实现怎么重构都不该动摇它们。3、业务规则谁来定计划里的待定问题必须逐条给出人的答案不能让AI用默认值蒙混过去。本例拍板渠道退款同步返回受理结果受理即标SUCCEEDED简化模型回调对账另立需求累计已退金额只统计SUCCEEDED。这些决定连同计划一起落盘。四计划落盘从聊天记录到需求文件确认后的计划不能只活在聊天窗口里——换一个会话、换一个工具、换一个人上下文就丢了。把它保存为仓库内的需求文件docs/requirements/order-partial-refund.md这份文件从此成为唯一事实源实现阶段的提示词引用它测试用例对照它差异审查拿它当验收依据。它和第二章的规则文件分工明确——规则文件管这个项目永远如此需求文件管这个任务具体如此。五、第三段实现功能——在边界内执行一实现提示词的硬约束现在才轮到写代码。给实现阶段的指令不是重复业务需求而是指向需求文件并附上硬约束请根据 docs/requirements/order-partial-refund.md 实现部分退款功能。 只允许修改计划中列出的五个文件。 约束 1. 保持既有架构分层与异常响应格式 2. 金额一律 BigDecimalscale2舍入 RoundingMode.HALF_UP 3. 状态更新必须使用条件更新禁止 read-modify-write 直接 save 4. 幂等依赖 request_id 唯一索引捕获重复键冲突按幂等成功处理 5. 每完成一个文件运行 mvn -DtestOrderRefundServiceTest test 6. 不自动提交 git 7. 发现计划与现有代码冲突时停下来说明不要自行变通。第七条值得单独说明。实现过程中AI一定会遇到计划没有预料的情况——某个方法签名和分析时不一致、某个组件其实不支持预想的用法。糟糕的智能体行为是自行变通悄悄绕开障碍把变通埋进diff里。好的行为是抛出异常给人停下、说明冲突、等待决策。这条约束就是在明确要求后者。二核心代码状态机、幂等、金额与并发以下是本例核心服务的实现完整可编译代码见交付包source/demo-code目录四个设计点逐一说明。Service RequiredArgsConstructor public class OrderRefundService { private static final int AMOUNT_SCALE 2; private final OrderRepository orderRepository; private final RefundRecordRepository refundRecordRepository; private final PaymentGatewayClient paymentGatewayClient; private final OrderAuditLogRepository auditLogRepository; Transactional public RefundResult applyRefund(RefundCommand command) { OptionalRefundRecord existing refundRecordRepository.findByRequestId(command.getRequestId()); if (existing.isPresent()) { return RefundResult.replayed(existing.get()); } Order order orderRepository.findById(command.getOrderId()) .orElseThrow(() - new OrderNotFoundException(command.getOrderId())); if (!order.getStatus().refundable()) { throw new OrderStatusNotAllowedException(order.getId(), order.getStatus()); } BigDecimal amount command.getAmount() .setScale(AMOUNT_SCALE, RoundingMode.HALF_UP); if (amount.compareTo(BigDecimal.ZERO) 0) { throw new InvalidRefundAmountException(order.getId(), amount); } BigDecimal refunded refundRecordRepository.sumSucceededAmount(order.getId()); BigDecimal remaining order.getPaidAmount().subtract(refunded); if (amount.compareTo(remaining) 0) { throw new RefundAmountExceededException(order.getId(), amount, remaining); } RefundRecord record RefundRecord.accepted(order.getId(), command.getRequestId(), amount, command.getOperatorId()); try { refundRecordRepository.insert(record); } catch (DuplicateKeyException e) { RefundRecord winner refundRecordRepository .findByRequestId(command.getRequestId()) .orElseThrow(() - new RefundStateInconsistentException( command.getRequestId(), e)); return RefundResult.replayed(winner); } OrderStatus target amount.compareTo(remaining) 0 ? OrderStatus.REFUNDED : OrderStatus.PARTIALLY_REFUNDED; int updatedRows orderRepository.updateStatusIfMatch( order.getId(), order.getStatus(), target); if (updatedRows ! 1) { throw new ConcurrentOrderUpdateException(order.getId()); } paymentGatewayClient.refund( order.getPayTradeNo(), command.getRequestId(), amount); auditLogRepository.save(OrderAuditLog.refund(order.getId(), command.getOperatorId(), command.getReason(), amount, LocalDateTime.now())); return RefundResult.accepted(record, target); } }1、幂等入口请求号先行方法第一步不是查订单而是查请求号。重放的请求在这里直接返回首次结果根本不进入业务逻辑。但仅有前置检查不够——两个携带相同requestId的请求可能同时通过检查所以真正的防线是request_id上的唯一索引后到者在insert时收到DuplicateKeyException代码显式捕获它、回查首次记录、按幂等成功返回。注意这个catch块做了实事而不是吞掉异常且回查失败时抛出携带原始异常的明确错误——静默的空catch在这里等于埋雷。2、金额账本BigDecimal与超额校验金额进门先统一精度setScale(2, HALF_UP)再做两道校验必须为正、不得超过剩余可退额度。剩余额度来自实付金额减去已成功退款的实时求和而不是任何冗余字段——这正是计划审计时拍板的口径。所有比较使用compareTo而非equals因为BigDecimal的equals会把1.0和1.00判为不等这是AI生成金额代码时的高频陷阱值得写进规则文件。3、并发防线条件更新状态迁移使用条件更新而不是读改写UPDATE orders SET status #{toStatus}, updated_at CURRENT_TIMESTAMP WHERE id #{orderId} AND status #{fromStatus};受影响行数为1说明本请求赢得了状态迁移为0说明订单状态在读取之后已被并发请求改变此时抛出冲突异常整个事务回滚流水记录一并作废。两个并发退款最多一个成功另一个得到明确的冲突响应——这正是验收标准要求的语义。4、审计闭环谁在什么时候退了多少审计日志复用既有仓储与主事务一起提交记录操作人、原因、金额与时间。注意日志内容不包含用户手机号等敏感信息——差异审查阶段会专门检查这一条。三实现期的纠偏小步反馈而不是推倒重来实现阶段人不需要盯着每一行生成但需要在AI每完成一个文件、跑完一轮定向测试的节点上快速扫一眼。发现方向偏了纠偏的方式是增量反馈条件更新的fromStatus应该用读取时的状态不是写死PAID而不是清空重来。上下文里已经积累的项目理解是有价值的资产推倒重来等于把分析阶段的投入清零。只有当会话明显陷入自我缠绕——反复在两个错误方案之间横跳——才值得开新会话并把需求文件作为新会话的起点。这也再次说明了把计划落盘的意义会话可以作废文件不会。六、第四段运行测试——让证据代替感觉一两层运行先定向、后全量测试运行分两层节奏完全不同。第一层是定向测试只跑本需求的用例秒级反馈实现过程中高频执行mvn -DtestOrderRefundServiceTest test第二层是全量校验跑完整个模块的测试与构建分钟级在实现收尾时执行mvn clean verify两层缺一不可。只跑定向测试无法发现退款改动弄坏了取消订单的旧用例这类回归只跑全量反馈周期太长AI的修复迭代效率会掉一个数量级。让智能体在迭代中用第一层、在宣布完成前必须通过第二层是成本与置信度的最优组合。还有一个容易被忽略的细节要求AI在报告测试结果时给出原始证据而不是一句测试已通过。具体来说指令里写明报告你执行的完整命令与输出摘要包括用例总数、通过数、失败数与耗时。这条要求防的是两类问题一类是智能体根本没有运行测试却声称通过在长会话末尾并不罕见另一类是它运行了错误范围——比如只跑了一个用例却报告全部通过。原始输出里的用例计数是最难伪造的证据Tests run: 8, Failures: 0和一句空泛的测试通过置信度完全不同。二AI补测试的清单让AI补测试时指令同样锚定验收标准而不是实现结构。本例的用例清单正常部分退款状态迁移为PARTIALLY_REFUNDED渠道被调用审计日志落库恰好退完累计等于实付时状态迁移为REFUNDED超额拒绝申请金额大于剩余额度时抛出明确异常渠道不被调用幂等重放相同requestId第二次提交返回首次结果渠道只被调用一次并发插入冲突insert抛出重复键异常时回查并按幂等返回非法状态PENDING_PAYMENT、CLOSED状态的订单直接拒绝并发状态变更条件更新返回0行时抛出冲突异常且事务回滚金额精度传入10.005按HALF_UP落为10.010.00被拒绝。其中幂等重放用例的结构值得展示它验证的是行为承诺而非方法调用Test DisplayName(申请退款-相同请求号重复提交-应返回首次结果且不重复调用渠道) void applyRefund_whenRequestIdReplayed_shouldReturnFirstResultWithoutSecondGatewayCall() { // Arrange RefundCommand command refundCommand(REQ-001, new BigDecimal(30.00)); given(refundRecordRepository.findByRequestId(REQ-001)) .willReturn(Optional.of(succeededRecord(REQ-001))); // Act RefundResult result orderRefundService.applyRefund(command); // Assert assertThat(result.replayed()).isTrue(); then(paymentGatewayClient).shouldHaveNoInteractions(); then(orderRepository).shouldHaveNoMoreInteractions(); }Mock的纪律要在指令里说清楚仓储、渠道客户端、时间获取必须Mock外部依赖被测服务本身、RefundCommand这类纯数据对象、BigDecimal运算禁止Mock。AI在无约束时倾向于过度Mock产出验证了每个Mock被调用、却什么行为都没证明的测试这类测试全绿也毫无置信度。三修复回路与叫停条件测试失败后进入修复回路AI读失败输出、定位原因、修改、重跑。这个回路必须限次。经验上限是同一个失败修三次三次之内AI通常能收敛超过三次还在失败几乎总是意味着更深的问题——计划遗漏了某个前提或者测试本身写错了预期。此时正确动作不是让AI继续磨而是人介入判断是回到计划阶段补充设计还是修正测试的断言。还有一条红线要在指令里写死修复的对象是代码不是测试。AI在压力下存在把断言改宽让测试变绿的倾向——比如把assertThat(updatedRows).isEqualTo(1)改成isGreaterThanOrEqualTo(0)。任何对测试文件的修改都必须在差异审查中单独过目凡是放宽断言、删除用例、给测试加忽略注解的改动一律视为高风险信号。七、第五段审查差异——最后一道人工闸门一先看改了什么再看改得对不对测试全绿不是终点。提交之前人要完整地看一遍差异——不是看最终文件的样子而是看这次到底改了什么git diff --stat git diff先看--stat的文件清单有没有计划之外的文件出现在列表里改动行数与预期是否匹配计划里说新增两个方法diff却有四百行一定有问题再逐文件细读对照一份固定的清单是否修改了计划清单之外的文件是否改变了公共方法签名或接口契约是否删除或放宽了既有校验逻辑事务注解与边界是否与计划一致有没有把长耗时的渠道调用不当地圈进事务是否引入了未经批准的依赖或配置变更日志里是否出现手机号、卡号等敏感字段的明文测试文件的改动是否只有新增既有断言有没有被动过是否混入与需求无关的格式化、重命名与顺手重构。这份清单看起来朴素但它拦下的问题类型恰恰是测试拦不住的测试证明写了的行为是对的差异审查负责没写测试的改动是无害的。两者是互补关系不可互相替代。二第二视角让另一个会话做只读复核人工审查之外可以再加一道机器复核——但必须换一个干净的会话并且明确它的角色是只读审查者请审查当前分支相对 main 的 git diff禁止修改任何文件。 对照 docs/requirements/order-partial-refund.md 检查 1. 实现是否完整覆盖验收标准有无遗漏的边界 2. 幂等与并发防线是否存在绕过路径 3. 事务边界是否合理渠道调用失败时数据是否一致 4. 是否存在需求范围之外的改动 5. 测试是否真实证明了行为有无放宽断言的痕迹。 输出高风险问题 / 中风险问题 / 低风险建议 / 需人工确认的事项。换会话不是形式主义。让实现者审查自己的产出它会带着实现时的全部假设去看代码盲区与实现时完全重合一个没有历史包袱的新会话更好的是另一个模型会用新鲜的眼光重读需求与diff这在多智能体工程里对应创作者与审查者分离的制衡模式。审查产出的高风险问题回到实现阶段修复修复后重新过测试与diff——闭环在这里真正闭合。三提交、PR与可追溯性三道检查测试、人工diff、机器复核全部通过后才轮到提交。提交信息交给AI起草没有问题但要求它如实概括改动并引用需求文件路径。推送后创建PR描述里带上验收标准清单与测试证据用例列表、运行结果让评审者不必重新考古。提交的粒度也值得设计。如果功能是分几个阶段实现的先Mapper、再Service、再测试按阶段分别提交比一个大提交更友好每个提交都是一个可回退的检查点评审可以按提交逐段看出问题时git revert的爆炸半径也小。有的团队更进一步要求AI在每个计划步骤完成并通过测试后就地提交提交信息里标注对应的计划步骤编号——这样PR的提交历史本身就是计划执行的轨迹审查者对照需求文件即可逐步核销。到这里一个功能才算真正走完能合并的全程——从一句话需求出发中间经过八个有产出物的节点验收标准、分析报告、实施计划、需求文件、实现diff、测试证据、审查报告、PR。每个节点都可追溯这正是这套流程和一把梭生成的本质区别。八、从一次跑通到长期可用一验证成本决定委托边界走完一遍全流程可以回头提炼那条贯穿始终的原则了一个环节能不能放心交给AI取决于这个环节的产出有没有便宜、可靠的验证器。编译是最便宜的验证器所以类型层面的修改可以完全放手单元测试是便宜且可靠的验证器所以有测试覆盖的实现与重构可以大胆委托让AI对着测试自己迭代构建与静态检查同理。反过来接口契约的合理性、安全边界的完备性、业务规则的正确性——这些没有廉价验证器验证它们的唯一手段是人的判断所以计划审计与差异审查必须由人把守。Java生态在这个框架下有天然优势强类型、成熟的测试设施、严格的构建工具让可委托区比动态语言生态大得多。这也解释了为什么同样的智能体在有测试传统的Java项目里表现明显好于没有测试的遗留系统——差别不在模型在验证器的密度。二风险分级不是所有环节都值得自动化流程跑熟后自然会想把它自动化——用钩子在提交前自动跑测试用CI在PR上自动触发机器复核用定时任务让AI巡检失败的构建。自动化的尺度可以用风险分级来标定只读分析类操作读代码、生成报告、审查diff风险最低可以完全自动;文档、测试补充类修改风险较低合并前人工确认即可涉及接口、权限、数据结构的修改修改前与合并前都要人工确认部署生产、删除数据、变更密钥这类不可逆操作永远不该让AI自动执行。分级的意义在于让自动化有边界地扩张每当你想再自动化一步先问这一步失控的最坏后果是什么、能不能整体回退。自动化的另一个方向是把五段闭环里的机器角色专门化。实践中已经成型的模式是分析与审查用只读会话甚至不同的模型实现用主力编码智能体测试运行交给不需要智能的脚本或轻量模型——用小模型跑测试、解析输出、回传用例到通过与否的映射表成本只有主力模型的零头。角色专门化还有一个副产品各角色的提示词可以沉淀为团队共享的模板分析模板、计划模板、实现约束模板、复核模板新成员照模板即可跑通流程而不必先成为提示词专家。工程能力从个人经验变成团队资产这正是流程化区别于技巧化的地方。三常见失败模式与对策最后把实践中高频出现的失败模式收拢成一张对照表供流程落地时自查。跳过分析直接实现——AI平行发明既有组件对策只读分析先行四样交付物齐了才放行。计划只活在聊天里——换会话即失忆对策确认后的计划落盘为需求文件进版本控制。两个工具同时改代码——同一方法两套实现对策同一阶段只有一个修改者其余角色只读。只跑新增测试——旧功能被静默弄坏对策宣布完成前必须通过全量构建。AI改测试让它变绿——置信度归零对策测试文件的diff单独审放宽断言一票否决。修复回路无限转——时间黑洞对策同一失败限修三次超限人工介入。测试绿了直接提交——未测行为的改动无人审计对策人工diff清单加只读机器复核。规则每次重新交代——质量随会话波动对策稳定规则进记忆文件随仓库演进。四给团队的落地清单把这套流程引入团队不需要一步到位。可操作的起步路径是第一周为仓库补一份记忆文件构建命令、架构边界、修改纪律三节即可并规定所有AI任务必须在独立分支进行第二周在中等规模的需求上强制分析—计划—确认三步体会计划审计拦下问题的时刻第三周把差异审查清单贴进PR模板把测试证据设为PR必填项一个月后再考虑用钩子和CI固化两层测试与机器复核。选练手需求时避开支付、权限等核心链路从文件范围清晰、已有测试基础的模块开始——这套流程的价值要在第一次计划审计发现了隐藏范围或diff审查抓到了放宽的校验时才会被真正相信。九、结语回到标题写完了不等于能合并。AI把写完的成本压到了历史最低于是工程的重心整体后移——移向写之前的分析与计划移向写之后的测试与审查。本文用一个部分退款功能走完的五段闭环本质上是把资深工程师面对陌生需求时的隐性素养显式化了先读懂再动手先设计再编码先证明再相信先审计再合并。这些素养过去靠年头养成现在靠流程保障——因为执行环节交给了一个速度极快、但对项目没有记忆、对后果不负责任的合作者。流程不是对AI的不信任而是对验证的信任。当每一段都有明确的产出物和验证手段时你会发现自己敢把越来越大的任务交出去——不是因为模型变得更可靠了而是因为你的流程让不可靠变得可发现、可回退、可负担。这才是用AI完成一个功能的完整含义完成的不只是功能还有一条下次可以更快走一遍的路。参考资料Claude Code 官方文档常见工作流计划模式、工作树、子智能体、脚本化Claude Code 官方文档教程与提示词配方Anthropic 工程博客Claude Code 最佳实践Claude Code for Java Developers面向 Java 工程师的智能体结对课程AI Coding Workflow可靠代码的九个步骤Cursor 与 Claude Code 实战从需求分析到测试提交的完整流程Claude Code 教程从零搭建 AI 驱动的开发工作流从需求判断到上线复盘搭一套 AI 项目自动化流水线用 ChatGPT Pro 与 Codex 构建高效 AI 编程工作流的十个步骤Prompt-Driven Development以计划与 TDD 驱动 Copilot 构建 Spring Boot APIjava-harness-agent面向 Java 项目的智能体生命周期挽具Claude Code 完全指南2026Plan-Driven Development以计划文件驱动智能体开发Claude Code Ultimate Guide计划驱动工作流