ENZH

维护你代码的,已经不是人了

这几个月我基本不自己写代码了。落进仓库的改动,大部分是 agent 敲的,我只描述要什么、审一遍、把方向掰回来。

这听着像个效率故事。但真正变了的不是效率,是代码的读者换了人。

过去三十年,源代码的第一读者是一个会在这儿待很久的人。他攒上下文:记得 auth 为什么拆成两块,知道哪个配置周五绝对不能动。看不懂的,扭头问隔壁工位。代码可以含糊,因为总有个活人兜着那半截没写出来的东西。

Agent 没有这些。它每次开工都是失忆的。它对你系统的全部理解,是当场从磁盘上的文件、加上你愿意写下来的那点东西,重新拼出来的。它扭头问不了人,也不记得上周二。

所以仓库不再是"关于系统的文档"。它变成了 agent 编程时要对着写的那个接口。接口有好有坏,差别会在每一次调用上收你的钱。

含糊以前是一次性成本

人维护代码库,含糊是摊销的。第一个被 utils2 这种鬼名字搞懵的人,花一下午搞明白,然后他就懂了。成本付一次,存进脑子。第二年这文件夹不要钱。

Agent 维护,没有那个脑子可以存。每个会话都从冷启动开始。utils2 今天把这个 agent 搞懵,下周一个新会话照样被搞懵,再下周还是。含糊的成本不再是付一次然后记住,而是每个会话都重付一遍,只要还有 agent 碰这个文件。

这把很多老直觉掀翻了。"大家都懂这块干啥,不用写",在一个稳定的人类团队里是句合理的话。现在它是一笔反复征收的税。默契不再是小小的低效,它是仓库里最贵的东西,因为这个"部落"每开一次会就忘光一次。

好消息是,修法跟过去让代码库对人友好的修法是同一个:清楚的命名、明显的边界、能验证的行为。区别只在于,agent 时代不再给你"偷懒也没事"的余量。一个资深工程师靠记忆还能摸着走的乱仓库,换成 agent 就是反复迷路,而且按它干活的速度在迷路。

现在每个改动上线前,我就用一条验收标准:

一个全新的 agent 会话,只给它这个仓库,它能不能收敛到正确的理解、做出正确的下一步改动?

如果答案要靠我脑子里某个东西、某条 Slack、某个同事的记忆才成立,那这仓库就有 bug,哪怕测试全绿。

仓库其实暴露了三个 API

把仓库当接口看,会发现它对 agent 暴露的是三个不同的 API。坏起来的方式不一样,设计起来也不一样。

目录结构是导航 API。 Agent 干的第一件事是读路径。一个函数体都还没看,文件夹结构已经先讲了个故事:东西在哪、谁拥有谁、要改该去哪。features/billing/engine/calculateInvoice.ts 这条路径在真的干活:它说账单模块拥有它,是纯领域逻辑,大概有单测,不该 import React、不该碰网络。这些 agent 不打开文件就能推出来。

反过来,一个叫 Dashboard.tsx 的文件,里面又拉数据、又读 localStorage、又算定价、又渲染图表、又查权限。路径没给 agent 任何真信息。更糟,它在撒谎:它说"我是个仪表盘组件",实际它是半个应用。让 agent 去改个定价规则,它得读两千行才发现规则原来藏这儿。然后在上下文压力下,它把下一条规则又加进同一个文件,文件更烂了。

libhelpersmisccommonv2 这种顶层文件夹,是导航 API 里返回垃圾的那些端点。它们不声明归属,也不声明能 import 谁。它们沦为垃圾场,恰恰因为名字没给 agent 任何"别往这儿倒"的理由。

契约和 import 边界是约束 API。 导航 API 告诉 agent 去哪,约束 API 告诉它到了之后能干什么:谁能 import 谁,哪些值是别人依赖的稳定承诺,数据跨边界时长什么样。

这里最致命的是隐性契约,只活在团队集体记忆里的那种。只在解析代码里写了的 CSV 列顺序。在十一个文件里被当裸字符串敲的事件名 'user.updated'。三个功能都在写、却没人拥有的那个 storage key。人能扛过去,因为有人记得。Agent 改其中一个,根本没法知道自己刚刚弄坏了另一个服务里的生产者,因为仓库里没有任何东西告诉它这条边界存在。

这块我单开一篇细讲,因为它是手里杠杆最高的一招。把默契变成契约,是"能安全改代码的 agent"和"弄坏自己看不见的东西的 agent"之间那条分界线。

验证命令是校验 API。 最后,agent 得知道自己有没有成。人瞄一眼 diff 能大致心里有数。Agent 要是自己改、又自己当唯一裁判,它会朝自己的假设过拟合,然后宣布胜利:它是带着"我是对的"这个信念写的代码,再读一遍,当然还是确认自己对。

救你的,是一个确定性的方式去问仓库"这真的做完了吗"。一条命令,把类型、lint、测试、契约检查、架构规则全跑一遍,吐回一个 agent 没资格投票的、干脆的通过或失败。"做完"从一种感觉,变成一个 exit code。这条我后面也会回来讲:把"谁改的"和"谁认证的"分开,是这整个时代里最承重的想法之一,也就是 别让 agent 自己说"我搞定了"

这跟我给人用的那张图是同一张

最花时间才接受的一点是,上面这些一点都不玄。我一直在等"给 AI agent 设计架构"会要求某种全新的、外星形状的东西。它没有。

对 agent 好的结构,就是过去对一个刚进大团队的新人好的结构:一个对公开 API 有完美记忆、对你走廊里那些对话零记忆的新人。纯逻辑跟副作用分开,好让它不用把整个世界都搭起来就能测。明确的模块边界,好让一个改动留在局部、可回滚。名副其实的命名。一条命令就能跑的验证。我们一直知道这是好的盖法。只是过去有余量可以跳过:一个资深的人能把缺掉的结构装进脑子,硬是让东西继续转。

Agent 把余量抽走了。它装不下那个缺掉的结构。于是一个强团队悄悄吸收掉的乱,现在变成 bug,一个会话一次,按 agent 干活的速度往外冒。

我觉得这事让人清醒,不让人沮丧。它意味着我不用去学一门叫"给 AI 写代码"的新手艺。我只要去做那件本来就知道是对的、却一直找借口不做的事。我在 别写代码了,学管 AI 吧 里说过,工作正从"自己写"挪到"管那个会写的东西"。这篇是同一件事的另一面:你管的那个东西,对你过去偷的每一个懒都不留情。

所以整件事就这么一个反转。别再把仓库当成"你建了什么"的记录,把它当成那个正替你建东西的玩意儿、必须对着编程的接口。这个接口越干净,你的 agent 看起来就越聪明,不是因为模型变强了,是因为你不再让它每次调用都把你的烂摊子重新推导一遍。

模型是租来的,它按自己的节奏在变强。接口是你的。这是你能设计的部分,也越来越是决定 agent 能走多远的那部分。


这是"给 AI agent 盖一个能维护的仓库"系列的第一篇,一共四篇。下一篇讲 为什么每条边界都得变成契约。它跟 光有聪明的大脑还不够 那篇 runtime 的讨论是一套的,也跟 软件变成日抛品了 互为正反面——后者是从产品侧看同一场迁移。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0