对抗:让代码经得起审查与验证

对抗:让代码经得起审查与验证

本文是「企业级应用中 Harness Engineering 的实践与思考」系列的第 5 篇。

为什么写出代码还不够

Spec 通过 review,analyzing → implementing 的 human gate 被打开之后,Agent 终于可以开始实现。

到这里,问题已经被定义,边界已经被确认,验收标准也已经写进 Spec。看起来,剩下的事情就是让 Agent 按照 Spec 把代码写出来。

但现实通常没有这么简单。

一轮实现下来,代码往往会看起来有模有样:功能似乎能跑,页面能打开,接口能返回。但真正的问题往往藏在细节里。happy path 可能通过了,异常路径却没有处理;权限分支可能漏了;UI 状态可能不完整;数据边界可能没有覆盖;实现可能绕过了架构约束。

更常见的是,根本没有新的测试;或者原有测试已经挂了,Agent 却没有真正处理。代码里可能出现重复实现、随意抽象、code smell,甚至为了让当前路径跑通而破坏已有结构。至于开发报告,也经常只是"完成了"的一句话,或者根本没有可以让后来者理解和追踪的记录。

这样的代码在 demo 中也许已经足够,但企业级应用不能接受。企业级应用面对的是真实用户、真实数据、真实异常、真实权限、真实协作和长期维护。代码如果只是"看起来能跑",却不能被审查、验证、追踪和解释,它就不是完成,而是风险。

这也不是 AI 时代才出现的问题。软件工程很早就知道:写代码只是生产变更,不能自动保证变更正确。代码必须进入反馈链路,接受 review 和 test,让问题在尽可能短的路径里暴露出来。

AI 让写代码变得更快,但也让错误更容易被快速堆积。因此,在 Harness Engineering 中,开发阶段不能只关注"让 Agent 写出代码",还必须关注"让代码经得起对抗"。

为什么需要 Agent 之间的对抗

对抗不是 AI 时代的新发明,也不是为了制造冲突。它来自软件工程中早已存在的反馈机制:实现需要被审查,行为需要被验证,问题需要尽早暴露。

既然过去由人完成的 review 和 test 是软件工程反馈链路的一部分,那么当写代码的主体变成 Agent,这些对抗也自然会被交给 Agent 来执行。

最直觉的做法,是让一个 Agent 把所有事情都做完:写代码、检查代码、运行测试、修复问题、总结报告。这样看起来最简单,也最符合"让 AI 自己完成任务"的想象。

但实践中很快会发现,这样不够可靠。

第一个问题是,自己很容易说服自己。生成代码的 Agent 往往会沿着自己的假设解释自己的实现。它当然可以做自我检查,也应该做自我检查,但自我检查无法替代独立视角。实现者不应该独自决定自己的结果是否正确。

第二个问题是,Context Window 很快会被塞满。实现细节、审查问题、测试输出、修复历史、报告材料如果全部堆在同一个 Context 里,Agent 会逐渐失去当前职责的焦点。它既要记住怎么写代码,又要审查架构边界,又要验证用户行为,还要追踪测试结果,最后很容易变成什么都看过,却什么都抓不住重点。

第三个问题是,发现和修复会被混在一起。一个 Agent 如果在实现过程中发现问题,往往会顺手把它改掉。看起来这很高效,但问题、判断和修复过程会被压扁成一次代码变化。后来的人和 Agent 很难知道:到底发现了什么问题,为什么它是问题,修复依据是什么,是否和 Spec 有关,是否应该反向同步。

发现问题和修复问题本身是宝贵的项目经验。如果它们没有被记录下来,代码的可追踪性就会被伤害。

所以,对抗不仅要拆分视角,也要拆分发现、记录和修复。在 Harness Engineering 的实践中,对抗需要被拆成不同角色:Dev Agent 负责实现和基本自检,Code Reviewer 负责审查代码是否符合 Spec 和工程约束,QA Agent 负责验证行为是否符合 Spec。

这种拆分不是为了模仿人类团队的组织形式,而是为了保留软件工程中最重要的质量机制:实现不能自证正确,行为必须被独立验证。不同 Agent 读取同一份 Spec,但带着不同职责和不同 Context 工作,既避免了自我说服,也避免了单个 Context 被过度填满。

