传统抽象(编译器、内存管理)底层的稳定性让我们可以安全地不再检查。但AI生成的代码不同——每个片段都正确,整体却变得不可解读。当一个工程师写了40个相似的逻辑时会主动合并,但AI不会,它只是无痛地重复,直到系统成了一团散落真相的迷宫。

软件里最古老的安全网失效了。没有谁做了决定,它就这样悄无声息地滑走了。
我想认真对待这件事,因为这根本不是纪律问题。是我们接受了一种新的抽象,却没有用检验以往所有抽象的那套标准来检验它。
先做一个区分,因为说到底这才是关键。代码并非不可读。每一段你自己都能打开跟下来。我们失去的是读“系统整体”的能力——看一眼就能明白它在干什么。部件依然清晰,但它们拼成的形状却再也看不穿了。
工程学一直在放弃那些不再需要手工完成的事。我们不再写汇编,不再管内存,不再去读编译器生成的机器码。每一次放弃一些可见性,都换来了好价钱。
每一次交换背后都有共同的特质。我们停止检查的东西,是安全的。计算器每次都返回相同的结果。编译器是确定的,如果你真需要,还能检查它做了什么、逻辑是否通顺。我们不看了,不是因为看不可能,而是因为没必要。底层的活儿已经定下来了——一次检查,永远正确。
这是让一切运转的安静条件。我们去掉的,是那些再也不用检查的工作。
AI去掉了——我们仍然需要检查的工作。
不是全部。AI代理(agent)产出的很多内容你可以安全地放手:测试、迁移、胶水代码,就像你放手汇编一样。麻烦的是那些你仍然需要理解的部分,而它不会声张自己与众不同。
它也不会大声失败。一个坏掉的编译器通常坏得显而易见。而AI生成的错误代码,看起来通常和正确代码一样。能编译,能通过明显的测试,读起来就像有经验的工程师写的。错在判断,不在语法——而判断在diff里是不会显示的。
而且量很大,大到了任何评审流程都吸收不了。所以我们停止了检查,理由很理性:太多了,大多数又是好的。留下的是一种新的抽象,它不具备旧抽象的那些让我们可以放心不看的特性。
坑就在这里。
我们当时在做一个常规功能。核心系统会触发事件,我们要把这些事件和各种数据属性同步到第三方平台(做客户消息推送)。做法是固定的:接通一个事件,验证能用,然后重复这个模式给所有其他事件。
其他事件有很多。几十个,每个都是同样想法的微小变体。所以我们做了聪明的事:先做好一个,确认模式成立,然后让AI代理来完成剩下的。因为每个案例都和之前的极其相似,第一个又是对的,我们就没有仔细审查剩下的。
每一个单独来看都是对的。代理忠实复制了模式。每个事件同步,每个属性落在该落的地方。如果你检查其中任何一个,都会签收。
但代理从来没有“合并”。一个人造第十个、第二十个、第四十个相同的东西时,会感到重量,然后会去找一个中心位置来统一路由。这个本能不是整洁癖,而是你知道自己以后还得回来理解这个东西。代理没有“以后”。它造第四十个案例和第一个一样轻松,直接挂在那里。结果是我们的事件同步散落在整个系统里,没有一个地方你能一眼看全。

每一部分都正确,整体却不可读。
什么也没坏。一切都能跑。但我们再也无法通过观察来回答一个基本问题:我们往外发了什么,什么时候发的?
这就是我反复强调的区分。正确是局部的,可读是全局的。
你可以验证每一个变更是否同样正确,却错过了系统整体已经变得不可解释。没有一个正确决策保护了整体的可读性,因为整体从来不是单一任务的问题。没有人负责合并,所以没人合并,而每一段正确的代码都让全貌更难读了。
然后可读性不再是抽象概念。几周后我们要加更多事件,发现有些事件在毫无道理的时间触发。调试意味着手工重构数据从哪儿发出——这正是分散结构导致的难题。查的过程中我们又发现了第二个问题:代理对哪些事件该在什么时候发送做了错误假设,误解了业务上下文,而这种错误任何单个diff都看不出来。
路径错了是真正的bug。但不可读性让它藏了起来。如果所有事件都经过一根管道,错的事件就会明摆在那里。可读性不是系统跑起来之后再加上的好东西,它是让下一个问题能被找到的前提。

