可靠的编程智能体需要更好的代码库
更好的模型和更好的智能体框架都很重要。但智能体同样需要这样的代码库:正确的上下文容易找到、值得信任、拿来就能用。
本文为中文版,根据英文原版改写,而非逐句直译。
摘要
怎样让编程智能体在真实代码库里更可靠?
常见的答案是更好的模型和更好的智能体框架(harness)工程。两者都重要,也都在快速进步,但它们不是全部答案。代码库本身同样决定了智能体能工作得多可靠。
AI 让写代码变便宜了,却没有让“正确地修改软件”变便宜。真实修改中最难的部分不是敲出补丁,而是获取足够的上下文:补丁必须保持哪些东西不变,相关的决策分布在哪里,这次修改是否已经完整。
这笔隐藏的成本就是上下文成本(context cost)。人类团队一直在为它买单,代价体现为资历、入职培训、代码评审和口口相传的部落知识。编程智能体让这笔成本显形:一旦所需的上下文是隐性的、分散的、不可靠的或难以发现的,智能体就会失败。
上下文最小化原则(Context Minimization Principle,CMP)围绕这笔成本重新审视软件设计:对于系统现实中必须支持的那些修改,正确修改所需的充分上下文越容易获取,设计就越好。
一位程序员接手一个 bug。真正的修复很小:也许是两行代码、一个条件判断、序列化器里漏掉的一个字段、后台任务里一个错误的假设,或者权限检查里的一个边界情况。但这个补丁不是两分钟能完成的,它花了三天。
第一天用来重建系统本该如何运作,并确认这个 bug 真的是 bug。第二天用来顺着调用链往下追,读测试,翻旧的 pull request,追问为什么同一段逻辑出现在三个地方。第三天用来积累足够的信心,确认修改已经完整,不会在别处弄坏什么。等提交终于合入,diff 看上去几乎微不足道。可工作量并不小。
这道落差有一个名字:上下文成本。
上下文成本,是获取足够上下文以正确修改系统所付出的成本。写补丁本身,即编辑文件、修改测试、提交 diff,只是其中很小的、看得见的那一部分。其余都是上下文成本:弄清预期的行为是什么,相关决策在哪里,哪些假设必须保持,哪些地方必须一起改,这次修改的边界到底在哪儿。在成熟的代码库里,这通常占了工作的大头。
有经验的程序员即使叫不出它的名字,也能感受到这笔税。他们称之为入职成本、上手时间、系统熟悉度、遗留复杂度、技术债、部落知识。每个说法都抓住了一部分痛点,但没有一个直接点出背后的那项成本。这笔隐藏的税,就是上下文成本。
几十年来,人类程序员交这笔税交得太习以为常,以至于我们已经看不见它了。我们把它叫作经验,叫作资历,叫作“熟悉代码库”。资深工程师之所以常常能安全地改动系统,并不是因为系统简单,而是因为多年积累的修改上下文已经缓存在他们脑子里。
然后,AI 编程智能体来了,上下文成本随之显形。
智能体让账单显形
AI 编程智能体体验代码库的方式,和一位在团队里待了多年的工程师完全不同。它不记得两年前走廊里的那次谈话。它不知道计费任务之所以特殊,是因为某个大客户依赖一种没有文档的导出格式。它不记得那次让团队不敢再碰某个模块的事故。它也无法凭借组织记忆推断出,两条看起来很像的代码路径绝对不能合并。
智能体可以非常快地搜索、阅读、调用工具、运行测试、生成补丁。但它知道的东西,必须由人提供、由它检索到,或者编码在显式的构件里。够不着的东西,在实践中就等于不存在。
这正是智能体编程呈现出一种奇特的混合——既强大又脆弱——的原因。智能体能写出称职的代码:实现一个函数、更新一个 API、生成测试、重构一个文件,速度比大多数人都快。可在真实代码库里,它仍可能漏掉同一个决策被复制到的那一处;可能信任一个名义上存在、实际上并无意义的边界;可能更新了显而易见的那个测试,却漏掉了从未被写下来的不变量。
出现这种情况时,最省事的解释是模型还不够聪明。这个解释太浅了。
当前的智能体有很多局限,未来的智能体也会强大得多。上下文窗口会变大,检索会变好,工具调用会更可靠。智能体会学到更好的策略,用于浏览代码仓库、解读测试、询问缺失的信息。但底层的问题不会消失,因为正确的修改仍然需要充分上下文(sufficient context)。
更大的上下文窗口改变的是能承载多少信息,它不会自动判断哪些信息相关、可信、完整、充分。更聪明的智能体搜索能力更强,但总得有东西可搜;推理能力更深,但仍需要系统给出可靠的信号。
更强的 AI 不会消灭上下文成本,只会抬高管理上下文成本的层次。
这才是 AI 编程智能体的真正意义。它们对软件设计之所以重要,不只是因为它们会写代码,而是因为它们把一笔间接的税变成了直接的税:人类团队一直默默支付的成本,如今被明明白白地摆在了台面上。
现在,我们能看到智能体检索了哪些文件、在哪里搜索过、哪些测试失败了、漏掉了哪些修改、尝试了多少次补丁才变得正确。这些痕迹并不是设计质量的完美度量,它们取决于智能体、提示词、工具和任务本身。但它们揭示了过去藏在人类认知里的东西:一次正确的修改究竟需要多少上下文,这些上下文又有多难获取。智能体并不是在写代码上失败,而是在获取上下文上失败。
当智能体以这种方式失败时,它往往是在向我们展示人类工程师一直在交的同一笔税。
这不是 AI 的问题
人们很容易把智能体的失败看成当下模型的暂时弱点。这样会错过更深一层的教训。
当智能体修改了一个字段,却漏掉了 schema 迁移、校验器、UI 表单、分析查询和测试 fixture,问题不只是智能体搜得不够广。系统把同一个决策编码在多个地方,却没有让它们之间的关系可以被可靠地发现。
当智能体读了接口,仍然要翻看三个实现才能弄懂某个方法到底是什么意思,问题不只是智能体缺乏判断力。那个边界没有表达出一个可信的契约。
当智能体弄坏了某个行为,原因是关键不变量只存在于某位资深工程师的记忆里,问题不只是智能体接触不到这份记忆。系统本身依赖隐性上下文来保证正确性。
人类也受困于同样的条件,但他们拥有一种智能体通常没有的途径:那些从未被写下来的信息。人类工程师可以去问当初构建系统的同事,可以想起走廊里那次关于某个任务为何特殊的谈话,也可以回忆起那次让团队默默不再碰某个模块的事故。真正持久的不对称,在于能否接触那些只存在于人脑和对话中、而不在文件里的上下文。
这种补偿很有效,但也有代价。它让入职变慢,让重构变得可怕,让代码评审依赖于恰好有对的人注意到对的遗漏,让团队不敢改动老模块,让“简单”的任务膨胀成好几天的调查,让生产力分布不均,集中在少数几个脑子里缓存了足够上下文的人身上。
对人类来说,这种痛是弥散的。它表现为犹豫、疲惫、漫长的上手期、模糊的风险感,以及一种“这个系统很难改”的笼统印象。智能体让这种痛变得尖锐,把看不见的上下文成本变成了看得见的失败。
上下文问题不是 AI 造成的。AI 只是让这张账单再也无法被忽视。
软件由修改构成
要理解这一点为什么重要,我们得先把 AI 放到一边。软件工程常被描绘成两个阶段:开发阶段构建系统,维护阶段修改系统,仿佛写新代码和改旧代码是两份不同的工作。这种划分是虚构的。从第二次提交开始,每一次改动都落在一个已经存在的系统上。修改不是开发之后才发生的事,修改本身就是开发。
添加功能,是在修改当前系统;修复 bug,是在修改行为;重构,是在尽量保持行为的前提下修改结构;评审 pull request,是评估一次修改是否安全;测试,是检查一次修改有没有守住预期的约束。有用的软件不是写一次就完事的,它靠不断变化而存活。
如果每一次改动都是修改,而每一次修改都有上下文成本,那么我们自然希望这笔成本更小。决策放在哪里,哪些边界可以信任,哪些写下来、哪些留作默会,这些都决定了下一次修改会有多昂贵。软件设计,就是这些选择的总称。
我们通常用“优美”“优雅”“整洁”这类词来形容好的设计。这些词指向的东西是真实的,设计良好的系统用起来确实感觉不一样,但它们是审美反应,而不是解释。它们隐约指向却从未点明的那个底层因素,是下一次修改能以多低的代价获取所需的上下文。
一个设计如果能让现实中的未来修改更容易、更安全、更可靠,它就是好设计。一个设计如果迫使修改者在做出正确改动之前搜索过广、追踪太多不可靠的路径,或者过度依赖隐性知识,它就是坏设计。
一个小模块,如果行为藏在配置和框架魔法里,可能很难修改;一个大系统,如果决策边界清晰、测试把重要的假设变成了可执行的检查,也可能很好修改。修改一个代码库的难度,并不取决于它有多少代码,而取决于获取足够上下文以正确修改它有多昂贵。
上下文最小化原则
这就是上下文最小化原则(CMP)背后的想法。
对于系统现实中必须支持的那些修改,正确修改所需的充分上下文越容易获取,设计就越好。
CMP 不是一种新的审美,而是给软件工程师一直在做的权衡起一个名字。边界之所以有用,是因为它让我们可以停止阅读。测试之所以有用,是因为它保存了一条我们不必再去记住的行为事实。架构之所以有用,是因为它告诉我们某个决策应该放在哪里。约定之所以有用,是因为它把搜索变成了导航。
这些实践表面上各不相同,底层做的却是同一类工作:让未来修改所需的上下文更容易获取。
这也解释了它们为什么会失效。无法信任的边界只增加了间接层,却没有减少理解的负担。一个不再能预测决策位置的架构,就沦为了仪式。为一个永远不会到来的未来而建的抽象,就成了过度设计。只存在于某人脑子里的约定,就成了部落知识。
CMP 并不取代判断,而是让问题更锋利。我们不再抽象地问代码是否整洁、简单或优雅,而是问:这个设计是否让现实中的修改所需的上下文更容易获取?
这个问题小到可以在实践中使用,又大到足以把许多由来已久的争论联系起来。作为第一性原则,这也就够了。CMP 的要点不在于每个设计决策都已经能用一个完美的数字来度量,而在于软件设计一直缺少一种衡量权衡的共同货币。上下文成本就是这种货币。
不会变便宜的那部分工作
AI 将会写下大部分代码。这已经不是问题,它在常规改动上已经在发生,并会扩展到更难的任务上。时间线是两年还是十年,并不改变方向。生产代码的成本正在崩塌。
新的问题是:如何把这种廉价迅速转化为可靠的系统。生产代码不等于构建系统。每一批廉价的代码,都得落到一个能吸收它而不失去一致性的代码库上。一次改动必须触及哪些决策,要保持哪些假设,哪些边界守得住,哪些测试承载着重要的约束,这些都决定了 AI 的速度最终带来的是可靠的系统,还是更快的一团乱麻。代码生产越快,设计能力就越重要。
软件设计一直很有价值,却一直很难为它辩护。“优美”“优雅”“整洁”“简单”,这些都是审美主张,而在另一边摆着“按时交付”的会议室里,审美主张往往会输。CMP 改变了这一点。它用一个团队真正能争论的问题取代了审美问题:对于系统将来必须支持的修改,这个设计是否让上下文更便宜了?这个问题小到可以用在单个 pull request 上,大到足以为一个架构决策辩护,又足够扎实,不会消解为个人偏好。
CMP 给了设计一个有答案的问题。
这个答案很少是一个精确的数字,但它足够具体,可以拿来评估,也足以让人在争论中被说服。设计不再是资深工程师比划出来的东西,而成了团队可以推理的对象。
对个人程序员来说,这改变了什么值得练习。既有的设计原则和模式,如 SOLID、DDD、整洁架构等,并没有贬值;它们每一个都是管理上下文成本的、经过检验的答案。在这些原则之外,有一个习惯会产生复利:在修改时留意自己默默携带的上下文——脑子里持有的假设、记住的依赖、没有重新推导就信任的先前决策——并尽可能把它们推进构件里,让下一位修改者不必重新发现你已经弄明白的东西。这是个小习惯,但日积月累,好的设计就不再是一门抽象的学问,而成为日常的条件反射。
未来也绕不开上下文
一种可能的反对意见是:未来的 AI 也许会自动解决这个问题。也许智能体会强大到能读完整个代码库,推断出架构,重建缺失的决策,在无需人类为上下文而设计的情况下做出正确的修改。
也许真会如此。但这并不会让 CMP 失去意义,它只意味着智能体学会了更高效地获取上下文。更多的智能并不会消除上下文管理的需要,只会改变上下文管理的形式。
未来的 AI 也许会发明比我们今天的模式更好的软件组织方式。它可能生成新的索引,维护活的文档,从观察到的行为中推导测试,或者持续绘制整个代码库中决策之间的关系。它可能以我们尚未想象到的方式让上下文获取更便宜。但无论这些机制最终是什么样,它们产出的软件仍然会是优美、整洁、优雅的,而这一次我们能够解释,而不只是感觉:它的组织方式让下一位读者——无论是程序员还是智能体——只需更少的上下文,就能理解它并正确地修改它。
账单已经摆上桌面
程序员一直知道,有些系统比另一些更容易改。我们熟悉那种代码库带来的感觉:下一步显而易见,边界清晰,测试告诉我们什么重要,命名把我们引向正确的地方,架构收窄了搜索范围。
我们也熟悉相反的感觉:每次改动都要打开另一个文件,每条规则都有另一个例外,每个抽象都在泄漏,每个测试不是脆弱就是无关,没有人确定真正的决策到底在哪儿。
这种差别不是品味问题。它是软件工作中最核心的经济事实:软件必须变化,而每一次正确的变化都需要充分上下文。
好的软件设计,就是让未来修改所需的上下文更容易获取的一门学问。
这笔隐藏的税一直都在。AI 没有发明它,AI 揭露了它。既然账单已经摆上桌面,我们就可以开始针对它来做设计。软件设计也将不再是少数工程师碰巧具备的一种感觉,而成为一个团队可以共同建设的学科。