外观
从一团乱麻到分层重构:AI 读懂祖传代码实录
问题背景:为什么 AI 改祖传代码越改越慌
遗留系统重构的难点,通常不是“不会写新代码”,而是没人敢确定旧代码为什么这样写。一个 OrderService 可能同时承担参数校验、库存扣减、优惠券计算、事务提交和第三方通知;一个 utils.js 可能被十几个模块隐式依赖;某些分支只有线上历史数据才会触发。直接把单个文件丢给 AI 编码助手,常见结果是:命名更优雅了,行为却变了;抽象更漂亮了,事务边界却丢了;测试看起来通过,灰度后才发现兼容字段被删除。
根本原因在于上下文缺失。模型看到的是“代码文本”,但重构需要理解的是“系统行为”:入口在哪里、状态如何流转、哪些异常被吞掉、数据库字段有哪些历史含义、外部接口依赖什么顺序。换句话说,AI 重构不是提示词一句“帮我优化代码”就能解决,而是一项上下文工程。
本文分享一套在真实遗留系统中更稳的做法:先为祖传代码建立分层上下文,再让 AI 按“行为保持、边界收敛、模块替换”的顺序生成重构方案。配套示例使用 Python 聚合代码上下文,并提供可复用提示词模板。
方案对比:整库投喂、单文件问答与分层上下文
团队常见使用方式大致有三种。
第一种是整库投喂。优点是信息全,缺点也很明显: token 成本高、关键信号被噪声淹没,模型容易引用无关模块,还可能把过期脚本当成核心链路。
第二种是单文件问答。它适合解释函数、补测试、改局部风格,但面对跨控制器、服务、DAO、消息队列的调用链时,会凭空假设接口职责,生成“局部正确、全局错误”的方案。
第三种是分层上下文注入:先给系统概览,再给目标链路,再给数据契约和约束,最后才贴具体代码。它把 AI 的注意力限制在与重构目标强相关的证据上,同时保留全局边界,适合中大型遗留系统的渐进式改造。
上下文分层:让 AI 先理解行为,再设计结构
建议把上下文拆成四层。第一层是系统概览,包括模块职责、关键入口、部署形态、共享数据库、同步或异步依赖。第二层是调用链证据,至少覆盖一个典型请求和一个异常请求,例如“创建订单 → 库存预占 → 支付 → 发货通知”。第三层是数据契约,包括表字段、状态枚举、对外 API 入参出参、消息体字段和默认值。第四层是工程约束,例如不能改库表、不能调整接口字段顺序、事务必须在 service 层、灰度期双写开关必须保留。
这四层的价值不同:系统概览防止 AI 只见树木;调用链提供行为依据;数据契约约束输入输出;工程约束把“理论最优设计”拉回可落地方案。
实操步骤:用 Python 组装可复用上下文包
下面脚本演示如何从一个简化的遗留项目中抽取文件、调用链和约束,生成给 AI 编码助手使用的 Markdown 上下文包。示例不依赖第三方库,可直接运行。
python
# context_pack.py
# python context_pack.py > refactor_context.md
from pathlib import Path
import re
ROOT = Path("./legacy-order-app")
# 只纳入与订单重构强相关的文件,避免整库噪声
TARGET_FILES = [
"src/controller/order_controller.py",
"src/service/order_service.py",
"src/dao/order_dao.py",
"src/schema/order.sql",
]
# 人工确认过的核心调用链,比让模型猜更可靠
CALL_FLOW = """
1. POST /orders 进入 order_controller.create_order
2. controller 调用 order_service.create_order
3. service 校验优惠券、扣减库存、创建订单、发送 MQ
4. dao 执行 orders 表插入,状态固定为 CREATED
5. MQ 消费者异步通知仓储系统,失败只记录日志,不回滚订单
""".strip()
CONSTRAINTS = """
- 不得修改 /orders 的请求和响应字段,移动端仍依赖 couponInfo 驼峰字段。
- orders.status 已有 CREATED、PAID、CLOSED 三个历史值,禁止重排枚举数字。
- 第一阶段禁止做库表结构变更,只能调整代码组织。
- 库存扣减与订单插入必须处于同一个数据库事务内。
- MQ 发送失败不能影响主流程,但要保留失败日志和补偿任务入口。
""".strip()
def read_file(relative_path: str) -> str:
path = ROOT / relative_path
if not path.exists():
return f"# 缺失文件:{relative_path}\n"
return path.read_text(encoding="utf-8", errors="ignore")
def detect_functions(code: str) -> list[str]:
# 粗略抽取 Python 函数,帮助模型快速定位职责
return re.findall(r"^def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", code, re.M)
def build_pack() -> str:
parts = ["# 订单服务重构上下文包\n", "## 1. 系统概览",
"这是一个同步下单、异步通知仓储的遗留订单服务。控制器、业务逻辑、DAO 目前存在职责混杂。\n",
"## 2. 已确认调用链", CALL_FLOW, "", "## 3. 工程约束", CONSTRAINTS, ""]
for file_path in TARGET_FILES:
code = read_file(file_path)
funcs = detect_functions(code)
parts.append(f"## 4. 代码证据:{file_path}")
if funcs:
parts.append("主要函数:" + "、".join(funcs))
parts.append("```python\n" + code + "\n```\n")
parts.append("## 5. 输出要求")
parts.append("请先列出现状行为,再给分层重构步骤;每一步说明风险、回滚方式和测试点。")
return "\n".join(parts)
if __name__ == "__main__":
print(build_pack())脚本里最关键的不是解析能力,而是“白名单 + 人工确认调用链 + 显式约束”。静态分析可以辅助发现引用,但涉及历史行为时,必须把线上事实、提交记录或接口文档中的结论写入上下文,不能让模型根据命名自行脑补。
提示词模板:要求 AI 输出分层重构方案
上下文包准备好后,不建议直接要求“优化这个服务”。可以使用下面的模板,把任务限定为方案设计而不是立刻改代码。
text
你是一名负责遗留系统渐进式重构的架构师。
请基于提供的上下文包完成任务,未在上下文中出现的事实必须标记为“待确认”,不要臆测。
目标:将订单创建流程从职责混杂的 service 重构为清晰分层,但第一阶段不得改变外部 API、数据库结构和状态枚举。
请按以下格式输出:
1. 现状行为清单:区分已确认行为、隐式依赖、待确认问题。
2. 风险热力图:按数据一致性、接口兼容、事务、消息、性能分类。
3. 分层重构方案:按 controller、application service、domain service、repository、integration 分层说明。
4. 分阶段步骤:每阶段包含改动文件、验证方式、回滚开关、不能做的事。
5. 测试清单:至少包含黄金样例、边界值、历史状态兼容、MQ 失败场景。
6. 仍需人工确认的问题:列出需要查日志、库表或问业务方的事项。
约束:
- 不允许一次性大重写。
- 不允许删除旧字段或旧枚举。
- 优先使用适配器保留旧接口。
- 每个阶段都必须可独立上线和回滚。这个模板的重点是让 AI 先产出“行为清单”和“待确认问题”。如果它连旧行为都没有复述清楚,就不应进入代码生成阶段。实际使用时,可以先让模型输出方案,再针对某一阶段生成代码;代码生成时继续携带数据契约和测试清单,避免上下文在多轮对话中漂移。
推荐落地节奏:小步替换而不是大爆炸重写
第一阶段只做无行为变化的结构调整,例如提取参数校验器、收敛事务边界、把第三方 MQ 调用包到 gateway。第二阶段引入应用服务和领域服务,让控制器只负责协议适配,repository 负责持久化,旧 service 保留为门面或适配器。第三阶段再通过流量灰度、双跑比对或契约测试替换关键实现。
每次让 AI 生成代码时,都应同时要求它给出“行为保持证明”:哪些输入输出不变、哪些 SQL 顺序不变、哪些异常仍被吞掉或转换。对于金额、库存、状态机等高风险逻辑,优先补 Characterization Tests(特征测试),即先固定现有行为,再重构内部结构。
避坑总结:AI 遗留系统重构的高频事故
第一,不要把“代码能搜到”当成“行为已确认”。 常见错误是 AI 根据 sendMessage() 的方法名推断消息失败会回滚订单,但线上真实逻辑可能是 catch 异常后只打日志。修正方法是在上下文包中提供调用链结论、异常处理代码片段和日志证据,并要求模型把未知项标为“待确认”。
第二,不要让 AI 一次性改完整条链路。 典型现象是一个提交里同时改接口 DTO、service 分层、DAO SQL 和 MQ 消息体,测试全绿却无法定位灰度问题。修正方法是把方案拆成可独立上线的阶段,每阶段只移动一种职责,并保留旧入口作为适配器。
第三,不要忽略历史数据和枚举兼容。 很多助手会顺手把 status=1/2/3 改成字符串枚举,或删除看似没用的 couponInfo 字段,结果旧客户端或 BI 报表立即异常。修正方法是在工程约束中明确“禁止改字段名、字段顺序、枚举值”,并提供表结构与接口契约。
第四,不要让多轮对话丢失早期约束。 常见现象是第一轮还强调不能改库表,第三轮生成迁移代码时模型已经“忘记”约束。修正方法是每轮都携带精简版约束清单,或把约束固化为系统提示、评审清单和自动化测试。
第五,不要把 AI 生成的测试当成安全网。 AI 可能围绕新实现写测试,而不是固定旧行为,出现“测试通过但系统变味”。修正方法是重构前先录制真实请求响应、补充特征测试,并在重构后做新旧实现输出比对。
遗留系统重构中,AI 编码助手更像一个需要充分取证的协作者,而不是读一遍代码就懂历史的维护者。把系统概览、调用链、数据契约和工程约束分层注入,再要求它按阶段输出风险、测试和回滚方案,才能把“祖传代码”从不可知风险变成可拆解、可验证、可渐进替换的工程任务。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。