遗留代码让我崩溃了三次,第四次我把 AI 拉进来了

那个 Python 文件叫 processor.py,830 行,没有一行注释。

接手的时候,上一个维护它的同事已经离职半年了。Git blame 显示这个文件最早的提交是 2019 年,作者是个更早离职的人。我打开文件,看到第一个函数名叫 handle_data,接受一个叫 obj 的参数,里面有七层嵌套的 if-else。

我关上了文件。


遗留代码这个词,在程序员圈子里有点像”前任”——人人都有,人人都避着谈,但迟早要面对。

我职业生涯里遇到的大多数遗留代码,问题不在于写得烂,而在于写它的人知道某些上下文,而这些上下文没有被记录下来。handle_data 里那七层 if-else,如果你知道它处理的是三种不同来源的 webhook 数据,每种来源有两种版本,加上一个历史兼容层……其实还挺合理的。但你不知道。

所以问题变成:怎么在没有文档、没有原作者的情况下,搞清楚这段代码在干什么。


第一次崩溃:靠猜

最早我的做法是硬读。从头到尾,一行一行,遇到看不懂的函数就跳过去看那个函数,跳着跳着就迷失了。

这种方法对几百行的代码还能用,到了五六千行的模块就彻底不行。你大脑同时能 hold 住的调用栈有限,稍微深一点就开始用”猜”来填补空白。猜的代价是:你改了一处,另一处以你意想不到的方式坏掉了。

我在一个支付模块里就这么干过。大致读懂了主流程,以为那个 retry_count 变量只在失败重试里用。改了一个边界条件,上线后发现退款接口开始产生重复调用。retry_count 其实在另一个地方也被引用了,作为”已尝试次数”的日志依据。

修了三天。

第二次崩溃:靠测试

后来学聪明了,读代码之前先看测试用例,用测试来理解行为。这个方法好多了,前提是有测试。

遗留代码往往没有测试。即便有,很多情况下测试写于代码之后,测试本身也是按照当时理解写的,可能同样包含错误的假设。

我见过一个测试用例,它 mock 了一个外部 API 调用,mock 的返回值是作者当时以为正确的格式,但实际 API 已经在某个版本升级中改过结构了。测试一直绿,线上一直在以一种隐蔽的方式出错。

第三次崩溃:靠文档

有些项目有 Confluence,有 Wiki,有设计文档。但设计文档往往写于项目启动时,而代码在五年里已经悄悄走了另一条路。文档和代码之间有个漂移——越老的代码,漂移越大。

按文档改代码,改完发现文档描述的那个状态机早就被重构掉了,现在的逻辑已经不是那样了。


第四次,我把 AI 拉进来了

大概是去年年初,我接手一个物流系统里的路由模块,大约 4000 行,三个文件,Python 写的,最近一次大改是 2022 年。

我换了个思路:先让 AI 读,让它告诉我这段代码在干什么。

方法很简单。把文件整个贴进去,让它从整体到局部地解释:先说这个模块的主要功能是什么,然后说每个关键函数的职责,最后列出哪些地方它觉得逻辑不寻常或者有潜在问题。

AI 给我的第一段总结是这样的:

这个模块实现了一个多条件路由引擎,根据货物类型、目的地区域、优先级和当前仓库库容,选择最优的物流路径。核心逻辑在 route_selector.pyselect_route 函数里,整个选择过程分三个阶段:候选路径过滤、评分排序、容量校验。

我看了一遍,觉得基本对。这比我自己读两小时代码得到的理解还准确。

然后我让它标注哪些地方”不寻常”。它指出了两处:一是有个 _legacy_weight_factor 变量,值是 0.85,被写死在代码里,没有说明来源;二是评分函数里有个 if carrier_code.startswith('JD_') 的特殊分支,逻辑和其他分支不一样,但没有注释。

这两处它都不确定含义。我拿去问了当时还在职的一个老员工,得知:0.85 是当年为了应对某个运营商降速问题临时加的折扣系数,早就应该去掉;JD_ 是一个已经停止合作的快递商,那个分支理论上永远不会触发了。

这是两个隐形炸弹,一个处理掉可以提升评分准确性,一个直接可以删掉。


AI 在这里到底有用在哪

用了将近一年,我觉得 AI 帮助理解遗留代码有三个具体场景:

一是大范围理解结构。 当代码量超过你大脑能同时 hold 住的范围,AI 能帮你先建立一张地图。你知道”这个函数管入口,那个类负责状态机,那个模块处理异常”,之后读具体代码时就不容易迷失。

二是找异常。 让 AI 专门找”看起来可疑的代码”——写死的数值、奇怪的特判、冗余的变量、不一致的命名。很多时候这些可疑点背后要么是一个已经过时的假设,要么是一个隐藏的业务规则。人读代码的时候容易对这些东西”视而不见”,AI 没有这个包袱。

三是生成理解性注释。 让 AI 对每个函数生成一段解释,直接作为临时注释加进去。这不是为了让代码变得漂亮,是为了让下一个读这段代码的人——包括三个月后的你——能快速上下文恢复。


踩的坑

当然 AI 也不是万能的。

最大的问题是它会在不确定的地方”编造”解释。有一次我让它解释一个复杂的数据库查询优化逻辑,它给了一段很有条理的解释,读起来非常合理,但有个关键细节是错的——它把一个分区裁剪优化说成了索引下推。这两种技术逻辑不同,如果我直接照这个理解去改代码,很可能改出问题。

解决方法是:AI 的解释要当成假设,不是结论。尤其是涉及性能优化、数据库行为、并发控制这些细节,一定要自己验证。

另一个问题是上下文长度。4000 行代码可以放进去,但 10000 行的单文件,或者多文件相互依赖的大型模块,就没法整个喂进去。这时候我的做法是分段:先让 AI 读接口定义和主入口,建立结构理解;然后按功能块分批喂,每次说清楚”这是上一步你说的 X 功能的具体实现”。


现在的工作流大概是这样:

接手陌生代码的第一步,不是运行,是让 AI 读并给我一份结构摘要。然后我自己读摘要,判断哪些理解是准确的,哪些需要存疑。然后针对存疑的地方,自己去读原始代码。

这比直接硬读快了至少一半。更重要的是,理解质量更高——我不再是靠猜了解一个模块,而是在一个基本正确的框架下去补充细节。

processor.py 那个文件,最后我花了两天理解它,写了完整注释,然后把七层 if-else 拆成了三个函数,每个函数处理一种 webhook 来源。现在那个文件 420 行,有注释,有测试。

下一个接手的人,应该不用崩溃了。


遗留代码让我崩溃了三次,第四次我把 AI 拉进来了
https://www.ohtudou.top/2026/05/12/2026-05-12-dealing-with-legacy-code-ai-era/
作者
Tudo
发布于
2026年5月12日
许可协议