AI 让写代码变快了。
一句需求,几分钟出一段能跑的代码;
遇到报错,把现象贴给 AI,它给出修复。
对很多开发者,写代码的效率和门槛都和过去不一样了。
于是产生一个问题:软件复杂性,是不是因此消失了?
如果代码可以快速生成、快速修复,过去困扰软件项目的"复杂性",是不是不再是个问题了?
软件复杂性是什么
要回答"复杂性是否消失",先要说清楚复杂性指什么。这里用 John Ousterhout 在《软件设计的哲学》里的定义。
Ousterhout 把复杂性定义为:使软件难以理解或难以修改的东西。他认为复杂性是软件项目失败的主要原因。
这个定义有两个关键词:理解、修改。
复杂性是指代码写出来之后,读它、改它、在它上面继续开发时有多费力。
复杂性以三种方式表现出来,Ousterhout 称之为三个症状:
变更放大:一个看起来简单的修改,需要改动很多处代码。比如把"每页 20 条"改成"每页 50 条",如果这个数字分散在 12 个文件里,就要改 12 处。 认知负荷:完成一个任务,开发者要在脑子里同时记住多少信息。比如调用一个函数,要记住它会改某个全局变量、第二个参数不能是负数、调用前要先初始化数据库。 未知的未知:改代码前需要知道某条关键信息,但这条信息藏在找不到的地方,甚至你不知道自己需要它。比如你改了函数返回值,测试都通过,但另一个模块依赖返回值里一个没有文档的字段,两周后线上出错。
这三个症状由两个更底层的原因造成,Ousterhout 称之为根因:
依赖:一段代码和另一段代码存在"改了 A 就必须改 B"的关系。依赖造成变更放大和认知负荷。 晦涩:代码怎么工作、为什么这样写,没有清楚呈现。晦涩造成未知的未知。
要记住的一点是:复杂性的来源是理解和修改,不是生产。
生产代码变快,复杂性消失了吗
AI 让代码的生产变便宜了:写第一版代码的速度提高,门槛降低。这是事实。
但前面说过,复杂性的来源是理解和修改,不是生产。这是两件不同的事:
生产:把代码从无到有写出来。 理解和修改:代码已经存在之后,读懂它、判断它、修改它、扩展它。
AI 降低了生产的成本,但没有同等降低理解和修改的成本。
理解一段自己没写的代码,判断它是否正确、是否处理了边界情况、改了会不会影响别处——这些工作仍然要由人来做。
代码是 AI 生成的,不等于它更容易被理解和安全地修改。
更关键的是,生产变便宜之后,需要理解和修改的代码总量反而增加。
代码一旦写出来进入代码库,就要被持续地理解和修改:修 bug、加功能、适应新需求。
AI 让生产变快,代码库增长更快,需要被理解和修改的代码就更多。
所以生产变快,不意味着复杂性减少。它可能让需要理解和修改的总量增加。
AI 能可靠地理解代码吗
有人会说,AI 能读懂任何代码,再晦涩、依赖再复杂,对它也不构成问题。真是这样吗?
这里要区分两件事:AI 能处理代码(读、生成),和 AI 能可靠地理解代码。这两件事不等价。
2023 年 Liu 等人的论文《Lost in the Middle: How Language Models Use Long Contexts》发现:当相关信息位于长输入的中间时,模型的准确率显著下降。
后续多项研究重复确认了这个现象。
Matt Pocock 提出过类似的说法:模型的上下文可以分成“聪明区”(smart zone)和“愚蠢区”(dumb zone)。
在聪明区里,模型专注、可靠;进入愚蠢区后,注意力下降,开始遗忘、犯错。
他把这个分界点放在大约 10 万 tokens,并强调它和模型宣传的上下文窗口大小无关。
他在社交媒体上说,即使某个模型支持 100 万 tokens 的上下文,其中也只有大约 10 万是“聪明”的,剩下 90 万是“愚蠢”的。
相关研究(如 RULER、context rot)也显示,模型真正能可靠利用的上下文只是宣传数字的一小部分,并且随窗口填满逐渐退化。
换句话说,宣传的上百万 tokens 的窗口,更多是营销数字,不等于可用的精确工作区。
对代码理解这种需要每一处都准确的任务来说,一旦超过聪明区,就不再可靠。
也就是说,AI 对代码的理解,在上下文变长、变复杂时会系统性退化。
它不是"不知疲倦地理解",而是越复杂越不可靠,而且退化从代码变复杂时就开始,不用等到超出上下文窗口。
所以说,AI 对代码的理解能力,会随复杂度上升而下降。
测试能作为完整的正确性标准吗
再进一步:如果人不需要理解代码,只要有一套单元测试和集成测试来判断对错,是不是就够了?
1972 年,计算机科学家 Edsger Dijkstra 在《The Humble Programmer》里写道:"程序测试可以用来证明 bug 的存在,但永远不能证明 bug 不存在。"
这句话有两层含义。
第一层:测试只能发现你预见到的错误。你写测试,是因为你知道某种行为应该怎样、某种失败会发生。
而前面讲的"未知的未知",正是你根本没预见到的失败——你不会为它写测试。
所以测试覆盖不到未知的未知。
第二层,测试是人的理解的产物。
写出能反映真实正确性的测试,本身就要求人理解系统该做什么。
如果人不理解代码,人也写不出可靠的测试。
你没法用一个本身依赖人的理解的东西(测试),去替代人的理解。
测试的质量上限,就是人当初的理解上限。
所以说,测试既覆盖不到未知的未知,又依赖人的理解才能写好。
描述现象交给 AI 修复,这个闭环可靠吗
那么遇到 bug,把现象描述给 AI,让它修复,是不是就形成了可靠的闭环?
自动程序修复(Automated Program Repair, APR)领域有一个公认的区分:plausible patch(通过测试的补丁)和 correct patch(真正修复 bug 的补丁)。两者不等价。
"通过测试但实际不正确"是这个领域专门研究的问题,学界叫 patch overfitting(补丁过拟合),有专门的综述论文。
研究人员做自动修复实验时,都要人工逐一检查通过测试的补丁是不是真的修对了,就是因为通过测试不等于真正修好。
也就是说,AI 给的修复可能恰好通过测试,但没有真正解决问题,或者只修了表象、引入了新问题(回归)。"测试绿等于修好了"并不成立。
再加上未知的未知:有时你连现象都描述不对,因为现象是表象、根源你不知道。AI 基于不完整的现象描述去修,可能修错地方。
从描述现象到 AI 修复bug,这条链路并不可靠——通过测试不等于真正修复,而现象本身也可能描述不全。
复杂性问题,是逐渐积累的
复杂性的增长不是突然发生的,而是逐渐积累的。
每一次改动,如果引入了新的依赖、让代码更难读懂、或者埋下一个测试覆盖不到的问题,复杂性就增加一点。
单次改动的影响很小,不容易察觉,但它们会累积。
这种积累每天都在发生:一段没被完全理解就接受的 AI 生成代码、一个通过了测试却没有真正修好的修复、一个测试没有覆盖到的边界情况——每一项都在增加系统里"需要理解却没有被理解"的部分。
积累到一定程度,系统就会进入这样一种状态:修改越来越危险,每次改动引入新问题的概率上升,开发速度下降,最终谁都不敢轻易改动。
这就是 Ousterhout 所说复杂性的代价——它不是某一次崩溃,而是逐渐累积的结果。
写在最后
回到开头的问题:AI 让写代码变快,软件复杂性消失了吗?
没有。
复杂性来自理解和修改,不是生产。
AI 降低了生产的成本,没有降低理解和修改的成本,反而让需要理解和修改的代码总量增加。
AI 不能可靠地理解任意复杂的代码;测试覆盖不到未知的未知,而且测试本身依赖人的理解;自动修复通过测试,不等于真正修好。
关键的一点是,测试无法替代人的理解。
这把问题的焦点从"AI 能力够不够"转到"正确性能不能被测试完全捕捉"。
后者的答案是不能。
所以在 AI 时代,复杂性没有消失,它只是换了一种方式积累。
理解代码依然是必须的,人的核心价值在于对代码是否正确的判断力上。
参考资料
John Ousterhout,A Philosophy of Software Design Liu et al.,Lost in the Middle: How Language Models Use Long Contexts Matt Pocock,Dictionary of AI Coding Garrit,Don't trust large context windows Edsger Dijkstra,The Humble Programmer Patch Overfitting in Program Repair: A Survey
夜雨聆风