用自然语言写代码,复杂不会消失只会转移
说起来你可能不信——我最近被一篇48年前的老文章打脸了。打得还挺响。
1978年,Dijkstra写了篇东西,标题就特别冲:《论“自然语言编程”的愚蠢》。当时我觉得,这老头儿也太极端了吧。现在我带团队用AI编程工具一年多了,从全组抵触到全员切Cursor,再到从Vibe Coding的狂热回归Planned模式……走完这一圈,我才发现。
老爷子说对了一半——而且是更要命的那一半。
先说说我们这一年的过山车。
去年我推全员Cursor的时候,团队经历了一个教科书式的闭环。
甜蜜期。 Vibe Coding真爽。动动嘴,Demo几分钟就能跑起来,快得像开了挂。前端同学慌得一批,因为后端的随便描述一下就能生成一个能用的界面。我们开始接受“不用想太清楚就能开始”的节奏。舒服。
然后翻车了。
真的翻了。
AI开始习惯性漏需求。代码架构松散得像沙堆,风一吹就散。上下文一长,它就开始“降智”。最要命的——我现在想起来还后怕——AI的服从性变成了毒药。它会顺着你模糊甚至错误的描述,在死胡同里一路狂奔,还跑得特别快。
说个具体的。上周清理旧代码发现的。
有一次我让AI定义users表,email字段加unique约束。代码能跑,测试能过,一切看起来岁月静好。后来偶然发现——是偶然,不是特地排查——它在回调函数里偷偷手动创建了一个idx_email索引。
问题在哪?unique约束本身就会自动生成索引。这个手动创建的索引完全是重复的。浪费性能,浪费存储。代码不报错,项目正常运行,但这个冗余就在那静静地躺了几个月。
要纠正这个问题,我得写一段比代码还长的prompt:“请使用Drizzle ORM定义users表,email字段添加unique约束,但禁止在回调函数中手动创建idx_email索引,因为unique约束会自动生成索引,重复索引会造成性能浪费与存储冗余。”
你品品。
这已经不是“描述需求”了。这是在用自然语言写代码。而且写得比代码还啰嗦。
还有一个更隐蔽的坑。
我在项目文档里明确写了:本地开发用dotenv加载环境变量,生产环境通过docker-compose注入变量,不需要dotenv。结果AI直接把dotenv装到了dependencies里。
应该是devDependencies。
代码不报错。项目能启动。但生产环境打包体积增大,不符合企业级工程规范。这种问题,你不review根本发现不了——等发现了,可能已经跑了几个月了。
这两个案例有一个共同点:代码不会报错,但都是明显的问题。不是隐藏的bug,是明晃晃的设计缺陷。
问题到底在哪?
这时候,Dijkstra 48年前写的那段话突然蹦进我脑子里。
他说什么来着?
形式化文本的美德在于,其操作只需满足少数简单规则;它们是排除各种胡说八道的极其有效的工具——而当我们使用自然语言时,胡说八道几乎不可避免。
绝了。
他还说了句更刺耳的:所谓自然语言的“自然性”,归根结底,不过是我们能轻松地说出那些荒谬性并不显而易见的话。
翻译成大白话:你用自然语言描述需求的时候,很容易觉得自己说清楚了,其实满篇都是你没意识到的模糊和矛盾。
这事儿我太有发言权了。用了这一年AI,我最大的感受就是——当你追求100% Vibe Coding准确落地时,你必须用“模糊”的语言去约束一个“极其确定”的结果。当你的约束严谨到足以让AI不犯错时,你的prompt其实已经变成了一套极其臃肿、低效的“新编程语言”。
复杂度没有消失。
它只是转移了。
Vibe Coding最大的幻觉是什么?降低开发难度等于消除系统复杂度。
真相扎心:AI把开发成本转移成了维护成本。项目从0到1极快,新手也能生成可用代码。从1到100呢?维护地狱。没人能读懂、修复、扩展AI生成的混乱代码。
短期利好:MVP验证、原型demo、一次性脚本——项目扔了也不可惜。
长期灾难:企业级系统80%的成本在维护与迭代。
回归期。
我们现在的开发流程变成了这样:描述意图 → 自动转化为Spec → AI分析排除矛盾 → 人类审查关键算法 → 设计测试套件 → 分拆执行。
你看这个流程的每一步,本质上都是在把自然语言的“毛坯”,精加工成形式化的“构件”。
我们又回到了Planned模式。
不是AI不行。是我们用AI的方式需要重新设计。
扯个题外话。
Karpathy——就是那个从去年12月起几乎没再亲手敲过代码的大神——最近在演讲里讲了个故事,我觉得特别说明问题。
他自己vibe code了一个叫MenuGen的app,功能是拍一张餐厅菜单,然后生成每道菜的图片。他花了些功夫把整个流程跑通,觉得这app挺有用。后来有人告诉他:你只要把照片丢给Gemini,加一句提示词,模型直接在原图上渲染出菜品图。
整个app根本不需要存在。
他的反思是:我们不能只把AI当成“加速器”。如果你只是用AI把旧流程加速10倍,那你做出来的东西可能会在下一个模型版本发布时,被一行提示词替代。
这话扎心。但确实是这么回事。
说回Dijkstra。
他到底对在哪?
他对在指出了自然语言的“毒性”——那种让你觉得自己说清楚了、其实满篇模糊的舒适感。这种毒性在Vibe Coding里被放大了十倍。48年前他写这篇文章的时候,可能都没想到会这么应验。
但他也错了一点:他低估了形式化符号和自然语言可以共存的可能性。
我们现在做的,不是用自然语言替代形式化符号,而是用自然语言作为入口,然后快速收敛到形式化的约束上。Spec是形式化的,测试套件是形式化的,架构约束是形式化的。自然语言只是那层皮。
这大概是Dijkstra没预见到的——不是“要不要形式化”的问题,而是“在哪一层形式化”的问题。这个问题我们还在摸索。说实话,我也不知道答案。
最后说一个我自己的判断。
AI对偶然复杂性——语法、实现、调试、样板代码、API对接——不止是银弹,简直是核弹。这些东西过去占开发者70-80%的时间,正在被快速自动化。真他妈好。
但对本质复杂性——需求判断、架构选择、业务权衡、系统边界的划定——AI连一颗子弹都不是。
Brooks在《人月神话》里画了一条线:偶然复杂性在这边,本质复杂性在那边。那是1975年,快50年前了。AI没有擦掉这条线,反而让它变得比任何时候都清晰。清晰得刺眼。
构建变得几乎免费了。
“决定构建什么”成了唯一的战场。
而这个战场,需要的恰恰是Dijkstra说的那种能力:用精确的形式化思维,排除胡说八道。
不是用自然语言。
是用脑子。
读者评论 2