最近圈里玩vibecoding的人越来越多了,大家都有同一种体验:让coding agent从零写一个功能,它唰唰唰就出来了,跑起来好像也没啥大问题,可一旦往生产环境一放,各种想不到的破事就冒出来了。前一篇我聊过怎么从"能用"走到"可控",核心是给整个流程建立反馈闭环。但这一篇我想碰一个更隐蔽、也更要命的点:很多人做闭环的时候,把"写代码"和"验代码"这两件事都塞给了同一个agent。说难听点,这就是让agent既当运动员,又当裁判,比赛结果能靠谱才怪。
先说个我亲眼见过的例子。某开发者的模拟项目X是个内部数据看板,需求变得快,他让coding agent加了个筛选功能,agent跑完自己写的单元测试,在对话里给了句"全部通过,可以合入"。结果上线当天,筛选逻辑在边界条件下把整张表渲染崩了,一堆同事的看板直接白屏。后来复盘才发现,agent写测试的时候,是照着它自己写实现时的假设去写的——它只会验证它认为正确的行为,根本不会去验证它压根没想到的行为。这不叫测试,这叫复读。
1. 自评自测的陷阱:为什么Agent说"测试全过"你反而要警惕
1.1 当生成器和裁判器是同一个模型时发生了什么
先从原理上理解这件事。coding agent本质上是概率模型在预测下一段token,它生成业务代码和生成测试用例,用的是同一套权重、同一段会话上下文。你让它写完功能再"检查一下""补几个测试"的时候,它的检查逻辑是建立在自己生成代码时的内部表征之上的。讲个不精确但特别形象的说法:你让一个人既出考题又当考生,他大概率会把考题出成自己刚背过的那部分——不是为了作弊,而是他打心底觉得那部分就是重点。
落到代码层面,这种自评自测的失灵通常有三个典型表现。
测试与实现共享同一个错误假设。这是最常见的一种。函数处理空数组的逻辑写错了,agent生成的测试用例往往只用非空数组,因为它在写实现时脑子里默认了"输入不会为空",生成测试时就继承了同一个默认值。测试跑通,恰恰说明实现和测试错得高度一致,而不是说明实现是对的。
补丁式修复掩盖真实缺陷。你指出bug之后让agent修复,它最常见的应对方式是加一个if分支或者套一层try-catch,而不是去纠正根因逻辑。然后它补的测试恰好覆盖它新加的那个分支,测试通了,但原本的语义被绕过了。表面看测试覆盖率高了不少,实际上代码变得更绕、更难维护。
自我评审变成复述。你说"帮我review一下这段代码",agent输出的经常是"代码逻辑清晰、边界处理基本完善、性能可接受"这类话。这本质上是把它生成代码时的内部推理重放了一遍,而不是从外部视角提出质疑。它不会主动说"这段逻辑在并发下有个隐藏竞态"——因为它生成的时候就没考虑到,现在自然也想不起来。
这三条不是理论推演,是我在真实项目里反复遇到的组合拳。
1.2 我在项目里遇到的"测试全绿、上线全崩"现场
讲一个具体的翻车现场。某个内部工具需要把用户上传的CSV解析后写入数据库,需求是"解析失败时自动跳过坏行并记录日志"。我让agent实现这个功能,它提交了一个版本,测试用例写着"传入一个包含正常行和坏行的CSV,断言坏行被跳过且日志有记录",一跑,全绿。
等我把这个版本接上真实数据,问题立刻暴露了。真实生产数据里的坏行不是"缺字段",而是"某一列包含逗号但引号没闭合"。agent的解析逻辑是先按逗号split再数字段数,这种实现本身就扛不住引号内的逗号,中间行直接被拆碎。它测试里构造的"坏行"是"少了一个字段"的类型,而这恰好是它自己实现逻辑能识别出来的那种坏。结果上线后日志里全是误报,一部分本来好好的数据被当成坏行跳掉了。
这个案例让我彻底想明白了一件事:agent自测通过,只能证明"它还记着自己写了什么",根本不能证明"它写的东西是对的"。两者之间的差距,就是翻车现场和家长口中"我孩子很聪明就是粗心"之间的差距。
2. 从"运动员兼裁判"到"三权分立":可控编码的核心治理原则
2.1 谁来写,谁来测,谁来拍板
要真正把局面控制住,得把三个角色拆开——产出代码的人、验证代码的人、批准合入的人。在纯人工开发年代,这个分工天然存在:我写代码,同事review,测试环境跑用例,CI和产品验收最后拍板。但轮到coding agent上场的时候,很多人把这套流程忘得一干二净,把生成、验证、决策全委托给同一个对话窗口,等于把一个功能整个外包给一个没有任何监督的执行者。
我建议的最小拆分是三层的:
- 生成层:agent负责产出版本代码和它自己的说明,包括改了哪里、为什么这么改、它觉得哪里还有风险。这一层鼓励它放开手脚发挥。
- 验证层:由独立于生成过程的检查手段承担,包括静态检查、类型检查、独立的测试用例、真实样本数据验证。这一层不允许agent单独说了算。
- 决策层:人类开发者负责最终的合入判断。你不需要看懂每一行代码,但你要看得懂验证层给出的证据和剩余风险,然后决定放行还是打回。
这套做法的本质是制造信息不对称:agent的产出要经过一道它无法完全预测的关卡,它的"自圆其说"就会被拆穿。就像考试不能让学生自己批卷,哪怕他知识点掌握得再好,也不能让他自己给自己打分——这不是不信任,是机制设计的基本常识。
2.2 权重分离的实操拆解
有人会问:那让agent帮我们写测试也不行吗?不是不行,而是要看这些测试归属于哪一层。我的区分标准很朴素——凡是只存在于agent工作流内部的检查,都不可信;凡是能脱离agent独立运行的检查,才有裁判的效力。
举个例子。agent在对话里自己跑一段Python脚本,输出"验证通过",这是一种检查。但同样的断言写成独立的pytest用例放进仓库,由CI环境单独执行,这是另一种检查。两段代码可能一模一样,但效力完全不同。因为前者由agent自己控制输入输出、自己解释结果,后者是固定的、可复现的、带着版本历史的。
我在实际操作中给验证层定了四个硬约束,你可以直接抄走。
- 验证代码和业务代码必须分开提交,agent不能在同一次会话里既产出功能又产出最终的验收断言。
- 所有验证命令必须能从本地和CI两个环境无障碍执行,不允许依赖agent对话里的中间状态。
- 关键路径必须有人类手动核对真实数据样本,比如拿脱敏数据跑一遍,逐个看结果是否符合业务直觉。
- 打回时给agent的反馈要具体到"证据链":哪个用例、什么输入、期望是什么、实际是什么,而不是笼统来一句"有问题,改一下"。
这四条里最关键的是第四条。没有证据链的反馈会让agent陷入瞎猜循环,它改一版你验一次,再改一版你再验一次,来回折腾七八轮,耐心耗尽,最后你大概率会忍不住自己上手改——然后流程就废了。所以反馈必须像代码review意见一样精确,让agent有据可依地修复。
3. 把"裁判权"收回来的具体做法:独立验证层的搭建
3.1 第一道闸门:静态检查与类型系统的"客观裁判"
第一道闸门最好用,也最容易被忽略。很多vibecoding项目跑得飞快,代码风格基本放飞,参数类型全靠鸭子类型硬扛。我的建议是:在让agent动工之前,先把项目的静态检查规则和类型标注约束立起来,让质量基线成为"硬编码"的一部分。
具体操作上,我习惯在项目根目录放统一的配置文件,包含linter规则、格式化配置、类型检查的严格模式。agent生成的任何代码,第一件事就是跑这些工具,没过直接打回,根本不进入人工review环节。这样做的好处是让机器先筛掉一批低级问题,把人类的注意力留给真正需要业务判断的地方。
这套东西的正确用法是"零容忍":格式化不过就格式化,类型检查不过就修类型,绝不因为"功能先跑起来"就跳过。一旦开了先例,agent是能观察到的——它会发现检查可以被绕过,之后生成的质量会肉眼可见地一路下滑。我实际对比过几个项目:把静态检查作为硬闸门的,agent代码的返工率能下降一半以上;没有这道闸门的,agent代码里的低级错误经常要拖到集成阶段才暴露。
3.2 第二道闸门:人类评审清单
第二道闸门是人的判断。很多开发者一听"评审agent的代码"就头大:"我又看不懂它每一行在干嘛,怎么评?"其实评审不需要逐行精读,而是要盯住几个关键点。
- 改动范围是否符合需求。agent特别喜欢顺手重构、顺手改名、顺手加依赖,这些无关改动是合入后出问题的头号来源。评审时第一眼就看diff范围,超出需求的改动一律标记打回。
- 异常路径是否有人替你想过。空值、超长输入、并发、部分失败、重复调用,这五种场景agent通常不会主动覆盖。评审时对着这五个词逐个问一遍,比盯十遍代码有用得多。
- 对外接口是否兼容。函数签名、返回格式、错误码、依赖版本,任何一项变化都会直接影响调用方。agent往往只盯着它自己那个函数内部,看不到上下游的契约。
- agent的自我说明是否可信。它说"改动不影响性能",那多半得实测;它说"已经覆盖边界情况",那就去看看测试里到底有没有边界输入。agent的措辞不能作为证据,只能作为线索。
我给自己定过一条规矩:对agent产出的代码,review时间不能低于我人工写这段代码时间的30%。也就是说,如果这段功能我自己写要两个小时,那agent交上来的版本,我至少得花40分钟review和验证。这不是浪费时间,这是在买保险。真出了生产事故,花的不止40分钟。
3.3 第三道闸门:独立测试设计与金标准对照
第三道闸门是暴露问题最狠的环节:用独立于agent的视角来设计测试。核心思路是"站在需求方写测试,而不是站在实现方写测试"。
具体做法有两种。一种是黑盒用例:只根据需求文档和业务规则列输入输出对,完全不看代码实现。比如"筛选功能要支持时间范围、模糊关键词、状态组合",那测试就按这些组合穷举,agent的代码只能被动去满足,没法"出自己会做的题"。另一种是金标准对照:对于有确定正确结果的场景,比如数据清洗、指标计算、报表生成,提前准备一份人工核对过的标准输出,用agent的代码跑真实输入,再把输出和标准逐字段比对。差一个字段都能立刻暴露。
我还试过一个特别实用的技巧:让两个不同的模型分别实现同一个功能,然后互相跑对方的测试。这等于让两个"运动员"互相担任对方的裁判。模型同源会有一部分相似性,但不同实现路径通常会暴露出不同的盲区,交叉验证的效果比单一模型自评强不少。这个技巧在数据转换、解析、计算这类纯函数场景特别好用,业务耦合度高的场景效果会打折扣。
4. 一步一步落地:我把一个功能改造为"可控流水线"的全过程
4.1 改造前的状态评估
光讲原则容易飘,我拿一个实际场景走一遍完整流程,你看看每一步是怎么动的。
模拟项目X是一个报表服务,核心功能是从多个数据源拉取指标并汇总展示。最初我是用一个coding agent全权负责的,让它加一个新指标,它会自己写实现、自己写测试、自己跑一遍,然后给我一个"完成"的结论。问题集中在两类:一是数据口径经常做错,二是表结构变更后旧代码没同步。
我决定把整个链路改造成可控流水线。改造之前先做了个状态盘点:
- 现状:所有验证都在agent会话内完成,人类只在最后看一眼输出截图。
- 风险:口径错误的指标被合入,报表数据跟财务口径对不上。
- 目标:任何指标上线前,必须有独立于agent的验证证据,并且至少有一个人工签核点。
这个盘点很重要,它能帮你确认"现在最疼的到底是哪个环节"。不盘点直接开干,很容易把力气花在错误的环节上。
4.2 每个环节的具体操作与参数
改造分四步走。
第一步,建硬闸门。我在项目里补齐了静态检查配置和严格类型检查,所有agent提交的代码必须在本地和CI各跑一遍。规则我选的是偏严格的那一档,宁可让agent多返工几轮,也要把低级错误挡在门外。刚开始agent会频繁抱怨"格式检查太严",但撑过两轮之后它就习惯了,生成代码时会主动按规则来。
第二步,拆分验证代码。我要求agent在产出业务代码时,只允许给出功能说明和自测脚本,但不准提交最终验收断言。验收断言由我根据数据口径文档单独编写,放在独立的测试文件里。关键指标计算场景,我准备了三组人工核对过的金标准数据:正常周数据、跨月数据、缺失源数据,每组几百行。这三组数据覆盖了三种最常出问题的时间处理分支。
第三步,引入样本验证。除了单元测试,我让agent按我给的格式产出可执行的数据验证脚本,用来跑脱敏的真实样本。跑出来的结果我逐项人工比对,重点看数值是否合理、有没有明显跳变、汇总逻辑是否与财务口径一致。这一步是纯人工的,也是整条流水线里最花时间但最有价值的一段。
第四步,定义打回与反馈规范。我在提示词里写清楚了:验证层未通过时,必须基于证据链反馈;agent提交修复后,不允许跳过验证直接进入下一轮。也就是说,每一次修复都要重新完整走一遍三道闸门,不允许"局部修复局部确认"。这会拖慢单次迭代速度,但能掐死"只修了表面却引入新问题"的恶性循环。
整套流程走下来,一个指标从提出到合入的周期,从原来的两小时变成四到六小时。看起来慢了,但上线后出问题的概率从每周两三次降到了几乎为零。这个时间账非常划算。
4.3 效果对比与数值
我把改造前后的数据拉了个表,效果非常直观:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 单个功能合入周期 | 约2小时 | 4至6小时 |
| 合入后需要返工/修复的比例 | 约40% | 不到5% |
| 数据口径错误导致的上线事故 | 每周2至3次 | 基本为0 |
| 人工review投入 | 每次约10分钟看结论 | 每次约30至45分钟看证据 |
最让我意外的是第二行。表面看周期变长了,但合入后返工的比例大幅下降,算完总账,真正交付一个稳定功能的总时间反而更短了。这就是典型的慢即是快。另外第四行也很有意思:人工投入从"看结论"变成了"看证据",看起来花的时间多了,但脑子里对系统的理解深了一大截,后续再提需求的时候,我能直接说出哪里会受影响,agent的产出质量也跟着上来了。
5. 进阶心得:哪些验证该自动化,哪些验证必须留给人
5.1 自动化的边界
验证层不是越重越好,自动化也不是越多越好。我踩过最大的坑就是过度自动化:把所有人都要看的结论搞成一大堆机器日志,结果人类反而不再思考,只盯着最后的PASS/FAIL看,等于又把裁判权交了出去,只不过是从agent手里换到了日志系统手里。
我的判断标准很简单:重复性的、有明确规则的验证全部自动化;需要业务判断、需要容忍度的验证必须留给人。比如"接口返回200""数据库写入成功""字段格式正确",这类交给机器没问题;但"这个数值是否符合业务口径""这个错误该不该静默吞掉""这个延迟会不会影响用户体验",这类必须人来拍板。
自动化能做到的,是把需要人判断的问题压缩到一个很小的集合。好的验证层不是替你想,而是帮你把90%不需要想的事先做了,剩下10%真正重要的事,每一条都清清楚楚送到你面前。这样人的精力就花在了刀刃上。
5.2 让Agent"自证清白"的正确姿势
这里我想澄清一个误解:不是说agent完全不能参与验证,而是它不能成为验证的最终解释者。我现在实际的做法是,让agent做"证据生产者"而不是"裁判"。它负责产出测试方案建议、生成测试数据、解释它认为的风险点,但这一切都只是材料,最终结论由独立的验证层和人类来下。
实际提示词里我是这么写的:你产出的代码必须附带一份自测说明,包括你测试过哪些输入、你预期什么行为、你没有覆盖哪些场景。然后我拿这份说明去跟独立测试的结果对照。agent说得准,说明它自我认知是好的;agent说得不准,独立测试会立刻揭穿它。它"自证清白"的过程,就变成了一个受控的、可审计的环节,而不是一个黑箱结论。
这套做法的意外收获是:agent在写自测说明时会变得更谨慎,因为它知道自己说的每一条都会被独立证据验证。有些原本会拍胸脯说"没问题"的地方,它会主动标注"这里我不确定,建议重点验证"。这个变化是质变的,等于agent学会了诚实汇报不确定性。
5.3 踩坑清单
最后整理一份我真实踩过的坑,供你绕道。
- 坑一:让agent推荐测试框架并立刻采用。它推荐的往往是训练数据里出现最多的框架,未必适合你的项目规模和技术栈。正确做法是自己先定框架,让agent在这个框架里干活。
- 坑二:只验证成功路径。agent自测最喜欢选正例,你追问边界它也就补一两个。解决方案就是前面说的黑盒用例表,把输入组合穷举出来,每个组合都跑一遍,不给它挑题目的机会。
- 坑三:验证脚本和业务代码共享上下文变量。有次agent写的测试里用了跟实现一样的全局状态,等于左手测右手,怎么测怎么过。测试数据的构造必须完全独立,这是底线。
- 坑四:把"agent说没问题"当成验证通过的证据。再次强调,它是概率模型,不是仪器。它说没问题,只能说明它当前的内部推断倾向于没问题,跟外部事实没关系。
- 坑五:打回次数多了之后麻木了。我见过有开发者对agent的返工循环失去耐心,最后手动"帮"它改了代码再合入,问题就这样悄悄进了主分支。耐心和流程一样重要,一旦你开始绕过自己定的流程,整个流程就废了,再想重建信任就难了。
最后说一句我这阵子体会最深的话。vibecoding的价值在于把"实现"这个体力活外包出去,但它外包不了"判断"。你可以让agent写代码、写测试、写说明,但"这个功能到底做对了没有"这个问题,永远得有一个独立的裁判来回答。这个裁判,最差也得是独立的工具链,最好是再加上你自己的脑子。把裁判权握在自己手里,coding agent才能从"能跑"真正变成"可控"。