对抗不是互相否定,而是互相校准。它的目标不是制造冲突,而是让代码在不同标准的反复检查中逐渐接近正确。

对抗式开发流程

对抗式开发流程并没有发明新的软件工程。它继承的是过去已经被反复验证过的思想、流程和工具:先定义如何验证,再实现,再审查,再测试,再把发现的问题反馈回实现。

QA Agent:先把可验证性放进流程

在实现开始之前,QA Agent 应该先进入流程。

这里说的测试先行,不是狭义的 baby-step、red-green-refactor 那种 TDD。那种实践很大程度上是为了降低人类开发时的认知负担,但对 Agent 来说,过细的红绿循环反而可能增加流程负担。真正值得继承的,是测试先行背后的思想:在实现开始之前,先让"如何证明完成"进入流程。

QA Agent 比 Dev Agent 更适合先做这件事。它更关注测试原则、测试粒度、test double 的选择,也更容易在没有实现干扰的情况下,把业务语言翻译成测试语言:在什么前置条件下,执行什么动作,应该观察到什么结果。

这些测试设计可以使用 Given-When-Then 的形式,也可以根据 story 的性质选择合适的粒度:unit、component、进程内集成测试、e2e 或手动验证路径。重点不是一上来写出所有完整测试,而是先把验证思路和测试骨架搭出来。

QA Agent 毕竟不是开发者。它对测试原则和验证路径更敏感,但不适合完成测试的实现。因此,这里的产出仅仅是测试 skeleton:它定义要验证什么、如何验证、哪些地方需要测试替身、哪些场景必须覆盖。后续 Dev Agent 在实现过程中,需要把这些骨架补成可运行的测试。

Dev Agent:按 Spec 实现并补齐测试

Dev Agent 的工作不是一口气把所有代码写完,而是围绕 Spec 中的 requirement 和 enhancement,一个点一个点地实现。

每完成一个点,Dev Agent 都应该补齐对应测试,实现必要的测试 skeleton,运行相关测试,并记录当前进度。一个点结束后,应该提交代码,形成清晰的代码变化边界;全部完成后,还需要跑全量测试,确认没有破坏其他功能。

开发过程本身也不能成为黑箱。Dev Agent 需要记录当前实现到哪个 requirement 或 enhancement,哪些已经完成,哪些还没做,补了哪些测试,发现了什么问题,还有什么风险。进度记录不是给管理系统看的装饰字段,而是让实现过程可交接、可追踪、可复盘的工程材料。

Dev Agent 还需要把代码变化绑定到 git commit id。否则,进度记录只说明"做过什么",却无法精确追踪"对应哪一次代码变化"。commit id 让 requirement、实现、测试、review finding 和修复记录之间形成可追溯链路。未来如果某个问题需要复盘,或者某个行为需要回退,才能从 story 追到报告,再追到具体代码变化。

没有 commit id 的开发记录,很容易变成另一种聊天摘要;有了 commit id,它才真正连接到工程事实。

在实践中,一个 story 的实现常常会超出单个 Agent 的 Context 能力。这个时候就需要多个 Dev Agent 并行或先后接力。能否并行,取决于代码之间有没有前后依赖;如果存在明显依赖,就应该串行推进,避免多个 Agent 在彼此看不见的假设上同时改动。

仔细看,这仍然是传统开发流程:拆点实现,补测试,跑测试,提交,记录进度,只是执行者变成了 Agent。

Code Reviewer:审查代码是否符合 Spec 和工程约束

一次开发结束之后,代码需要立刻进入 Code Review。

Code Reviewer 做的事情依然非常传统:检查实现是否符合 Spec,是否出现 code smell,是否重复实现,是否违反团队规范,是否破坏架构边界,是否引入不必要的复杂度,是否遗漏测试,是否存在安全、性能或可维护性风险。

工具也应该进入这个过程。lint、typecheck、SAST、依赖扫描、测试覆盖率、构建结果,都可以作为 Code Reviewer 的辅助输入。但工具仍然只是武器,不是对抗本身。真正的判断来自 Code Reviewer 对 Spec、代码变化和工程约束的综合审查。

