☰
AI重写83万行代码:三周128个PR的存量重构实践
2026/9/28 20:33:09 网站建设 项目流程

128个PR、83万行代码、三周时间,GitHub把“让AI重写自己”这件事从一个口号变成了公开数据。很多人的注意力都停在“AI写代码”这三个字上,但我更关心另一件事:83万行代码是怎么被拆成128个PR,又在三周内安全合入主干,而没有把整个仓库搞崩的。这篇文章从一个长期做存量系统重构的工程师视角,把这套流程从拆解思路、AI介入方式、PR合流、测试保障到翻车现场,完整复盘一遍。无论你是技术负责人,还是正在研究AI辅助编码的开发者,都能在里边找到自己项目里可以直接复用的部分。

1. 项目全貌:三周、128个PR、83万行代码是怎么拆出来的

1.1 为什么要让AI重写存量代码

先别急着聊AI,回到最开始的问题:一个正常运转的代码库,为什么要冒风险重写?我见过太多“重构一时爽,事后火葬场”的项目,所以拿到“AI重写自己”这个题目时,第一反应是去看动机是否成立。

存量代码最大的问题是技术债的复利效应。同一个分页逻辑在十几个模块里各写一份,有人用LIMIT 10 OFFSET 20,有人用limit(10).skip(20),还有人在SQL里硬拼字符串。这种代码不是不能跑,而是每次改需求都要把所有副本找出来逐一同步。维护成本会随着模块数量指数上升,最终超过新增功能的收益。这时候重写的价值就出来了:不是“看代码不顺眼”,而是把重复逻辑收敛成公共组件,把隐式依赖改成显式接口。

那为什么偏偏是AI来做?因为这类重写有一个共同特征:机械性极强、模式高度重复。AI非常擅长“照着旧行为写新代码”,尤其是在目标模式明确的情况下。比如“把这段基于字符串拼接的查询改成参数化查询”“把这三个工具函数合并成一个类”,这种任务让工程师手工做也能完成,但会消耗大量低价值时间。AI参与之后,初级工程师可以专注审查和补齐边界,而不是泡在复制粘贴里。

还要澄清一个误解:标题里的“重写自己”不是推倒重来。真正发生的是代码现代化,更接近一种受控的迁徙。旧模块被切成小块,逐一转换成新结构,每一步都能单独验证、单独回滚。83万行规模听起来吓人,拆成128个PR之后,单个变更的体量反而比很多日常需求还小。这也是整个项目能三周走完的前提——大工程不是一口气做完的,而是被拆成很多小步快跑。

1.2 128个PR的拆解逻辑与工作量判断

为什么是128个PR,而不是一个大PR或者500个碎PR?这是项目启动时就要回答的问题。一开始就扔一个83万行的巨型PR,代码评审根本没法展开。几千个文件的diff在屏幕上加载完都会卡顿,更不用说CI要跑几个小时,测试失败时连定位都困难,出了问题回滚更是灾难。

反过来说,PR也不是越碎越好。每个PR都有固定成本:触发一次CI、至少一次人工审查、一次合并操作。如果一个PR只改几十行,那128个PR远远不够,可能得拆成上千个,管理成本和排队等待时间就会压过策略收益。128这个数字,其实是“可评审性”和“管理开销”之间的平衡点。按83万行除以128粗略估算,平均每个PR大约涉及6500行代码变更,去掉注释和空行后,真正有业务逻辑的改动大概是两千到三千行,这个体量刚好在一两个工程师可以认真审完的范围里。

拆解顺序上,首先要画依赖图。哪些文件是最底层工具库,哪些是业务模块,谁依赖谁,必须列清楚。重写不能从业务层开始,因为底层结构没定,上层代码很快会因为API变动反复返工。通常的做法是先重写无业务逻辑的基础层:日期处理、字符串工具、数据结构封装这一类;确认这块稳定后,再推进数据访问层和对外接口;最后才是业务流程编排。依赖图一旦确定,每个PR的边界就顺理成章了。

工作量判断也有一个简单公式可以套:总行数 ÷ 单PR有效行数 × 单PR周期。假设一个PR从AI生成到人工审查、修反馈、测试通过需要半天,128个PR单线程就要64个工作日,显然三周内做不完。所以必须靠并行。把团队按模块分成几个平行小组,每个小组维护自己那批PR,共享底层的合并序列。我见过不少重构项目,前期拆解做得很好,最后却卡在“串行依赖”上,所以这里提前说清楚:三周83万行,靠的不是个人英雄主义,而是正确的并行结构。

2. 核心引擎:AI如何参与代码重写

2.1 让AI重写代码的三种介入方式

AI在这个项目里不是一个“自动写代码的按钮”,更像一个可以持续对话的结对程序员。实际操作中有三种介入方式,按自动化程度从低到高排列。

