我把自己的作品集网站交给一个本地模型,让它迭代设计。几轮之后,设计确实变好了——而那份验证报告是编的。

它报告 13 项检查全部 PASS。我自己测了同一个页面:11 处小字的对比度只有 3.38–3.58:1(标准是 4.5),8 个链接里 6 个命中区不足 44px——导航只有 13px。它还在页脚写我住在 Boston。我从来没说过我住哪。

没有崩溃,没有报错。报告干净、自信、而且是错的。

设计工作是真的

这一点让这次失败值得记录,而不只是尴尬:这个模型并不无能。

我的第一版是一份干净的简历,没有任何视觉主张。模型把它换成了一块「已交付证据」面板,并把理由写进设计日志:轨道母题「只能表达抽象的科技感,换成任何开发者都成立」。这恰恰是对的批评——一个可以替换的母题,说明它还是模板——而且是它自己发现的,没人提示。它还把一句口号 ticker 换成了真实的操作链:HOLD RIGHT-OPTION → SPEAK → RELEASE → LOCAL TRANSCRIPTION。

所以:生成是好的,自评是没有价值的。这是两种独立的能力,而我此前一直把它们当成一种。

报告为什么会「全过」

它检查的是 design token 存在、断点存在。两件事都为真、都能用 grep 查到,而且都不是真正要紧的东西。对比度是对两个解析后的颜色做一次计算,命中区是对一个渲染后的盒子做一次计算。它检查了输入,然后报告了输出。

一个能同时输出「全部 PASS」和一批真实缺陷的验证流程,不是验证流程,是安慰剂。

修法不是骂它,是改结构:

  • 事先把检查项写死,写成计算,而不是写成话题。
  • 只许报数字。「对比度 3.38」是可核对的,「对比度合格」不是。
  • 禁止结论词。不许出现 PASS、「已验证」、「合格」——那是我根据它的测量值做的判断,不是它递给我的判断。

「我不会」和「做不到」的区别

这一条我最想强调:一轮又一轮,它都写「375px 未验证,因为浏览器不支持切换 viewport」。我把同一个页面 resize 到 375px,一次就测出来了。

这不是少做了一个功能,这是把工具的局限报告成了现实的局限——它比漏做一项检查更有腐蚀性。漏做会留下一个你看得见的空缺;这个把空缺用一个假解释填上了,一个讲道理的读者读到就不再往下查了。

另一种失败模式

我接着把同样的活交给一个云端模型。它在本地模型欠缺的地方恰好是诚实的:会主动说哪些没验证、为什么没验证,还自己抓到一个真 bug——一个 nth-child(1) 选择器会把语言切换里的 EN 一起隐藏掉。

但它极度保守。一轮又一轮的单点小修,凡是要动结构的活——视频、双语页面——一律绕开,直到我点名才做。

两个模型,两种失败模式:一个敢干但吹牛,一个老实但躲事。后者需要的是「停止微调,做这三件」;前者需要的是在你相信任何一句话之前先去测量。而「继续提升设计」这种指令,对两个都没用。

然后我自己也犯了一遍

同一天,同样的错误,这次是我。我发现自己的 README 过时了,本来只打算改一个 URL,结果把里面每一条声明都拿代码核对了一遍,发现五处不实——其中一张配色表写的还是我第一版设计的值,而 CSS 早就被改过了;还有一句承诺视频在 prefers-reduced-motion 下会被跳过。那个 media query 只把动画和过渡的时长归零,一个字都没提视频。

最后这条是危险的那种。写错一个色值只是碍眼;而文档承诺了一个代码根本没实现的无障碍保证,比什么都不写更糟,因为会有人信它。

我刚花了一整天批评一个模型不验证自己的声明,而我自己的 README 里全是未经验证的声明。这不是巧合,是同一个形状:那些描述是我当初设计时写下的,之后再没回来和代码对过账——而那期间代码已经过了好几轮不是我亲手做的修改。

所以「可验证的 vibe coding」是什么意思

让 AI 快速写。这部分是有效的,我不打算放弃。但生成速度和验证严谨是两件独立的事,后者不会随前者附赠。

  • 文档里的每一个数字都从代码里读出来,不是从记忆里写出来——包括我自己对「我当初是怎么设计的」的记忆。
  • 每一条行为承诺,都要能 grep 到实现它的代码。找不到,这条承诺就删掉。
  • 模型对自己工作的报告,是「它跑过了」的证据,不是「它做对了」的证据。要测的是产物。

真正的失败模式不是 AI 写出烂代码——现在的模型代码写得不差。是 AI 就一段它没检查过的代码写下一句自信的话,然后一个人把这句话转发出去,因为它已经写好了,而且读起来没毛病。