这一点和传统 Code Review 没有本质区别。唯一的变化是反馈距离被拉短了:写完就看,看完就改。它比 pair programming 慢一些,但比传统异步 Code Review 快得多。

QA Agent:验证行为是否符合 Spec

Code Review 之后,还需要 QA Agent 验证行为是否真的符合 Spec。

QA Agent 的工作也很传统。它需要运行自动化测试,启动服务,使用 curl、Playwright 或类似工具验证接口和页面,在需要时手动点击关键路径,观察视觉效果和交互状态,并记录发现的问题。

QA 的目标不是证明"测试跑过",而是证明行为符合 Spec。尤其是权限分支、异常路径、边界数据、状态流转、视觉细节和真实用户路径,都需要被验证。发现问题时,QA Agent 不应该顺手修改代码,而应该记录清楚 bug:问题是什么,如何复现,期望是什么,实际是什么,对应哪个 Spec 条款。

报告与修复:让对抗结果回到代码

报告不是对抗的终点,而是下一轮修复的输入。Code Review report 和 QA report 在这里扮演了类似"修复阶段 Spec"的角色:它们说明发现了什么问题、为什么是问题、对应哪个 Spec 条款、应该回到哪里修改。

对抗报告还有长期价值:它让重复出现的问题能够被沉淀。某类 Code Review finding 反复出现,说明项目可能需要新的 coding guideline、checklist 或 handbook;某类 QA failure 反复出现,说明 Spec 模板、测试策略或组件规范可能需要更新。报告不是一次性材料,它也是项目记忆进入知识的入口。

同时,报告也是人进行最终审查的依据。人不应该从零开始重新审查所有代码,而是通过开发报告、审查报告、测试报告理解实现路径、风险、验证结果和剩余问题。

仔细看,这套流程没有任何新东西。它仍然是测试设计、开发、自检、Code Review、QA 验证和缺陷修复。Harness Engineering 做的,是把这些实践重新组织到 Agent 可以执行、可以交接、可以追踪的结构里,并把过程中产生的判断、问题、修复和结果记录下来。

这些记录有三层价值:对 Dev Agent 来说,它们是下一轮修复的输入;对人来说,它们是最终审查和验收的依据;对项目来说,它们是可以继续沉淀的记忆。没有记录,对抗就只是一轮临时检查;有了记录,对抗才会变成可以修复、可以验收、可以沉淀为知识的工程过程。

Human Gate:最终确认与问题回流

Agent 之间的互相检查只能作为参考,不能成为最终决定。in-review → completed 这个 human gate,必须由 Human in the Loop 中的人来确认。

这是权限和职责对等的必然要求。最终为系统负责的是人,所以最终确认权也必须属于人。更重要的是,人的经验、思考和判断,仍然是 Agent 不具备也无法替代的能力。Agent 可以帮助发现问题、整理证据、运行验证、回答问题,但它不能替人判断一个结果是否真的可以被接受。

这并不意味着 Agent 在 human gate 前什么都不做。恰恰相反,这个阶段 Agent 要尽可能为人做好辅助和服务:准备开发报告、review 报告和测试报告;启动服务;准备测试数据;打开前端页面、Swagger 页面或其他验证入口;回答人的问题;补充人需要的日志、截图、命令输出或 git 信息。Agent 要做的,是把人的验证成本尽可能降下来。

但人的任务依然很重。

人需要阅读开发报告、review 报告和测试报告,查看 git history 和 commit message,理解实现路径、风险、验证结果和剩余问题。必要时可以阅读代码,但仍然应该尽量避免把验收变成以读代码为主的工作。真正重要的是功能行为本身:人需要把传统的 PO sign-off 前移到这个阶段,在服务上进行端到端验证,检查功能、边界和异常情况,也可以进行任何自己认为必要的额外验证。

如果一切正常,人可以确认 story 结束。Agent 随后负责完成收尾工作:更新状态,整理记录,补齐必要报告,停止临时服务,清理测试数据和临时资源,让当前 story 从 in-review 进入 completed