第一种是“逐函数重写”。把旧代码块、目标接口定义、预期行为描述一起丢给AI,让它生成新版实现。这种方式最可控,适合处理逻辑复杂、边界条件多的核心函数。人需要做的事情是写清楚prompt,然后逐行审查生成结果。用起来非常像把需求文档念给一个熟悉项目语法的工程师听。

第二种是“文件级批量重构”。直接把整个旧文件作为上下文,给出新风格规范,让AI一次性重写全部内容。这种方式效率很高,但风险也明显:文件里的隐藏依赖、历史遗留的特殊处理逻辑,AI可能照搬也可能自作主张。我在实际操作中会强制要求AI在重写时保留所有注释,因为注释经常是理解边界条件的唯一线索。

第三种是“AI Agent自主拆任务”。给它一个明确的最终目标、代码库的读取权限、以及一份验收标准,让它自行探索并拆出子任务。这种方式适合那些模式重复、边界清晰的模块,比如自动生成数据模型层、批量转换接口参数类型。但它不能失控,我在项目里会设置一个硬性限制:Agent做完一个文件、或做满一定数量的改动后必须停下来等人工确认,不能一口气改完一组再提交。

我自己的体会是,三种方式经常混合使用。一个模块里如果8成代码是重复模式,就用第二种或第三种批量处理;剩下那2成有特殊业务逻辑的,单独拿出来用第一种逐函数处理。这比“全交给AI”或者“全人工写”都快,也稳。

2.2 人工和AI的分工边界:什么代码适合AI改

不是所有代码都适合丢给AI重写。这里有一条我反复验证的判断标准:如果这块代码的意图能够从注释、函数名、接口定义中直白读出来,那它大概率适合AI;反过来,如果理解这段代码需要翻五个文件、查三次历史提交记录,那就说明AI没有足够上下文,人工必须先进场。

适合AI的部分包括:纯工具函数、参数校验逻辑、DTO和模型定义、重复的分页查询、标准化日志输出、按照接口文档生成客户端代码。这些代码的特征是输入输出明确,几乎没有不可预期的副作用。AI只要看到接口签名和目标规范,就能生成出质量稳定的结果。

不适合AI的部分包括:涉及状态流转的支付流程、并发资源访问、强一致性的数据迁移脚本、带复杂权限判断的业务规则。这些代码的“正确性”依赖于大量隐含约束,比如某个操作必须在事务里执行、某个状态只能从特定入口进入。AI不会知道这些约束,硬让它重写,生成出来的代码看起来天衣无缝,一上生产就是事故。

实际操作中,我会给不合适的代码做标记。标记规则很简单:如果这段代码在过去一年里有超过一次线上故障记录、或者有专门的try-catch处理幂等,那就列入“人工保护名单”。名单上的代码不参与AI批处理,最多让AI做辅助建议,真正的改动由人完成。这个保护名单,是整个项目能安全落地的重要底座。

3. 实操过程:从单个PR到128个PR的合流

3.1 单个AI重写PR的标准化流程

当128个PR不是随机产生,而是遵循同一套流水线时,审查和追踪成本才会降到最低。我总结的单个PR流程分为五步:AI生成 → 基础编译 → 人工语义审查 → 自动化测试 → 合并。

流程里最关键的是第一步生成端的约束。要求AI每次只输出与本PR相关的文件,不要顺手调整格式化风格,不要“帮”你重构相邻代码。AI有个坏毛病:会在完成指定任务后自作主张改善邻近代码,这在批量重组时非常危险,它会让diff变得不可控。我在prompt里会专门加一句:“只修改指定范围内的代码,不改变任何无关内容。”

然后是PR模板,每个AI重写PR必须包含四块内容:变更摘要、风险说明、测试证据、回滚指引。模板写清楚后,审查者一眼就能判断要不要深入看。一个可参考的模板是这样的:

## 变更摘要 - 重写范围:src/services/order/checkout.ts - 重写前逻辑:基于字符串拼接生成订单SQL,存在注入风险 - 重写后方案:使用参数化查询 ## 风险说明 - 影响接口:/api/v1/orders/checkout - 变更点:数据库查询构造方式完全替换 - 已知风险:无,原有错误处理逻辑全部保留 ## 测试证据 - 相关单测:tests/services/order/checkout.test.ts,新增12条用例 - 本地冒烟:已跑通下单、秒杀、超时取消三个场景 ## 回滚指引 - 回滚命令:git revert <commit-sha> - 依赖项:无,本PR不涉及数据库表结构变更

这套模板的好处是,把“AI是不是写对了”这个很难全局回答的问题,转化成几个可以逐项验证的问题。审查者不用从头读所有代码,先看风险说明里圈定的接口范围,再对测试证据做抽查,效率会高很多。

