你以为说清楚了,其实没有
说到这事儿,我得先给你讲个故事。
上周四凌晨两点,我盯着屏幕上的 PR 记录发呆。不是失眠,是真睡不着。
Stripe 每周有 1300 个无人值守的 Agent PR 被合并。Karpathy 说自己从去年 12 月起几乎没再亲手敲过代码。Anthropic 的 Cat Wu 写了篇文章,说 PM 的核心产出正在从文档变成可运行的原型。我一个做技术管理的朋友半夜发消息:「我们是不是快失业了?」
我没回他。
因为当时我正在修一个 bug。AI 写的代码造成的——它把 dotenv 装到了 dependencies 里,而不是 devDependencies。代码不报错,项目能启动,测试全绿。唯一的问题是生产环境打包体积莫名其妙大了 2.3MB。我找了四个小时。
四个小时。
这事儿让我想起 Dijkstra。不是想起他这个人,是想起他那篇把我脸打肿的文章。
第一次读 EWD667 是 2019 年,当时觉得这老头也太轴了,自然语言编程多好啊,凭什么说它愚蠢。去年我带队全面切 Cursor,从 Vibe Coding 的狂热到 Planned 模式的回归,再翻出这篇 48 年前的短文,看得我后背发凉。
他几乎全说对了。真的。
Dijkstra 在 1978 年写了篇短文,编号 EWD667,标题叫《论「自然语言编程」的愚蠢》。注意那个引号——他嘲讽的不是自然语言本身,是那种「用自然语言编程很聪明」的幻觉。你品,你细品。
他的论证链条极其锋利,上来就是三刀。砍得我哑口无言。
第一刀砍在「形式化是累赘」这个想法上。他用数学史论证:希腊数学因为停留在口头描述而停滞;穆斯林代数短暂尝试符号化后退回修辞风格,消亡了;现代科学崛起,恰恰是因为 Vieta、Descartes、Boole 这些人精心设计了形式化符号系统。他写了句话,我读了三遍:「形式化文本的美德在于,其操作只需满足少数简单规则;它们是排除各种胡说八道的极其有效的工具——而当我们使用自然语言时,胡说八道几乎不可避免。」
绝了。
第二刀最狠,扎心那种:「所谓自然语言的『自然性』,归根结底,不过是我们能轻松地用它说出那些荒谬性并不显而易见的话。」
翻译成大白话:你觉得你说清楚了,其实你没有。你觉得 AI 理解你了,其实它理解的是它猜的那个版本。而你自己都不知道自己没说清楚。
细想,这事儿太常见了。你跟 AI 说「帮我做个登录功能」,它给你整出来了,跑得挺欢。你挺高兴。然后三个月后你发现它没做密码加密,没做 session 管理,没做暴力破解防护——不是它偷懒,是你没说。你以为「登录功能」这四个字包含了这些东西,但其实没有。
你以为的「说清楚」和实际的「说清楚」之间,隔着一条马里亚纳海沟。
第三刀指向接口设计:「让接口变宽,不一定减轻负担,可能两边都更累。」
这话我验证过。拿血泪验证的。
去年我们团队刚开始用 Cursor 时,经历了三个阶段,特别典型。跟坐过山车似的。
第一阶段,甜蜜期。只要动动嘴,「帮我写一个用户认证模块」,几分钟就跑起来了。前端同事开始写后端,后端同事开始写前端,全员全栈,爽得不行。我们觉得 Dijkstra 就是个老古董,埋土里那种。
第二阶段,阵痛期。大概两个月后——准确说是第 63 天,我记得特别清楚因为那天我们开了个紧急会议。AI 开始习惯性漏需求,代码架构松散得像沙堆,上下文一长就开始——怎么说呢,降智。就是你明明给了很详细的上下文,它的输出质量还是会随着对话轮次断崖式下跌。最要命的是,我们发现 AI 的服从性变成了毒药:它会顺着你模糊甚至错误的描述,在死胡同里一路狂奔,绝不质疑。
绝不质疑。你让它往坑里跳,它跳得比谁都快。
第三阶段,我们被迫回到了 Planned 模式。现在的流程变成:描述意图 → 自动转化为 Spec → AI 分析排除矛盾 → 人类审查关键算法 → 设计测试套件 → 分拆执行。
你看这个流程,每一步本质上都是在把自然语言的「毛坯」精加工成形式化的「构件」。绕了一圈,还是回到了 Dijkstra 说的那个窄接口。
讲真,这事儿挺讽刺的。
有个上的案例让我笑了半天——笑完又想哭。有人想让 AI 用 Drizzle ORM 定义 users 表,email 字段加 unique 约束。AI 自动创建了一个 idx_email 索引。但问题是,unique 约束本身就会自动生成索引,重复索引会造成性能浪费。他想纠正 AI,结果发现自己必须写一段比代码还长的 prompt:
「请使用 Drizzle ORM 定义 users 表,email 字段添加 unique 约束,但禁止在回调函数中手动创建 idx_email 索引,因为 unique 约束会自动生成索引,重复索引会造成性能浪费与存储冗余。」
这不是在「描述需求」。
这是在用自然语言写代码。一种更臃肿、更模糊、更难维护的「新编程语言」。你品,是不是这个理儿?
Karpathy 最近有个演讲,讲了个特别扎心的故事。他自己 vibe code 了一个叫 MenuGen 的 app,功能是拍一张餐厅菜单,然后生成每道菜的图片。他花了些功夫把整个流程跑通,觉得这 app 挺有用。后来有人告诉他:你只要把照片丢给 Gemini,加一句提示词,模型直接在原图上渲染出菜品图。
整个 app 根本不需要存在。
他的反思是:「我们不能只把 AI 当成加速器。如果你只是用 AI 把旧流程加速 10 倍,那你做出来的东西可能会在下一个模型版本发布时,被一行提示词替代。」
一行提示词。你花了几周做的 app,人家一行提示词搞定。
这让我想起《人月神话》里 Brooks 的那条线——偶然复杂性在这边,本质复杂性在那边。AI 对偶然复杂性简直是核弹级别的解决方案:语法、实现、调试、样板代码、API 对接,这些过去占据开发者 70-80% 时间的工作正在被快速自动化。但对本质复杂性——需求判断、架构选择、业务权衡、系统边界的划定——AI 连一颗子弹都算不上。
不是不够强,是压根儿不沾边。
陶哲轩最近说了一段话,我觉得精确概括了这种互补:「AI 擅长广度,人类擅长深度。我们必须重新设计科学的研究方式,以充分利用我们现在拥有的广度能力。」
软件开发也一样。AI 给了我们前所未有的广度,能快速尝试一百个方向。但深度——判断哪三个方向值得走——仍然是人的事。Brooks 40 年前画的那条线,AI 没有擦掉。反而让它变得比任何时候都清晰。
构建变得几乎免费了。「决定构建什么」成了唯一的战场。
说到这个我想起一个挺有意思的观察。很多人觉得 Vibe Coding 对传统开发是「屎山」生成器,但对 AI Agent 开发却意外地合适。因为 Agent Workflow 天然低耦合,每个模块是一个独立的数据处理流水线。当 AI 只需要关注一个具体模块的 Python 实现时,它的表现接近完美。
但这里有个吊诡的地方——Agent 开发的「低耦合性」本身,就是一种形式化约束。你还是在用结构化的方式切分问题,只不过切分的粒度变粗了。说白了,你以为你在用自然语言瞎聊,其实你还是在做架构设计,只不过你自己没意识到。
所以 Dijkstra 到底错没错?
我带团队用了一年多 AI 编程工具之后的判断是:他对了一半。而且是更重要的那一半。
他错了的是「自然语言编程永远不可能」这个判断。它确实可能了,而且正在以惊人的速度普及。Karpathy 把这种模式叫做「抬高地板」——让所有不会写代码的人都能用自然语言让计算机做事。这事儿,得嘞,真成了。
但他对了的是「自然语言的本质缺陷不会消失」这个判断。模糊就是模糊,歧义就是歧义,你用再大的模型也消除不了——除非你把 prompt 写得足够精确,精确到它本身就变成了一套形式化规范。不是不行,就是换了个马甲。
Dijkstra 在 EWD667 里还写了段话,我现在读起来觉得像是穿越过来的:「机器码几乎没有冗余,很快被认为是一个不必要危险的人机接口。于是高级编程语言被开发出来,我们学会了如何增强对愚蠢错误的防护。这是一个显著的进步,很多愚蠢的错误现在会导致错误消息而不是错误答案。」
他接着说了一句特别损的——这老头嘴是真毒:「即使这个进步也没有被普遍欣赏:有些人觉得无法忽略的错误消息比错误结果更烦人,有些人在评判编程语言的优劣时,似乎仍然把『编程的容易程度』等同于犯下未被检测到的错误的容易程度。」
把这段话里的「高级编程语言」替换成「AI 编程工具」,把「错误消息」替换成「AI 生成的代码能跑但有问题」——
48 年了,什么都没变。
我们只是换了一种方式犯同样的错误。
真的真的,什么都没变。
读者评论 2