它从不厌烦,也从不回来。
你可能会称这种合并本能为好的工程能力。我觉得不完全是。我认为这是长周期下的利己行为。
当你做的东西自己会一直拥有时,你会做得不一样。你会是那个半夜调试它、六个月后扩展它、别人问它为什么这么做时需要回答的人。那个未来影响着你的做法。你让系统可理解,因为你自己需要理解它。可读性不是你对代码施加的美德,而是预期中的所有权滋生产物。
这就是为什么外包出成果如此难以真正做好。写代码的人和住在代码里的人是两拨人,所以让系统保持可读的隐性激励消失了,没有任何流程能完全替代它。
AI是这种行为的极致。它和代码库没有利害关系——明年不需要它维护,冬天也不用,因为不存在一个明年还会回来的版本。每次访问都是冷启动。它不会因为要解开自己写的乱麻而沮丧——它只是解开,每次,以零成本。你可以从工具的回应中看到这种缺失:当你指出问题,它们立刻同意,修复这个实例,但不会把任何东西带到下一个文件。没有任何东西拉着它们走向易懂,因为没有东西会让它们为不付出代价。
那对于一个以前免费、现在不再自动来的属性,你能做什么?
不是逐行阅读。那条边界已经消失,不会再回来,假装不是这样就是怀旧。大部分生成的代码可以不用读,就像你不读汇编一样。界限从来不是你读多少,而是你让什么保持可读。数据流动的形态、业务意图编码的地方、那些重构成本高昂的决定。现在的任务是主动保护这些,因为写代码的那个东西不会这么做。
第一步是所有权。找一个有名有姓的人,让他负责解释一个系统,而不仅仅是交付它。主人会建设可读的系统,因为他们预期会被问“为什么这么做”。所以重现这种预期,你就重现了大部分本能。这也是为什么在构建过程中,人类拥有结构,那个承载大量数据的“骨架”。你来建主通道,代理往里面挂各个分支。不是因为设计优先是一种美德,而是因为那个生产大量数据的东西没有理由收敛到单一的形态。
第二步是让它保持可解释,并把这一点当作系统的真实属性,而不是锦上添花。如果没人能说清楚一段代码为什么能工作,那就是缺陷——即使代码正确。看到不可读的地方就修,像修一扇破窗户一样,不用搞成仪式。对那些错误代价高昂的部分,用老办法:让大家坐下来,让别人从代码中口述它在做什么。如果说不出来,那就是发现。
第三步是停止只优化给AI看。有一种流行的想法:目标是让代码库变得对AI友好。它只说对了一半。现在代码有两个读者:AI和必须为它负责的人。目标不是AI觉得好改的代码库,而是经过AI触碰每一个角落之后仍然能被人类理解的代码库。如果你只优化第一个读者,你就把本文讨论的那个问题直接发了出去。

第四十个副本曾经令人痛苦。痛苦就是警告。
我们抓住了自己的问题。代价是一个困惑的下午和一个重构,没出事。但那是运气身着险情的伪装。
我一直在想的是,AI根本没有制造这个问题。它只是移除了那个曾经警告我们的摩擦。用手写第四十个几乎相同的东西是烦人的,烦人就是信号:停下来,合并。过去我们常把这归功于判断力。但很大一部分只是人们拒绝被烦到第四十一次。
以前有两件事几乎免费地保持了系统的可读性:做重复手工工作带来的近期摩擦,以及有人需要长期住在代码结果里这个远期事实。AI感觉不到两者。它从不厌烦,也从不回来。
所以可读性不再自己照顾自己了。它是一项你必须刻意维护的纪律,否则你就看着它一次一次合理决定地离开。
免费获取企业 AI 成熟度诊断报告,发现转型机会
关注公众号

扫码关注,获取最新 AI 资讯
3 步完成企业诊断,获取专属转型建议
已有 200+ 企业完成诊断