3.2 128个PR的合并顺序与冲突控制

128个PR不是随便哪个先合哪个后合,合并顺序错了,后面一半的PR都会反复冲突,光是解冲突就能耗掉三周里的大部分时间。我的做法是画一张“合并路线图”,把PR分成几个批次,批次之间严格串行,批次内部可以并行。

第一批是基础工具层的PR,通常只有十来个,但所有其他PR都依赖它们。这一批必须最优先合完,并且每合一个,就要立即跑一次全仓库的回归测试,确保底层变更没有破坏现有功能。第二批是数据访问层和接口定义层,这一批会和第一批产生直接依赖,但只要第一批的API没有频繁变动,冲突仍可控。第三批是业务流程层,这一批数量最多,可以拆给多个小组合并。最后一批是清理层,负责删掉不再使用的旧函数、旧引用、过期注释。

冲突控制方面有几个操作细节值得说。所有PR在合并前都要求基于最新的main分支rebase,不允许使用merge提交。合并窗口也有限制:每天只在固定时间合并两批PR,比如上午一批、下午一批,其余时间只更新分支不合并。这样可以保证冲突集中出现,而不是随机爆发。一旦某个PR因为底层变更导致冲突,第一时间不是硬解,而是查冲突文件是不是也出现在其他等待中的PR里。如果是,那要考虑调整合并顺序,避免所有PR都撞在同一个文件上。

三周83万行的高强度,决定了对合并没有太多回旋余地。所以在项目启动前一天,我会把所有参与者的分支命名做统一规范:pr-{模块}-{序号},并且要求所有人只从统一的基座分支切出,不在别人分支上开自己的分支。这些看起来不起眼的规则,实际决定了后两周的合流是否顺利。

4. 保障体系:如何在83万行代码变更中不翻车

4.1 测试金字塔与CI自动化防线

大量代码变更时,最可依赖的不是人工审查,而是一套能快速反馈的自动化防线。这次AI重写项目里,测试策略按金字塔分了三层。

底层是单元测试,重写后的每个函数至少覆盖正常输入、异常输入、边界值三种情况。对AI生成的代码,我会额外要求测试数量和改动行数成正比。比如一个函数重写后变化超过200行,那新增测试用例不能少于10个。这不是形式主义,而是AI生成代码最常犯的错误就是丢掉边界守卫,单测是拦截这类问题效率最高的手段。

中间层是合约测试和快照测试。重构过程中最怕的是“逻辑没变,接口变了”。合约测试专门盯对外接口的输入输出格式,一旦AI生成的代码改变返回结构,测试立刻红灯。快照测试则负责UI渲染和序列化结果的比对,专门抓AI“偷偷调整了输出格式”这种隐性变化。

最上层是集成测试。重写涉及跨模块调用时,光靠单测是不够的,必须真实跑通一条完整链路。比如支付模块重写后,要实际走一遍从下单到扣款到回写订单状态的全流程。集成测试不需要太多,但每个核心链路至少要有一条,否则就是拿生产环境当测试环境。

CI流水线的检查项也要针对AI代码做特殊配置。常规的类型检查、lint、覆盖率检查必须有,但要额外加三条:检查是否存在未使用的变量和函数,因为AI经常生成“预留”代码;检查是否引入重复工具函数,防止AI把别人的代码新复制一份;检查注释是否覆盖所有导出的public函数,避免重写后文档断层。这三条规则全部能通过,我才会允许PR进入人工审查阶段。

4.2 代码审查的节奏:人该审什么、怎么审

很多团队搞AI重构,最担心的是代码出问题没人发现。我的经验是:不需要让每个审查者从头到尾读每一行代码,但要保证每个PR至少有一个真正理解业务的人,认真读一遍关键diff。

人工审查应该盯四件事。第一,AI是否改变了原有业务行为,特别是错误处理分支、默认值、超时时间这类“平时注意不到但一旦出事就致命”的细节。第二,是否存在隐藏依赖被破坏,比如某个工具函数在其他地方被以特定顺序调用,重写后顺序变了。第三,并发安全性,AI很容易把同步代码改成异步逻辑时不加锁,或者反过来。第四,是否出现了“假重构”:只是换了种写法,底层还是那段已经有问题、需要淘汰的逻辑。

审查节奏上,我采用“分层审查制”。第一层是AI自审,让另一个AI对生成的代码做静态差异检查,专门标出可疑点。第二层是自动化工具,前面说的CI检查。第三层才是人工。人工审查不需要覆盖全部文件,但重点文件必须逐行看。我给自己定的规则是:核心模块的大约百分之三十的文件,我必须亲眼看完,其余可以依赖工具加随机采样。

