老项目最难受的不是看不懂,是不敢动:一个函数牵出十几个调用方,改完自己都不敢提交。这篇不给「AI 一键重构」的幻想,而是把梳理、切片、验证、回滚拆成可执行步骤,告诉你每一步模型能帮到哪、哪一步必须人来拍板,以及什么情况下这套流程不适用。
老项目最难受的不是看不懂,是不敢动。一个函数牵出十几个调用方,改一行心里打鼓,改完自己都不敢提交。这篇给维护老系统的工程师一套能落地的 AI 辅助重构流程:怎么分块喂老代码、怎么让模型讲清依赖、怎么控制改错风险,每一步都配验收判据。
先说清楚这篇不承诺什么。没有「一键重构」这回事,模型在这条链路上只做三件事:解释代码、找依赖、生成候选实现。拆解范围、判断行为是否等价、决定提交还是回滚,都是人来拍板。下面按梳理、切片、验证、回滚的顺序拆,每一步都告诉你模型能帮到哪、哪一步必须自己上。
先搞清楚:哪些老代码适合交给 AI,哪些别碰
不是所有老代码都值得进入这套流程。判断的第一道门槛很朴素:你能否为这段代码写出一条可重复执行的验证命令——能自动跑、能对比前后输出。写得出来,说明它的输入输出边界是清楚的,AI 参与重构才有意义;写不出来,先别重构。
| 代码特征 | 能不能交给 AI | 原因 |
|---|---|---|
| 输入输出明确、可单独跑起来的函数或模块 | 可以主导 | 模型看得全上下文,验证也简单 |
| 强依赖运行时环境、靠全局状态和时序耦合 | 只读不写 | 模型看不全上下文,容易编出不存在的行为 |
| 涉及资金、权限、合规、并发安全 | 只用来读懂和写注释 | 改动的正确性靠推理保证不了,必须人工评审 |
| 没有版本控制、没有测试、没有可回放样本 | 先别碰重构 | 出问题不知道退到哪,等于蒙眼走钢丝 |
第一步不是改代码,是把地图画出来
很多人的第一反应是让模型直接改写。正确做法是这一轮只做只读的事:让模型解释模块职责、列出对外暴露的接口、标注可疑的隐式依赖——全局变量、单例、静态缓存、写在类初始化里的副作用。产出物是一份模块地图加一份待确认清单,不是任何一行代码改动。
- 让模型讲模块职责贴入模块代码,要求它用一段话说清这个模块对外提供什么能力、对内依赖什么。讲不圆的地方,通常就是你也没搞清楚的地方。
- 列出对外接口要求模型输出公开函数和字段的清单,每条注明被谁调用。它会漏,但比人工翻快很多。
- 标注隐式依赖专门问全局变量、单例、静态缓存、环境变量、配置文件这几类。老代码翻车大多栽在这。
- 人工核对猜的部分模型给的每条依赖都要求指出代码位置。指不出位置的当幻觉处理,重点标记,后面验证时优先覆盖。
这份地图的价值不在于多准确,而在于把「你不知道自己不知道」的部分暴露出来。模型猜错的地方,往往正是团队里只有一两个人清楚的历史包袱,趁这个机会把它们问清楚写下来。
怎么把几万行老代码切给模型看
按文件切是最省事也最容易出错的做法。老代码最常见形态就是一个文件里塞了三个模块、互相引用还有循环。切片要按调用边界切,不按文件切。一个文件里职责混杂的,先按职责拆开再分别喂。
- 一次只处理一个可独立验证的单元,别贪多
- 上下文里固定放三样:目标模块、它的直接调用方、相关数据结构的定义
- 调用链太长时先让模型做摘要压缩,把压缩结果而不是原始代码放进上下文
- 注意上下文预算,硬塞的结果是关键信息被截断,模型拿半截代码给你编一个完整答案
- 切片完成后回看一下:每个单元都能单独写出一条验证命令吗,不能就切错了
关于上下文预算和压缩策略的具体做法,站内已经有过专门讨论,这里不重复,只强调一句:宁可多切几刀,也不要把一个说不清的巨型模块整段塞进去。
让模型干最擅长的事:找依赖和副作用
找依赖和副作用是模型在这条链路上性价比最高的环节,因为它本质是模式匹配加梳理,不需要模型理解业务意图。但提问方式要固定,否则每次得到的答案格式都不一样,没法横向比对。
- 读状态
- 这段代码读了哪些外部状态:全局变量、配置、数据库、缓存、环境变量
- 写状态
- 这段代码写了哪些外部状态:数据库写操作、文件系统、缓存失效、消息投递
- 异常行为
- 出错时会发生什么:异常吞掉、日志级别、是否回滚、是否有重试
三类问题分开问,得到的清单分开记。特别要盯四类高危位置:数据库写操作、文件系统、网络调用、时间与时区相关逻辑。前三类涉及外部世界,改动后的差异很难靠读代码发现;第四类是老系统里最隐蔽的坑,跨时区、夏令时、本地时间与 UTC 混用的逻辑,模型经常答得头头是道但细节全错。
| 高危位置 | 典型症状 | 验证手段 |
|---|---|---|
| 数据库写操作 | 查询条数变了但结果没变 | 回放样本时统计 SQL 条数 |
| 文件系统 | 路径拼接、临时文件残留 | 改动前后比对目录快照 |
| 网络调用 | 超时、重试、幂等性 | 录制响应做桩,禁止打真实下游 |
| 时间时区 | 跨时区结果漂移、夏令时错位 | 固定时钟做注入,覆盖边界日期 |
小步改:一轮只做一件事的循环
一轮只干一类改动:这一轮只改名,下一轮只抽函数,再下一轮才换实现。混在一起改,出了问题说不清是哪一步引入的。至于测试怎么写、怎么补,属于另一篇文章的范畴,这里只管节奏:先固化验证手段,再改,再对比,再提交。
- 固化输入输出用真实流量录制或手工构造样本,先跑老代码,把输入和输出都存下来,当作这一轮的对照基线。
- 明确本轮改动类型本轮只做一类改动,写在提交信息开头。改动类型混了就拆成两轮。
- 让模型产出候选实现把目标模块、调用方、数据结构定义一起给它,要求它只改指定部分,别顺手优化别处。
- 对比并记录对比不只比结果,还比调用次数、查询条数、日志行数。行为等价但副作用翻倍,同样是回归。
- 提交或回滚对比通过就提交,把验证方式和未覆盖范围写进提交信息;不通过就走下一节的回滚判据。
这个循环跑起来之后,单轮改动的体量应该小到你敢在十分钟内手工复核完。如果某一轮你发现自己根本看不懂模型改了什么,说明上一轮切片切大了,退回去重新切。
改完怎么验收:一张对照清单
验收不是「跑一遍没问题」,而是四项比对里至少三项留下可重复的证据。证据的意思是:换个人照着记录能复现出同样的对比结果,而不是「我本地试过了」。
| 比对项 | 做法 | 证据形式 |
|---|---|---|
| 输入输出比对 | 固定样本跑新旧代码,逐条比对输出 | 差异报告,零差异或已解释的差异 |
| 边界值比对 | 空值、极值、超长输入、非法输入各跑一遍 | 边界用例清单及结果 |
| 异常路径比对 | 构造下游失败、超时、权限不足等场景 | 异常场景清单及结果 |
| 性能量级比对 | 关注数量级而非精确值,避免噪声误判 | 量级对照结论 |
样本来源上,用老系统真实流量录制出来的回放样本,比手工造用例可靠得多,因为真实流量会带上你没想过的畸形输入。样本量不用大,但必须覆盖主流程和上面那张表里的四类高危位置。任何一项无法解释的差异,都当作回归处理。
验收记录要落到提交信息或变更单里,写清楚本次改动验证了什么、用什么方式验证的、哪些范围没覆盖。这不是形式主义,下一个人接手时能省掉重来一遍的成本。
什么时候该回滚,什么时候该放弃重构
回滚的判据要提前定好,不能等到出问题再临时商量,那时候情绪会盖过判断。三条触发线,任意一条满足就回滚:出现无法解释的行为差异;验证耗时已经超过改动本身的价值;同一处反复改了三轮还没稳定下来。
- 出现无法解释的行为差异——不是「可能没关系」,是解释不了,就回滚
- 验证耗时超过改动本身——说明验证手段没建好,先补工具再动代码
- 同一处反复改三轮还不稳——通常意味着切片或地图阶段出了问题,退回去重做
- 重构范围在评估后持续扩大——前期地图没画准,先停下来补地图,不要硬推
- 回滚后别急着再试同一刀,先问清上一轮到底漏了什么信息
有一些项目在这套流程下根本不该启动:没有测试、没有版本控制、没有可回放样本。这三样是前置条件,缺哪样就先补哪样,补完再谈 AI 辅助重构。硬上不是勇敢,是给自己挖坑。
模型和工具怎么选,以及成本怎么估
读懂类任务,比如解释模块职责、列接口清单,用便宜档模型就够,输出内容以梳理为主,不需要太强的推理。复杂推理和跨文件改动,比如换实现、重构调用链,才需要高配档。按任务切模型比笼统比较模型更实际,具体哪档模型什么价,到站内价格页核对当时的数字,别拿别人截图里的价格做预算。
成本估算可以按输入构成来算:一个可改动单元的分析输入,大致包含模块代码、直接调用方、相关数据结构定义三部分,再乘上你可能要迭代的轮数。这个构成基本稳定,单价变动直接去价格页看当时的数字就行。想省 token,优先在切片和摘要压缩上做功夫,而不是砍掉调用方和数据结构定义——省下的那点钱,换来的是模型看不见全局然后瞎编。
- 先扫一遍老代码里的硬编码凭据、内网地址、密钥,喂模型之前处理掉
- 日志、报错信息、样本数据导出前做脱敏,脱敏清单按站内安全类文章执行
- 区分只读任务和写入任务:只读任务可以放宽,会改代码的任务控制在受控环境里
- 把每轮改动的输入构成和轮数记下来,几次之后你就有自己的成本基线了
常见问题
整个老项目直接丢给 AI 让它重构,行不行?
基本不行。上下文塞不下、模型对运行时行为无从判断,产出多半是不能编译或行为悄悄变了的代码。可行的做法是按调用边界切片,一次只处理一个能独立验证的单元,改完验证再进下一片。
没有单元测试的老系统,能不能先靠 AI 补测试再重构?
可以,但顺序是先做特征测试(记录现有行为)再谈重构,而不是先写理想测试。特征测试不判断对错,只锁住当前行为,这样重构后行为变了能立刻发现。测试怎么写属于另一篇文章的范围。
模型说这段代码有某个依赖,我怎么判断是真是假?
要求它给出文件与行号,然后自己去点开核对,指不出位置的直接当幻觉。另外看这条依赖是否符合你对系统的既有认知,冲突时以代码为准。反复出现编造依赖,说明喂进去的上下文不完整。
重构到一半发现范围比想象中大得多,怎么办?
停下来,不要扩大战线。先把已改完且验证通过的部分单独提交并锁定,然后重新评估剩余部分的地图,必要时把任务拆成几个独立变更分批推进。范围失控通常是前期依赖分析没做完。
这套流程在什么情况下不适用?
没有版本控制、没有可回放样本、没有能重复执行的验证手段时都不适用,先补这三样。另外涉及强时序、强并发或依赖特定硬件行为的代码,模型判断力有限,只能用于读懂,改动仍需人工主导。