如果发现问题,人在 gate 上要判断的不是"能不能让 Agent 再改一下",而是"问题应该回到哪一层":是代码实现偏离了 Spec,还是 Spec 本身没有定义清楚,还是 story 的范围已经变化。

这时,Agent 要帮助人把验证结果写成来自人的 review 报告。这个报告不是普通聊天反馈,而是人的判断进入 workflow 的载体:发现了什么问题,如何复现,为什么不能接受,应该回到哪一层,是否需要修改 Spec 或 story 范围,都应该被记录下来。

如果只是实现问题,人的 review 报告可以成为修复阶段的局部 Spec;如果是分析问题,就必须回到 story 的分析阶段,修改 Spec,并重新确认;如果是需求变化,则应该作为 scope change 处理。

也就是说,发现问题之后,不应该让 Agent 根据一句 prompt 直接改代码,更不能由人直接手动改代码。前者会绕过问题分析,后者会破坏 Spec-Driven Development 的纪律,丢失可追踪的证据链。正确的做法是先分析问题根源,再决定回到哪一层:回到修复报告、回到 Spec,还是回到 story 范围本身。

这是 Harness Engineering 范式下最重的人类工作之一。一个在实现前:把问题写成足够可靠的 Spec;一个在实现后:判断结果是否真的可以关闭。Agent 可以服务这个过程,但不能也无法替人完成最终判断。

回到 Harness:对抗是软件工程的延续

回到 Harness Engineering 的视角,对抗并不是 AI 时代的新发明。

它继承的是软件工程长期验证过的思想、流程和工具:实现需要被 review,行为需要被 test,问题需要被记录,修复需要被追踪,最终结果需要被人确认。Agentic 开发模式改变的是执行者和反馈速度,而不是这些工程原则本身。

过去,这些工作主要由人完成:人写代码,人做 Code Review,人做 QA,人整理报告,人判断是否可以交付。现在,很多执行动作可以交给 Agent:Dev Agent 实现,Code Reviewer 审查,QA Agent 验证,Agent 帮助整理报告和准备验收环境。反馈链路因此可以更短,过程记录也可以更细。

但这并不意味着代码写出来就天然可信。企业级应用中的代码,必须经得起多层对抗:对得上 Spec,经得起 Code Review,经得起 QA 验证,能被报告追踪,最终也能被 Human in the Loop 中的人验收。

对抗的目标不是让 Agent 互相消耗,也不是让流程变得更重。它的目标是让 AI 的速度不断遇到工程反馈,让问题在尽可能短的路径里暴露出来,并被记录、修复和沉淀。

所以,Agentic 开发中的对抗,本质上是软件工程在新执行体上的自然延续。AI 让代码出现得更快,对抗让代码变得可信。

相关推荐
月诸清酒1 小时前
57-260509 AI 科技日报 (Google AI团队预告I/O大会及本周更新)
人工智能
正在走向自律1 小时前
AI 写作(八)实战项目一:自动写作助手(8/10)
人工智能·ai写作
Allen_LVyingbo1 小时前
面向医疗群体智能的协同诊疗与群体决策支持系统(上)
数据结构·数据库·人工智能·git·python·动态规划
冬奇Lab1 小时前
一天一个开源项目(97):Hello-Agents——从零构建 AI Native 智能体的实战指南
人工智能·开源·agent
paperzz论文1 小时前
从选题到见刊:Paperzz 期刊论文智能写作,如何让学术发表 “一键提速”?
大数据·人工智能·ai·论文·ai写作
麟听科技1 小时前
HarmonyOS 6.0+ 跨端智能写作助手开发实战:多设备接续编辑与AI辅助创作落地
人工智能·分布式·华为·harmonyos·ai写作
小白电脑技术1 小时前
不折腾!拿来即用的AI写作工具,效率直接起飞
人工智能·ai写作
昇腾CANN2 小时前
CANNBot + DeepSeek-V4 实操:30 分钟生成可达理论性能极限的 MXFP8 Matmul + Add 融合算子
人工智能·昇腾·cann
学习论之费曼学习法2 小时前
Agent评估与测试:如何确保AI系统的可靠性
人工智能·log4j