为了不让审查成为瓶颈,需要给审查者足够的上下文。每个PR关联的依赖图、必要的历史提交记录、以及旧代码的位置,都要提前挂在PR描述里。别小看这一步,很多审查卡壳是因为审查者还得自己翻代码找旧逻辑在哪,一旦找不到了就只能草草approve。上下文越清晰,审查速度越快,质量也越高。

5. 常见问题与排查经验实录

5.1 AI重写代码的几个典型翻车现场

项目做多了,踩过的坑基本都能分类。AI重写代码的翻车现场,我见得最多的是下面四类。

第一类是调用了一个不存在的API。原因通常是AI从训练数据里记了一个相似库的函数名,在项目里其实没有引入这个依赖。这个错误在编译阶段就能暴露,风险不算高,但容易让人放松警惕。难的是那些“编译能过但运行就挂”的版本,比如参数顺序被悄悄调换,类型又刚好兼容,这类问题一定要靠测试覆盖。

第二类是丢失空值保护。旧代码里常见的if (user != null && user.address != null)在AI重写后可能被合并成user.address.city的链式访问。AI会默认数据永远合法,但真实线上数据经常不合法。这个问题最隐蔽,因为正常单测永远测不出来。我的对策是在prompt里明确要求保留所有空值检查,并额外加一条规则:AI生成代码里如果去掉了任何防御性判断,必须显式说明原因。

第三类是把旧代码的低效逻辑原封不动作了一遍“格式化搬运”。AI没有真正重写,只是把代码拆得更碎,行数翻倍,性能特征原样保留。这类问题靠代码审查抓,看到diff里大量新增但逻辑等价的内容,就要怀疑是不是假重构。识别之后,我会要求AI重新针对性能瓶颈做一次真正的方案设计,而不是继续做文本级替换。

第四类是把带副作用的函数改成了纯函数。AI看到函数内部有外部状态修改,误以为这是坏味道,自作主张改成返回新对象。表面上看更优雅,实际上破坏了原本依赖这个副作用的调用方。这种问题单测如果只是分别测两个函数也能通过,只有集成测试能把问题暴露出来。

5.2 排查工具链与速查表

面对83万行级别的变更,问题排查不能靠肉眼扫描代码,要有一套高效的定位方法。我把排查过程分成三个层面:构建失败、测试失败、运行时异常。

构建失败一般最容易定位。先看报错发生的文件,确认是不是依赖的底层库还没合入,这种情况直接查合并状态就好。排除依赖因素后,看是不是AI生成的代码引用了不存在的符号。我常用的工具是编译器的错误输出配合全局搜索确认,基本五分钟内能解决。

测试失败要区分是单测失败还是集成测试失败。单测失败先看是不是测试数据本身过时,AI重写代码后测试输入和代码行为不一致。集成测试失败优先级最高,因为它往往意味着跨模块行为变化。这时我会把失败用例的调用栈贴回给AI,要求它解释新旧逻辑差异,同时人工去看最顶层调用方代码,确认是不是边界条件被漏掉。

运行时异常是排查成本最高的。线上偶发错误、数据不一致、性能劣化这些,都很难在测试环境复现。我的经验是先把异常的错误信息聚合成关键字,去diff里搜这些关键字的上下文,看AI是否改动过相关分支。如果搜不到,再从git历史里找出这个API最近几次的变更记录,从时间线上缩小嫌疑范围。

最后整理一个排查速查表,方便实际项目中直接对照:

现象可能原因优先排查方法
编译失败引用了不存在的API全局搜索符号定义,检查依赖声明
编译通过但运行抛异常参数顺序或返回类型被调换对照旧代码和生成代码的参数签名
数据丢失防御性判断被移除搜索所有可能为空的链式访问
接口返回值变化合约测试未覆盖对比新旧接口文档和实际返回结构
大规模重复代码AI低效重写或“搬运”检查diff中是否只是格式化变化
集成测试偶发失败并发逻辑被改变检查锁、事务边界、状态切换顺序

这套速查表本质上是把常见问题从“看缘分”变成“按路径排查”。我自己的习惯是,在项目中每天都留出固定时间,把当天所有失败用例按表分类填一遍,坚持下来后,绝大多数问题都能在当轮解决,不会滚到明天成为更大的债。

最后再分享一个真实感受。AI重写存量代码这件事,技术选型和工具配置只是表象,真正决定成败的是你有没有把“不可控”变成“可控”。128个PR、83万行代码,这个数字不是你给AI下一道命令就会发生的,它是一套精密的拆解、合流、验证机制推着走完的。如果你也想在自己的项目里做类似的事,我建议从一个小模块开始,比如把那几个散落各处的分页逻辑收敛成公共组件,跑通一个完整的AI重写PR流程。跑通之后你会发现,AI并没有替代工程师做决定,它只是把那些重复劳动消掉,把人的精力逼到真正该花的地方去。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询