明系魔法吟唱之1 -- Vibe Coding 一年实践后的冷思考
英文文化适配版: One Year of Vibe Coding: A Cold Hard Look (English cultural adaptation)
前言
最近一年我在 Google/Anthropic/OpenAI 三家烧了超过 1 万美金的 token 账单。所以本文内容基于 opus4.6、codex-5.3-xhigh、gemini3-pro 等最强模型不限量使用所表现出来的编码能力进行评价。
这些 token 分布在嵌入式内核驱动、后端架构、DevOps、以及前端 UI 四类项目中。不同领域的 agent 表现差异大到离谱——20 美金的 token 就能让 agent 低人类指导条件下 vibe 出一个还不错的 React dashboard,但 2k 甚至 20k 美金的 token 也无法让 agent 在真机上编写出可用的内核驱动程序。下文的结论,基于这种跨领域的对比。
现象:Agent 的信任危机
就好像保健品销售拿着他的《大数据量子 AI 生物磁场治疗仪》,忽悠我说这台原价 20 万、现在活动价 8 万 8 的仪器,可以彻底根治我的颈椎病腰椎病高血压糖尿病,还能逆转我的动脉血管粥样硬化、冠心病、阳痿早泄等等
Agent 编程现在就是这么个状态。
Agent 给我一堆 emoji 庆祝刚才生成的七八万行屎山通过了全部测试用例,告诉我可以替换生产环境了。你信吗?
假设你是一位项目 leader,你最靠谱的组员同事,交给他的开发任务 80% 可以在预期时间内高质量交付。这位同事拿头给你保证下周就可以上线,那么你大概率能信任他最迟下下周也搞定了。但是 agent 给你保证现在质量和完成度可以上线生产了,你信吗?
此时此刻,无数知识星球、自媒体、AI 导师教父们正在到处收割韭菜的学费。大意基本上都是教你如何 prompt(tool/skill 换汤不换药),然后让你多开 agent 并行干活。
真实案例
Agent 的盲目自信不仅会误导使用者,也会误导 agent 自己。
我曾给 agent 这个任务:为当前 Kotlin 项目集成 GCP Transcoding 服务。我给了 agent 该产品的页面和文档作为参考,让它开始 plan。Agent 做出了如下计划:
- 通读文档后,发现该服务仅提供了 Java SDK,而当前项目使用的是 JVM 上的其他语言,并非原生支持
- 根据 RESTful 文档指示,结合文档定义字段,使用 ktor-client 进行手动接入
- 编写代码并执行测试
你发现这份计划中存在的问题了吗?
事实上,如果你曾经「古法手工编程」做过此类工作,你会发现手动实现 RESTful 远没有想象中那么简单。哪怕仅实现 Transcoding 服务的基础能力,也涉及到 5-10 个 endpoint 调用。每个 endpoint 的输入输出参数又有几十甚至上百个字段嵌套定义,agent 在应对这类长上下文任务时会频繁犯错。
而如果 agent 选择对 Java SDK(Google 也是从 protobuf 生成出来的)进行简单包装隔离,大概半天到一天就可以让这个功能稳定上线。
若是让 agent 按照 RESTful 文档手动实现,agent 可能会陷入 debug 泥潭——因为当 AI 幻觉导致写错了可选字段的字段名(大小写、驼峰、下划线),程序不会立即报错。你需要多久才能发现它实现错了?等上线生产后客户投诉吗?
当然,公允地说,agent 也不是毫无进步。比如跨层复杂 debug——OS 低概率复现死锁、渲染显示错误、Kernel Panic 这类调用链很长、横跨多个 Layer 的问题——去年年初基本上是帮倒忙,现在已经能帮正忙了。虽然不能全链路排查,但让 agent 制定一个有效的短链路排查计划,效果还不错。不过这依然是以人为主导的,不能放手让 agent 做——你给它方向,它执行得越来越好;但你让它自己选方向,它还是会把你带进沟里。
为什么我们无法信任 agent? 经过一年的实践,我认为问题的根源在于:我们缺乏有效的验证手段。
原因:验证手段的全面失效
Code Review 失效
常见观点:某种意义上来说 AI 并没有取代程序员,只不过是一个新的高级工具罢了。你作为生产代码的人,还是得弄明白要干啥,合入的代码就得弄明白。
但我认为,这个在实际项目里很难做到。
像我们之前内部 review 的时候,大部分时候 review 的是 code style,作者讲一下设计思路,我们也就是大概一听就过了。以前这套方法是有效的:
- 代码风格差的 PR,设计思路也一团糟,性能也差,也没什么可扩展性
- 代码风格好的 PR,设计思路都挺清晰,性能考虑也周到,就算有性能瓶颈也容易改,最后扩展性也不错
但是这个相关性在 agent 编码时代不存在,甚至相反。
Agent 一分钟就能生成出来注释齐全、风格优秀的——屎山代码。反正我肉眼看过去的时候,经常会被这第一层假象蒙蔽,放松警惕。主要是这个屎山有点难在 review 阶段发现,经常是上线后出了问题,回头细查的时候才发现是「巧克力味的屎」。
你信我,opus、codex-xhigh 这些你们舍不得用的模型,我开 thinking+max 模式站起来蹬,一样有这个问题。
我也试过让 AI 反过来审我的设计。AI 对你谄媚的语气,你感受一下——把一个我自己都知道有问题的方案丢给 gpt4o 评审,enterprise level、future proof、scaleable,彩虹屁一套接一套。后来我换了提示词,让它扮演"讨厌的同事"来批评我的代码,好了一点点。但最终效果依然不理想——AI 看到思路连贯的专业文字就会降低警惕,给出较高评价,即便内容错的离谱。
测试失效
更不用说测试了。现在的 test cases 也是 AI vibe 出来的,agent 又当裁判又当运动员,它说什么就是什么。蒙我坑我也不是一次两次了。写了几千行 getter/setter 的 test case,最后测试全绿告诉我可以上生产环境发布了。
就像前面 GCP Transcoding 的例子,agent 写错了可选字段的字段名,测试照样能过,因为测试也是它自己写的,错的一致就是「对」的。
有人会说:那让不同的 agent 分工——一个写代码一个写测试,不就行了?我试过。比同一个 agent 自写自测好一些,显而易见的 bug 确实能被揪出来。但这些 agent 共享相同的训练数据和系统性偏差,盲区高度重合。不像两个人类程序员——张三是做嵌入式出身的,李四是搞 web 的,他俩的知识结构和思维盲点能互补。两个 agent 再怎么分角色,本质上都是同一个培训班出来的——解决的是「同一个人出卷又答题」的问题,但解决不了「所有出题者都来自同一个培训班」的问题。
而且 AI 自己审核自己,边界递减效应非常明显。如果一审 agent 没看出来的问题,二审三审大概率也一样,除非你能喂给它更多错误信息。
与传统行业的对比
说到这里,有人可能会问:其他行业被机械、智能赋能后,难道就没有这个问题吗?
让我用 CNC 机床打个比方:
CNC 机床精度比我高,但机床产出工件后,我们可以对工件进行客观的物理测量——用卡尺量一下,公差是不是在 ±0.01mm 以内,一目了然。即便我没有手搓出这个精度的能力,但我依然有评价 CNC 机床和工件质量的能力。
这就是传统制造业被机械赋能后的状态:机器精度高,质量统一且稳定,而人依然能评价机器的产出。
那么软件开发行业被 Agent 变革后,理想状态应该是什么样的?Agent 交付的代码确实覆盖了需求,具备基本的安全防护,且更容易长期维护(哪怕仅考虑 agent 自己维护,不考虑对人类的可读性),性能更高,资源占用更少。
但程序不仅需要完成眼下需求文档中的功能,还需要考虑到基本的安全防护。一个功能完成但安全漏洞百出的项目代码,同样是不合格的。
而目前我们还无法评价 agent 是否达到了这个状态。单就「功能实现」这一基础要求,agent 还不能脱离人的引导和测试验证——更别提安全性、可维护性、性能这些更高阶的指标了。
问题是:目前主流的验证手段——人工 review、自动测试——都能被 agent「污染」。它可以写出风格完美但逻辑有毒的代码,也可以写出与错误代码完美匹配的错误测试。我们需要的是一把 agent 自己没法干扰的「卡尺」——不依赖它自己的判断、具有客观确定性的验证手段。
CNC 机床加工塑料、铝合金小件精度高,不代表加工钛合金、不锈钢精度也能达标。后者更考验整体刚性,以及工件质量大了以后热胀冷缩对程序进刀补偿的要求。
同理,vibe coding 出来的代码,本地点两下鼠标测试通过了,上线也是极大概率会直接炸掉。
传统行业:机器精度高、质量稳定,人能评价。软件行业:Agent 产出快、覆盖广,但人还没法可靠地评价。这就是问题所在——那把「卡尺」在哪?
方案:让编译器替你把关
既然人工评价(Code Review)和自动测试都靠不住,我们需要另一种评价手段——一种不依赖 agent 自己判断的、客观可验证的评价手段。
我的观点和主流 AI 编码观点相反:
Agent 编程时代,更需要强类型,更需要严格可验证的语言,而不是放任 agent 去写 python/js/java/go,还有 anyscript。
为什么?
AI 堆屎山这么快,别说生成个几万行了,就是生成超过 100 行我都已经懒得逐行去细读了。但是读类型签名、pre/post-condition 明显要快于通读逻辑代码。而这些东西只有 Rust/Scala/Haskell 甚至 formal method 能提供。
我在 agent 编码前就一直用这种风格写自己的代码,主要是代码量大了以后,编译器检查比我肉眼检查更靠谱。现在 agent 编码流行起来了,我发现让 agent 遵循我的这个要求,更能控制产出代码质量——当然也只能说一定程度上,起码比什么都不做好。
回到 GCP Transcoding 的例子:如果 agent 用的是强类型语言,字段名写错了至少还能在编译期被类型系统拦住一部分。但 RESTful + 弱类型的组合,错了就是悄无声息地错,等你发现的时候已经晚了。
但这只是第一层防线。强类型能拦住类型不匹配、空指针、Rust 的生命周期错误这些「语法级」的问题。能告诉你「这段代码编译不过」,但没法告诉你「这段代码逻辑上是错的」。
第二层防线:让 z3 替你证明
更进一步的方案是 Formal Method 级别的验证——通过 refinement type 或 OpenJML 这类工具,在类型系统上直接编码业务约束。
举个具体例子。让 agent 写一个排序函数,传统强类型能保证输入输出类型正确——接收数组,返回数组。但没法保证返回的数组真的是有序的。而如果用 refinement type,你可以在函数签名上直接写:
1 | ensures(forall i, j : i < j → arr[i] ≤ arr[j]) |
这行 pre/post-condition 就是你的「质量公差标准」。z3 求解器会数学证明 agent 写的那几百行排序实现,是否真的在所有可能的输入下都满足这个约束。证明通过就是通过,证明失败就是失败,不存在 agent 嘴硬说「没问题」就能蒙混过关的空间。
关键在于:人类只需要审读这一行 spec 是否表达了自己想要的语义,而不必逐行阅读几百行实现代码。 这才是前面说的那把 agent 自己干扰不了的「卡尺」。
实践效果
先说第一层,y1s1,该夸的还是要夸。现在的最新最强模型,过编译问题不大了,除非你比我还执着于类型体操。
过去 agent 碰到 Rust 生命周期错误、函数式类型体操,能反复尝试几十轮甚至陷入死循环直到上下文爆掉。FM 学习曲线本就陡峭,人脑都得一直进行抽象符号推理,我曾经认为 AI 永远不可能学会解决类型体操。
但现在不一样了。Pure-FP Scala、tagless final,opus 4.5 和 codex-xhigh 遵循得挺不错,过编译基本上是自动的。函数式类型体操的编译错误基本上都是几十上百行的类型天书,agent 读懂并修复这些编译错误已经不再是困难。
关键是:agent 解决类型体操并不是靠作弊(比如到处 asInstanceOf 或者 any),而是通过反复尝试,真正填补了这些形式化过程的 gap。这不是绕过,反而给了我足够的信心——说不定 AI 真的能写出通过 FM 检查的代码。如此一来,代码的正确性就有了形式化保证,这远比单元测试覆盖更全面、更可靠。
但第二层目前还是另一个世界。FM 级别的验证错误——z3 求解失败、refinement type 约束不满足——agent 处理起来仍然非常吃力。不过第一层的突破让我相信,这条路是有希望的。
局限性
当然,这个方案也有局限。
实际上现在的 formal method 工具链和生态还是很贫瘠,基本上只支持一门语言很有限很小的一个子集。有些工程上常用的语法/模式在 FM 那边都是 unsound,或者尚未证明。更不用说动不动就陷入死循环/无解证明了——稍不注意,z3 求解器要在比宇宙空间还大的可能性里搜索,到宇宙毁灭那一天都证明不出来。
强类型和 FM 能解决一部分问题,但不是全部。
更深的困境:Plan 与 Execute
即使有了强类型 + FM 作为评价手段,还有一个更深层的问题:agent 对计划的理解和执行。
GCP Transcoding 的例子其实已经暴露了这个问题:agent 选择手动实现 RESTful 而不是包装 Java SDK,这不是代码写错了,而是路线选错了。编译器能告诉你代码有没有语法错误,z3 能证明你的实现满不满足 spec,但没法告诉你该不该走这条路。
再举个更极端的例子:给 agent 一个复杂任务,研制一款火箭发动机。Plan 决定了做全流量分级燃烧循环,路线选择了共轴方案。
Agent 不遵循的话: 可能就偏离到抽气循环也说不定。编译器能告诉你代码有没有语法错误,但没法告诉你这是不是你要的火箭发动机。
Agent 遵循太好: 真的做出来共轴方案,那可能上线后会碰到更大的问题——共轴以后动密封系统做不好,氧化剂和燃料随着涡轮轴互相泄漏,俩预燃室要炸一个。编译器能保证类型正确,z3 能保证实现满足 spec,但没法保证 spec 本身是合理的设计。
现在的 plan/edit mode 切换也只是现阶段的权宜之计、无奈之举。这个问题比「评价手段缺失」更难解决,因为它涉及到对需求和设计的理解,而不仅仅是代码质量。
初见即巅峰
Agent 编程有一个显著的特点:初见即巅峰。
让 agent 开始一个全新的 CRUD 项目,或者一个 React 管理系统页面,agent 第一次的表现着实让所有人都大吃一惊——干净利落,结构清晰,甚至还贴心地加上了注释和错误处理。
但随着项目维护越来越久,那些「不可明说的」、没有被文档记录的、约定俗成的隐藏上下文越来越长。哪个字段其实已经废弃了但没删、哪个 API 有个历史遗留的 quirk、哪个模块之间有个微妙的依赖关系——这些东西,老员工心里都有数,但从来没人写下来。
而 agent 无法处理无限长的上下文,只能通过压缩、总结来选择性遗忘细节。可能被丢弃的是几次失败尝试的经验,也可能被丢弃的是关键数据结构的偏移量、寄存器地址、枚举定义。
这不是感觉。实际用下来,上下文窗口占用接近 30% 时 agent 就已经明显降智,接近 50% 时退化到不如基础模型。即便关键细节仍然在上下文内,agent 也会视而不见。
每次新开一个 session 的时候,开发者不得不面对一个几乎全新的「员工」——它似乎继承了压缩后的上下文(claude.md / agents.md),但细节完全不知。你得重新跟它解释一遍:「不是,这个接口虽然文档上写的是这样,但实际上我们从来不传这个参数……」
对于 CRUD、Spring、React 这类重复度高的任务,这似乎不是什么痛点——反正每次都差不多,忘了就忘了。
但对于嵌入式系统开发,任何被遗忘的细节都可能被 agent 天马行空的幻觉填充。寄存器地址错了?中断优先级配错了?DMA 通道冲突了?轻则系统崩溃,重则永久烧坏硬件。这不是「改个 bug 重新部署」能解决的问题。
更要命的是,kernel 里充斥着大量寄存器地址、flag 常量,而我们 inspect 内存的时候这些都是关键信息。一旦 AI 压缩上下文时把这些细节遗忘了,它的下一个决策可能是完全反向误导你——不是「不够好」,而是「彻底错误」。这也是为什么在这类场景下,我宁愿忍受长上下文带来的降智,也绝不肯让 agent 压缩上下文。这些信息是真的不能丢。
更何况,debug 的过程本身就不是线性的。我们经常同时考虑多种可能性,分别验证。A 路线走不通,切换到 B 路线——但这不意味着 A 路线就是死路,可能只是当时的认知还不够。等在 B 路线里积累了新的理解,回头一想,A 路线当初做过的尝试似乎并非死路一条。此时我再回到 A 路线上来——我人是回来了,AI 呢?你大概率会面对一个清纯的新手 AI,对之前在 A 路线上的所有探索一无所知。人类 debug 的经验是螺旋式积累的,而 agent 的记忆是一次性的。
Agent 时代,CS 基础还要学吗?
既然评价 agent 产出是核心问题,那开发者的基础知识就必然还是要学的。不然你拿什么去评价 agent 生成的代码、模块、架构设计质量到底如何?没有评价能力的开发者,和保健品店里待宰的老头老太没有区别。
那么该如何学习呢?
打开 LeetCode,题目还没读完呢,Copilot 已经把答案补全出来了。点一下 Submit & Run,前 1%。就这?
我的意见是:既然有 AI 了,当然不能局限于过去的难度,得上强度,上到 AI 做不出来的程度。
放心,该学的不会落下。上了强度以后 AI 幻觉越来越多,该补的课全都得补上。期间 AI 还会给你帮不少倒忙——但这恰恰是学习的机会。
比如你要实现 Red-Black Tree、B-Tree、AVL Tree,那就上点强度:给算法加上形式化验证,再把泛型支持也加上。放心,当下最强模型也写不出来。
其实幻觉反而会帮助你学习——因为幻觉里包含了常见的误解,你去验证和纠正幻觉的过程,本身就加深了学习效果。
当然,不是每个人都要一步到位搞形式化验证。更实际的第一步是:学会写 spec 而不是写 implementation。让 agent 动手之前,先自己用人话写出这个功能的 pre/post-condition——输入满足什么条件,输出应满足什么约束,哪些边界情况必须覆盖。然后把这份 spec 作为 agent 的验收标准,而不是一句话需求丢进去许愿。
从人话 spec 到 property-based test,再到 refinement type——这条路可以慢慢走。但核心能力是一样的:准确描述你想要什么。这本身就是 CS 基础功底的体现,也是 agent 时代人类最不可被替代的能力。
结语
AI 框架、模型、工具、方法论层出不穷,日新月异。但说到底,这些都是在给模型做加法、打补丁。
人类完成一个完整工作流的时候,不需要把自己拆解成多个「子 agent」去协作——因为人类是真的有记忆能力,且会学习的。做的时间越长,成长越多,越熟练。项目里那些隐藏的上下文、踩过的坑、约定俗成的规矩,都会沉淀成经验。
而 agent 则相反。上下文越长,智力下降越明显。即便细节仍然在上下文内,agent 也开始频繁地忽略这些细节,自顾自地幻觉出一些「看起来合理」的东西来。
核心问题始终没变:我们依然缺乏可靠的手段来评价 agent 的产出。强类型是第一层部分解,FM 验证是第二层部分解,但也只是部分解。
一天不学,错过很多。一年不学,好像也没错过什么。
框架工具更新迭代,爆款层出不穷,但其炒作因素远大于实际能力和价值。而 CS 基础知识才是久经时间考验的硬通货。与其追新框架新工具,不如把精力放在强化自己「评价 Agent 产出」的能力上——这才是 agent 时代真正稀缺的东西。
补记:内核网卡驱动开发实录
本文发布后,有读者问到 AI 处理跨层复杂问题的实际能力。这里补充一个完整案例。
我曾在清华开源操作系统社区做过一次报告,分享了在 AI 辅助下开发内核网卡驱动的踩坑经验。当时使用的模型是 Claude 3.7 到 4.0,效果完全是帮倒忙——90% 的信息都是幻觉误导。AI 混合了 dwmac 从 2.x 到 5.x 各个版本的行为,甚至牵扯到高通/Intel 芯片的代码,更不用说 PHY 芯片的寄存器、C22/C45 协议这些了。
这类领域的训练数据和讨论比较封闭,厂家文档藏着掖着,要注册会员才能获取。全链路做过这款芯片的人可能全世界不到 100 人,AI 没有清晰可借鉴的经验。Kernel 仓库里的代码有 10-20 年历史,几千次提交,十几万行代码,还有一大堆不同厂商不同架构导致的 workaround——这些对 AI 来说都是噪音。
一开始我是外行,协作方式是 AI 做鞭子、我做牛马——我负责执行 AI 给出的方案并反馈错误信息。但随着我越来越懂,发现 AI 净给我胡扯,于是角色翻转:我指定逆向路径,AI 帮我执行,不厌其烦地一遍一遍做 bit 级别对比实验。最终的结论和下一轮路线决策是人 + AI 一起产出的,而不是让 AI 直接大跨度地做下一步。
这个案例其实印证了前面的几个观点:训练数据稀缺的领域 AI 幻觉尤其严重;长上下文中的寄存器级细节一旦丢失就是灾难;而 debug 的螺旋式推进过程,目前的 agent 架构根本无法胜任。但反过来说,当人类掌握了主导权之后,AI 作为一个不知疲倦的执行者,在 bit 级别的重复对比实验上确实帮了大忙。
本文同步发布于 知乎