三个模型,一颗暗雷:给 AI 重构上保险
codex-orchestrator 的 main.go 长到了 18782 行。593 个函数、106 个类型,全部挤在一个文件里。今天我把它拆了。
拆分本身不是这篇文章的重点。重点是拆分方案定稿前的那一个小时:我让三个不同家族的模型对抗式审查同一份重构方案,它们各自抓到了对方在原理上就抓不到的问题——其中一颗暗雷,编译、vet、全量测试、格式检查全部通过的情况下,会让两个线上功能静默崩掉。
一份看起来很稳的方案
先交代背景。拆 18782 行的单文件,最大的恐惧是"搬丢东西":某个函数在剪切粘贴中消失,某段注释和它的声明分了家。所以初版方案的核心是一套机械验收门禁:
go build/go vet/ 全量测试(138 个)全绿- 拆分前后用
grep "^func \|^type "生成顶层符号清单,diff 必须为空 help输出逐字节一致
编译器 + 测试 + 符号奇偶校验,三层保险。我当时觉得这已经算严谨了。
然后我把方案分别喂给了三个 reviewer:Codex(GPT,只给它方案文本,不让碰仓库)、Pi(DeepSeek,允许读仓库)、Kimi(K2.7,允许读仓库)。同一份 prompt,各自独立跑,互相看不到对方的结论。
每个模型抓到了不同的东西
Codex 把我的"机械保证"拆穿了。 它只看了方案文本,就指出三个漏洞:grep "^func " 匹配不到方法声明(func (r T) Foo() 开头是 func (,但更要命的是它根本不枚举 const/type/var 块内部的条目);const 块如果拆开搬运,iota 会重置、隐式值继承会断——而我的方案里恰好写了"把 const 块中属于全局的部分归位"这种危险指令;包级变量的初始化顺序跨文件后会变。三条全是"符号校验本身不可靠"的元问题。它给的替案是用 go/parser 生成 AST manifest:每个声明(含 receiver)、每个块内 spec、每条 doc comment 的 hash,拆前拆后 diff。
Pi 找到了那颗暗雷。 它读了真实代码,发现生产逻辑里有两处在运行时读取 main.go 的源文件文本——docs-drift-checker 和 roadmap-next-task-suggester 两个 routine 靠解析 cmdRunRoutine 函数里的 case "..." 字符串来枚举可运行的 routine 清单。方案要删掉 main.go。删掉之后:编译通过(字符串路径不参与编译)、测试通过(测试在临时目录里自建 fixture)、help 输出不变、符号校验不变——我设计的每一道门禁都拦不住它,但那两个 routine 上线即崩。
Kimi 给出了比 Pi 更好的修法。 Pi 建议把硬编码路径改成扫描全部 .go 文件,但 Kimi 指出这会连锁改动证据文案和测试断言——为了"零行为变化"的重构去改行为,本末倒置。它的方案是:main.go 不删,保留成一个 40 行的薄壳,只装 cmdRunRoutine 和一条注释,写明"这个函数必须留在这个文件里,因为有两处运行时自省依赖它"。零代码改动,零语义变化。它还顺手验证了那个 const 块(16144 行处)其实是策略规则 ID 表,纠正了我方案里的归属矛盾。
注意这个分工不是我安排的,是视角差异的自然结果:只读方案的模型专注拆解方案自身的逻辑漏洞,能读代码的模型负责戳破方案与现实的偏差。 同一个模型再聪明,也只能站在一个位置上。
修订、执行、验收
三家 findings 合并后(P1 四条、P2 七条),方案改到 v2:grep 换成 AST manifest、const 块只许整块搬、main.go 保留薄壳、外加一条静态检查专门盯着壳的完整性。然后把执行派给了一个便宜模型的 worker,在隔离 worktree 里干活,十条门禁全绿才许交。
验收的时候我没有采信 worker 的自报——所有门禁在它的 worktree 里重新跑了一遍,help 输出和测试名称集合与主分支逐一 diff。这是被坑出来的纪律:worker 报告"已 commit"而实际没有的事故,我遇到过不止一次。
最终结果:21 个生产文件 + 10 个测试文件,最大 1670 行;1982 条声明的 manifest 拆前拆后完全一致;138 个测试一个不少。行为零变化不再是一句口号,是一摞可以复核的 diff 为空的证据。
为什么是"三个模型"而不是"更仔细的我"
有人会说:那两处运行时自省,你自己仔细读代码也能发现。理论上是。但"仔细"不是工程保障。18782 行的文件,靠人(或单个模型)的仔细程度做安全网,和不系安全带靠开车小心是一个逻辑。
我在上一篇里说,AI 编程缺的不是模型能力,是外层控制系统。这次算是给那个论点补了一个具体切面:review 也是控制系统的一部分,而单一模型的 review 有结构性盲区——不是它不够强,是它和写方案的模型共享同一套先验。换一个模型家族,盲区的形状就不一样;三个家族叠起来,漏网的形状就很小了。
成本呢?三个 review 并行跑,总共十几分钟,其中两个用的还是订阅内额度。相比之下,那颗暗雷如果上线,是两个功能静默失效加一次深夜排障。
这笔账不难算。难的是每次都记得算。