AI啃祖传代码实战:从三千行老函数到安全重构
祖传代码这个词圈里人听见就头大。它不一定烂但一定“老”文档欠奉测试为零写它的人要么离职了要么自己也忘了当时为啥这么写。上个月我们接手了一套运行快十年的订单系统核心的下单函数三千多行没有注释没有单测里面的变量名有拼音、有缩写还有一行写着“这段不能动会炸”。要是换几年前我大概已经开始准备简历了。但这回不一样我手上有AI。我想把这段时间用AI啃祖传代码的完整思路和踩坑经验写下来给同样被老系统折磨的兄弟们一个参考。AI在祖传代码面前能干的事非常多读懂逻辑、梳理依赖、生成测试、辅助重构、迁移文档但它也有非常明显的边界和坑。这篇文章会从工作流设计、实操案例、避坑指南三个角度展开不讲虚的全是我摔出来的经验。1. 先认清对手祖传代码到底难在哪1.1 祖传代码不是“烂代码”是没人敢动的业务化石不少人一听到祖传代码就默认等于烂代码。我在一线维护老系统这么多年发现事情没那么简单。祖传代码其实分两类一类是真的烂随手写、不拆函数、变量名用拼音和a/b/c当后缀、注释欠奉纯粹的“代码垃圾场”。另一类其实不算烂而是“老”——它里面埋着的是这家公司十几年踩过的坑、拗过的需求、妥协过的产品逻辑。当年做出某个怪异的判断也许在当时是充分权衡过的结果只是后人接手时没人知道为什么。更麻烦的是大部分祖传代码同时具备三个致命特征没有测试、没有文档、没有还能讲清楚的活人。没有测试意味着你改任何一行代码都没法快速确认“出没出事”没有文档意味着所有业务逻辑只能靠通读代码反推连“这张表为什么多一个字段”这种最基本的问题都得去仓库里考古没有活人能讲清楚意味着你想找一个“懂王”问两句都找不到只能自己硬啃。我记得最夸张的一次为了让一个促销活动多支持一种商品类型我顺着代码追了整整三天。入口藏在配置中心中间经过消息队列、一堆事件订阅、两套缓存最后才落到订单计算的 SQL 里。这三天我几乎什么都没产出只是搞清楚了一件事——“这堆东西到底是怎么串起来的”。这种增量理解成本是任何一本《代码整洁之道》都替你还不了的。所以在面对祖传代码时最大的敌人不是技术债而是“没人知道它为什么长成这样”。1.2 AI为什么适合干这活把“理解”变成“对话”以前我们理解老代码靠什么靠人肉阅读、打断点、打日志、去工位上堵老同事效率极低但至少可靠。现在AI带来了一个根本性的变化它可以像人一样读上下文而且不怕你问第二遍。你把一段几百行的老函数扔给它问“这段在干嘛”它给你拆得明明白白还能主动标出“这里看起来像问题点”。相当于你身边多了一个永远有耐心的代码考古助手它不会嫌你烦也不会因为你去库克山脚下度假就失联。这个变化最本质的地方在于AI把“读代码”从以天为单位压缩到了以分钟为单位。过去理解一个三千行的函数你得自己画调用关系、理状态分支、猜业务规则光把变量之间绕来绕去的关系理清楚就累得够呛。现在AI可以快速给出一个初步框架哪个是入口、哪段做校验、哪个分支走活动优惠、哪个地方有隐藏的全局状态赋值。你只需要在这个框架上去做确认和纠偏效率完全不是一个量级。但这里有个前提必须讲清楚AI的理解能力不等于它真正“懂业务”。它能告诉你“这段代码做了什么”但“为什么这么做”它只能根据代码上下文去推测更多时候得靠你去跟业务对。所以我的定位非常明确——AI是高效的“读码外挂”不是“业务专家”。它负责把移山填海的工作量变成人力的十分之一但最终拍板业务正确性的依然是你。1.3 AI的边界它能读懂代码但读不懂业务在讲具体流程之前我想先把AI的边界说透免得有人抱太高期望。AI对代码的理解本质是基于海量代码训练出来的“模式匹配”。它见过无数种常规写法所以碰上常规逻辑它的理解非常准。但祖传代码往往恰恰是“非常规”的集合奇怪的命名、绕了八道弯的流程、为了兼容老数据而保留的分支、被历史需求反复叠加上去的补丁。AI面对这些东西有时候会基于“代码整洁”的偏好本能地把它们解读成“坏味儿”甚至在你没要求的时候顺手帮你“优化”掉。我见过一个真实的翻车案例。同事让AI帮忙重构一个优惠计算函数AI很“贴心”地发现了一个看似多余的分支——一个优惠金额大于订单金额时的特殊处理代码里写得很绕。AI觉得这是“逻辑冗余”直接给优化成行业通用的“金额不能为负”。看似没问题但业务上的真实规则是如果优惠金额大于订单金额订单金额必须归零但订单状态要进入“待人工审核”因为这个用户大概率是内部测试或者特殊渠道。AI一优化状态流转就断了运营那边立刻报警。这件事之后我定了一个规矩AI重构老代码时宁可让它“笨一点”也不要让它自作聪明。所以你在使用AI啃祖传代码之前先在心里画一条线代码结构、依赖关系、测试生成、重构搬移这些可以让AI放开手脚但涉及业务规则的判断、魔法数的含义、分支保留的理由一定要经过人工核对。AI给你的答案永远当作“候选答案”而不是“最终答案”。2. AI啃祖传代码的工作流设计2.1 四步流程解释 – 固化 – 重构 – 验证我试过很多种AI参与老代码维护的方式有直接问“帮我优化这个函数”的有让AI生成详细注释的还有让AI直接翻译成新语言的。最后沉淀下来真正稳定可靠、能复用到多个项目的一套流程就四个字解释、固化、重构、验证。顺序一步都不能乱。第一步是解释。这个阶段的目标只有一个搞懂现状。你把模块按入口、流程、副作用、边界条件拆开让AI输出一份“业务翻译件”把代码语言翻译成人话。这个阶段只动嘴、不动手任何修改都不做避免还没搞清楚状况就挖坑。第二步是固化。目标是在“搞懂现状”的基础上给现状上保险。做法是让AI生成行为级测试把当前代码的输入输出行为锁定下来。注意这里是“行为级测试”不是“正确性测试”。我不管它是否符合业务预期先保证“今天的输出是这样”以后改完必须还能保持输出一致这样才能防止重构时把行为改歪。第三步是重构。测试垫底之后才轮到AI发挥“搬砖”能力。让AI在行为测试的保护下做小步重构一次只提取一个函数、一次只动一个逻辑点绝对不允许一口气把三千行重写成六个类。第四步是验证。每个小重构做完立刻跑测试、做行为对比、查差异。没通过就回退通过了才继续下一步。这套循环看起来笨但胜在稳。很多人失败的根源就是跳过了“固化”AI一进场就重构结果改坏了只能靠肉眼和记忆来回找成本极高。2.2 工具选型不同场景下我推荐用什么这两年AI编程工具多到挑花眼真到落地的时候别光看榜单要看场景。我把市面上常见的工具分成三类大家可以根据自己的安全要求、网络条件和使用习惯选。第一类是对话式大模型比如 ChatGPT、Claude 这类通用对话产品。它们最大的优势是上下文容量大、理解能力强适合做“整体理解”和“方案讨论”。我通常把完整模块拆成几段丢进去让它输出结构化解释然后我按这个解释继续追问。这类工具的缺点是代码不能有太强的私密性要求因为它会把内容发到云端。第二类是IDE插件和AI编程助手比如 GitHub Copilot、Cursor、通义灵码、CodeGeeX 这些。它们嵌在编辑器里写代码、改代码、补测试的时候实时给建议体验最顺滑。尤其是生成测试这一块效果比对话式模型更直接——你在IDE里选中一个函数让它“生成单元测试”它马上能拉出可跑的测试框架。缺点是单个文件的上下文感知有上限大文件还是要配合对话式模型一起用。第三类是本地模型比如通过 Ollama 跑 CodeLlama、Qwen-Coder 这类开源模型。适合代码敏感、不能出内网的团队。效果比大厂在线模型弱一些但胜在安全可控。我有个朋友在金融公司所有代码都不能上外网他们就是用本地模型做辅助虽然笨一点但能跑。具体到祖传代码场景我个人的组合是主力用对话式大模型做理解和重构方案设计辅助用IDE插件补测试和重构代码。因为理解老代码对“上下文容量”要求极高对话式模型更擅长而生成测试和代码搬移IDE插件更顺手。如果只能选一个我建议优先选对话式大模型因为它能帮你把“全局看不懂”的问题解决掉这是啃老代码最大的痛点。2.3 喂AI的正确姿势上下文工程三原则AI能力再强你喂的方式不对它一样给你胡说八道。在祖传代码场景里上下文工程比提示词技巧更重要。我总结了三原则每一条都是拿血泪换的。原则一只贴代码不够要给它补背景。很多人的习惯是把一段代码甩给AI然后问“这是什么”。大模型在缺乏背景时只能瞎猜。我更推荐在提问前先写一段环境说明比如“这是一个电商系统的下单模块使用Python编写订单表结构是xxx这个函数在接口层被调用它有以下几个已知依赖”。背景越充分AI的回答越靠谱。原则二分块永远优于硬塞。即使大模型的上下文窗口已经很大把一个几千行的文件整个塞进去它也会在长文本中“迷失”尤其是老代码各种跳转和隐式依赖特别多模型很难抓住重点。我自己的做法是每次只喂500行到1000行让AI对这一段输出摘要然后带着摘要继续读下一段最后再让它综合输出全局理解。这种方式看着慢实际却是最快的。原则三要求AI输出“不确定项”而不是默认正确答案。这是很多人容易忽略的。大模型天然倾向于给你一个“看着合理”的答案哪怕它心里也没底。所以我会在提示词里明确加一句“遇到你不确定或需要业务确认的地方单独标注出来不要擅自补充解释。”这样能够最大限度过滤幻觉让AI把自己的不确定性暴露出来你再针对这些点去做确认。3. 实操案例拆解从“一个3000行的下单函数”说起3.1 解释阶段让AI先做代码考古讲完理论来看一个我实际处理过的典型场景。系统是一套老订单服务核心逻辑全部堆在一个叫place_order的函数里三千多行没有任何注释。它把用户校验、地址校验、商品库存、价格计算、优惠券、会员折扣、订单落库、库存扣减、消息推送、物流初始化、营销埋点、幂等控制全部写在一起。想改任何一个需求都得先把整座大山爬一遍。我的第一步就是“代码考古”。操作方式很简单先给AI一段环境说明再分块喂代码。我用的提示词是这样的我是一个刚接手这个模块的新人请你扮演一位资深开发工程师帮我解读下面的函数。 要求 1. 按执行顺序拆解逻辑标出每个阶段在做什么业务 2. 指出所有对外部函数、全局变量、数据库查询的依赖 3. 遇到“看起来像bug但可能是故意为之”的地方单独标注不要擅自评价好坏 4. 输出格式Markdown包含流程分阶段说明、依赖清单、风险点清单。每次喂500行左右让AI输出摘要再喂下一段。全部读完以后我让它综合输出一份全局分析。它最终给出来的结构大致是这样的从函数入口看place_order 可以分为四个阶段 - 前置校验检查用户状态、收货地址、商品上下架、库存余量 - 价格计算叠加优惠券、会员折扣、活动满减total 变量反复累减 - 落库与状态流转写订单主表、明细表扣库存插入状态流转记录触发消息队列事件 - 收尾处理异步通知物流、营销埋点、清理缓存。这份输出让我在半天内就完整理解了这个函数的核心逻辑放在以前至少要一周。当然中间AI也标出了好几个“风险点”比如某个魔法数-1看起来是用来兜底“无库存”但实际触达方式很奇怪。这些点我逐个和人工确认最终搞清楚了原因是早期库存系统迁移时留的兼容逻辑。3.2 固化阶段用行为测试把现状锁死理解完现状千万别急着重构。我见过太多人走到这一步就手痒直接让AI“帮我优化一下”结果改完行为变了上线就出事故。正确做法是先补测试、锁现状。老代码普遍没有测试但我们不是要补一套“完美测试”而是补一套“行为测试”。什么叫行为测试就是先不管这段代码逻辑是否符合业务预期只要它当前的输入输出被记录下来作为重构的回归基线。你重构完代码跑一遍测试如果行为一致说明你没把现状改坏如果行为不一致说明你的重构动到了不该动的地方需要立刻排查。具体操作我以这个下单函数为例。技术栈是 Python所以我用 pytest 配合 mock 工具生成测试骨架。操作分两步第一步让AI识别出函数的所有外部依赖数据库连接、缓存服务、消息队列、物流接口、营销系统。针对这些依赖写mock把外部调用全部挡掉确保测试只验证函数自身的逻辑。第二步准备几组典型输入场景正常下单、库存不足、优惠券过期、重复提交、黑名单用户、金额为零的特殊商品。每组输入都跑一遍记录函数的返回值和关键副作用比如是否扣库存、是否发消息。这些记录就是行为基线。当时AI生成的测试骨架大概是这样的from unittest.mock import patch import pytest pytest.mark.parametrize(user_id, sku_id, coupon_id, expected_status, [ (1001, 2001, 3001, CREATED), # 正常下单 (1002, 2001, None, NO_STOCK), # 库存不足 (1003, 2001, 9999, COUPON_EXPIRED), # 优惠券过期 ]) patch(order.service.send_message) patch(order.service.reduce_stock) patch(order.service.query_inventory) def test_place_order_behavior(mock_inventory, mock_stock, mock_msg, user_id, sku_id, coupon_id, expected_status): mock_inventory.return_value {sku_id: sku_id, stock: 10} mock_stock.return_value True mock_msg.return_value None result place_order(user_id, sku_id, coupon_id) assert result[status] expected_status这段代码当然还不能直接跑因为老函数里很多依赖是隐式的比如直接通过import xxx_db操作数据库你根本没法用参数注入。我的做法是先让AI列出所有依赖再手写 mock有时候甚至需要改函数签名把隐式依赖改成显式传入。这个过程比较痛苦但做完以后整个函数的可测性就上来了后面重构才有安全网。3.3 重构阶段小步走一次只动一个点有了行为测试垫底重构才敢放开手脚。但放开不等于让AI一口气把三千行重构成六个类。我的原则非常明确一次只提取一个函数一次只动一个逻辑点每改一步立刻跑测试。测试过了再继续测试挂了就回退不要试图一次性解决所有问题。以这个下单函数为例第一个可以动手的点是“前置校验”那一大段。那部分代码有几百行全是if ... return error的堆叠。我让AI把这一整段提取成一个独立的_validate_order(...)函数原来的place_order入口处只留一行调用。AI干这种“搬砖”活特别快而且准确率高。第二个点是“价格计算”那一大段。这段逻辑最乱因为中间反复修改同一个total变量一会儿减优惠券一会儿乘折扣一会儿加运费。我让AI分析出完整的计算顺序把它提炼成一个calculate_order_price(...)纯函数。纯函数的好处是输入输出明确特别容易测试。第三个点才是“落库与状态流转”。这一段涉及大量外部依赖也是最容易出问题的地方。我的建议是暂缓大改先只做“重命名”和“提取局部变量”把那些像s1、st、flag之类的名字改成有业务含义的变量名让AI帮你在不改变行为的前提下把可读性提上来。这里有一个重点要提醒AI在提取函数时可能会“顺手”改变局部变量的作用域或者把一个只在某个分支生效的全局状态赋值放到更外层。我在一次实操中遇到AI提取函数后把一个原本只影响某个分支的self._cache_key xxx赋值挪到了函数开头导致后续调用逻辑有细微差别。要不是行为测试立刻发现又要踩坑。所以每次让AI重构我追加要求只做结构搬移不要优化逻辑所有分支、魔法数、赋值位置都保留原样。3.4 验证阶段行为对比是最终裁判重构做完验证环节一定不能糊弄。我的验证分四层跑行为测试、跑编译构建、人工 code review、灰度上线。第一层是行为测试。这是最基础的每个小步重构完成之后立刻跑。只要有一条测试挂了马上定位是哪个行为变了。如果确认是这个行为必须保持不变就回退重构代码重新调整提取方式。如果确认是行为本来就是错的可以改但必须经过业务确认不能AI说了算。第二层是编译构建。老代码的技术栈往往比较旧有时候AI生成的重构代码调用了一个不存在的方法名或者漏掉了import编译阶段就能暴露。别嫌麻烦重构完一定在完整工程里做一次构建不能只在编辑器里看代码没变红就觉得没事。第三层是人工 code review。哪怕行为测试全过我也坚持让团队里另一个工程师过一遍diff。AI生成代码的速度太快了而且非常“像模像样”肉眼很难发现它藏起来的逻辑偏差。让第二个人从业务角度而不是语法角度审一遍能堵住很多漏网之鱼。第四层是灰度上线。这是祖传代码场景下最稳妥的做法。重构后的模块只放一部分流量进去同时对比老代码和新代码的输出结果用真实请求做回放。只要能对得上心里那块石头才算落地。我当时重构完下单函数直接上线灰度跑了一天线上请求的行为对比全部一致才敢把流量切完。4. 实战中必踩的坑与排查技巧4.1 AI一本正经地胡说八道怎么治祖传代码场景下AI最大的坑就是幻觉。大模型的本质是根据概率生成内容它不知道“事实”是什么只知道“这样接下去最顺”。而老代码又往往不是“顺”的——整个调用链有时候就是绕来绕去、补丁摞补丁模型没见过这么拧巴的写法就非常容易“编”。我遇到过最典型的例子AI在读一个老函数时发现代码里访问了一个数据库字段user_level但它没有在数据库表结构里找到这个字段的定义于是“自作聪明”在解释里写了一句“此处可能从用户缓存中读取”。实际上这个字段来自一个历史遗留的冗余表跟用户缓存一点关系都没有。这种解释看起来特别合理实际上全是编的如果照它去改代码必出事。治理办法有几个。一是给AI足够的背景资料把表结构、接口文档、调用方代码都给它降低它瞎猜的概率。二是让它在回答里区分“明确”和“推测”——我用提示词强制要求“如果某段逻辑不是代码中明确体现的请使用‘可能’‘推测’并说明依据。”三是人工交叉验证凡是AI给出的关键结论必须能在代码里找到对应证据找不到就当作无效信息。4.2 文件太大塞不进上下文怎么办老代码最烦人的特点就是“长”动辄几千行一个文件。即使现在的模型上下文窗口已经很大直接硬塞也会导致两个问题一是模型在长文本尾部丢失前面信息二是回答会变慢还不一定准确。我常用的解决方案有三个。第一个是分块加摘要法把大文件按函数或逻辑段落切块先让AI对每块输出摘要再把摘要汇总最后让AI基于摘要做整体分析。这样虽然多跑几轮但准确率远高于一次性硬塞。第二个是自建“代码地图”把整个模块的文件结构、函数清单、关键调用关系先整理成一棵树做成一个简短的结构文档再带着这个文档去问AI。AI看到地图以后对目标的定位准确很多。第三个是外挂知识库如果团队要长期维护这套老代码与其每次让AI临时读一遍不如把所有解释、依赖关系、调用链沉淀到一个团队内部知识库以后用检索的方式把相关片段喂给AI成本低效果好。4.3 小心AI帮你“修bug”业务正确不等于代码正确这一条是祖传代码场景下最严重的事故源值得单独拿出来说。老代码里经常有“看似bug但其实是业务补偿”的怪逻辑。比如某个促销活动要求“优惠券金额大于订单金额时订单金额直接归零但不能为负”代码里就可能写成一个很不直观的魔法数和分支。正常人看了第一反应都是“这写错了吧”AI也不例外它基于“代码整洁”的偏好很容易把这些分支识别成坏味儿然后替你改成“更合理”的写法改完业务直接崩。所以我在所有重构类提示词里都会反复强调一句话“保留所有分支和魔法数即使它们看起来不合理。没有经过人工确认之前不要优化任何业务逻辑。”这句话能拦住大部分AI自作聪明的冲动。另外AI生成完重构代码以后我会专门加一个审查环节对比重构前后的分支结构逐个确认每个if和每个魔法数都是严格保留的。这个环节一开始觉得很麻烦后来慢慢习惯了反而成为我审查AI代码的一个“探针”——AI有没有偷偷改业务逻辑一比分支数量、一比魔法数数量立刻现形。4.4 常见问题速查表最后整理一张排查表都是我实际踩过或帮同事排查过的典型问题可以直接当参考。现象可能原因解决办法AI给出的解释和代码对不上上下文太长导致信息丢失或模型在瞎猜缩小代码块分块阅读要求AI标注不确定项AI重构后编译报错引用了未导入的类或函数漏写import重构后立刻跑完整构建别只看编辑器提示重构后行为测试挂掉职能提取改变了变量作用域或赋值位置定位行为差异的那一行和原逻辑对比回退后重试AI把业务分支“优化”掉了模型基于代码整洁偏好自作主张在提示词里强调“保留所有分支和魔法数”逐分支对比文件太大塞不进上下文模型窗口不够或长文本丢失信息用分块摘要法或建代码地图逐步喂给AI模型不理解老框架特性老框架在训练数据里出现太少先把框架资料或同类型示例喂给AI再让它分析项目代码AI生成的重构代码“过于完美”模型照搬了通用最佳实践忽略了历史约束对照原代码逐段审查重点关注全局状态和异常分支这张表后面我还要再补一句经验不管AI给出的答案多顺滑永远不要在项目里开“无验证直通”。老代码重构验证环节一旦省略AI的效率优势立刻变成风险敞口。宁可重构慢一点也要保证每一步都有测试兜底、有行为对比。我自己的体会是AI并不会让祖传代码消失但它确实改变了“啃老代码”这件事的成本结构。以前团队最怕接老项目因为时间全耗在“看懂”上没人敢动现在有了AI理解这个环节被降维成了“提问和确认”我们反而能把更多精力放在验证和设计上。最后再分享一个小技巧如果你也要用AI啃祖传代码别一上来就问“怎么优化”先问它“帮我解释一下这段代码做了什么、依赖什么、可疑点在哪”。先当好考古队员再当拆迁队长。这句话我写在这关键时刻能救你一命。