第23讲|重构遗留代码:AI 如何帮你啃最硬的骨头

3 阅读1分钟

金句:遗留代码是一个没有测试的代码库。AI 是第一个不会因为它的复杂性而感到恐惧的"工程师"。


一、遗留代码的噩梦

每个工程师都有这样的经历:

  • 接手一个 5 年前的老项目,没有文档,注释残缺
  • 想改一个 Bug,却发现牵一发动全身
  • 明知道代码需要重构,但不敢动,因为"不知道会影响什么"

遗留代码的核心问题是不可见性:你不知道它的边界,不知道它的副作用,不知道改动它的后果。

AI 正在改变这个现状。


二、AI 重构的四步法

第一步:读懂代码(AI 翻译遗留代码)

# 一段典型的遗留代码
def proc_data(d, t, f=None, x=True):
    r = []
    for i in d:
        if i['s'] != 'D' and (not f or i['c'] in f):
            tmp = {'id': i['id'], 'val': i['v'] * t}
            if x and 'tax' in i:
                tmp['val'] = tmp['val'] * (1 + i['tax'])
            if i.get('disc'):
                tmp['val'] = tmp['val'] * (1 - i['disc']/100)
            r.append(tmp)
    return r

提示词

请分析这段代码:
1. 用自然语言解释这个函数的业务逻辑
2. 解释所有参数和变量的含义
3. 找出潜在的 Bug 或设计问题
4. 给出重命名建议(所有参数和变量)

AI 解析输出

这个函数的业务逻辑是:处理商品价格计算

参数含义:
- d: 商品数据列表(data)
- t: 汇率或价格倍率(tax_rate 或 multiplier)
- f: 允许的商品类别过滤器(filter_categories)
- x: 是否计算含税价格(include_tax)

潜在问题:
1. 参数 t 的含义不明确,可能是汇率或税率
2. 未处理 i['v'] 不存在的情况(KeyError 风险)
3. disc 字段的折扣计算顺序可能有问题(先乘税再乘折扣 vs 先折扣再乘税)

重命名建议:
- d → products
- t → price_multiplier
- f → category_filter
- x → include_tax
- r → result
- i → product
- tmp → processed_item

第二步:生成测试(锁定当前行为)

重构的黄金原则:在重构之前,先为现有代码生成测试,锁定当前行为。

基于以上分析,请为这个函数生成完整的测试用例。
要求覆盖所有参数组合和边界情况。
我的目标是:先确保重构后函数行为不变,再优化代码。

生成的测试

import pytest

# 测试基础功能
def test_basic_price_calculation():
    products = [{'id': 1, 'v': 100, 's': 'A', 'c': 'electronics'}]
    result = proc_data(products, 2)
    assert result[0]['val'] == 200

# 测试过滤删除状态的商品
def test_filters_deleted_products():
    products = [
        {'id': 1, 'v': 100, 's': 'D', 'c': 'electronics'},  # 已删除
        {'id': 2, 'v': 200, 's': 'A', 'c': 'electronics'},  # 正常
    ]
    result = proc_data(products, 1)
    assert len(result) == 1
    assert result[0]['id'] == 2

# 测试类别过滤
def test_category_filter():
    products = [
        {'id': 1, 'v': 100, 's': 'A', 'c': 'electronics'},
        {'id': 2, 'v': 100, 's': 'A', 'c': 'clothing'},
    ]
    result = proc_data(products, 1, f=['electronics'])
    assert len(result) == 1

# 测试含税计算
def test_tax_calculation():
    products = [{'id': 1, 'v': 100, 's': 'A', 'c': 'x', 'tax': 0.1}]
    result = proc_data(products, 2, x=True)
    assert result[0]['val'] == 220  # 100 * 2 * 1.1

# 测试折扣计算
def test_discount_calculation():
    products = [{'id': 1, 'v': 100, 's': 'A', 'c': 'x', 'disc': 20}]
    result = proc_data(products, 1, x=False)
    assert result[0]['val'] == 80  # 100 * (1 - 20/100)

第三步:逐步重构

原则:每次只做一种类型的重构,运行测试,通过后再进行下一种。

重构顺序

1. 重命名(变量/参数/函数名)— 风险最低
2. 提取函数(长函数拆分)
3. 提取类(相关逻辑封装)
4. 改变数据结构(如果有必要)

提示词

请对以下函数进行第一步重构:只做重命名,不改变逻辑。
基于你之前的分析,将所有缩写和无意义的名称替换为语义化名称。
保证重构前后行为完全一致。

[粘贴原函数]

第一步重构结果

def calculate_product_prices(
    products: list,
    price_multiplier: float,
    category_filter: list = None,
    include_tax: bool = True
) -> list:
    result = []
    for product in products:
        if product['s'] != 'D' and (
            not category_filter or product['c'] in category_filter
        ):
            processed_item = {
                'id': product['id'],
                'val': product['v'] * price_multiplier
            }
            if include_tax and 'tax' in product:
                processed_item['val'] *= (1 + product['tax'])
            if product.get('disc'):
                processed_item['val'] *= (1 - product['disc'] / 100)
            result.append(processed_item)
    return result

第四步:架构级重构

当单个函数重构完成后,进行更大范围的架构优化:

基于以上代码,请进行架构级重构:
1. 应用单一职责原则,将价格计算逻辑拆分为独立的类
2. 使用策略模式处理不同的价格计算规则
3. 添加完整的类型注解
4. 优化错误处理

目标:使代码满足开放-封闭原则,
新增价格规则时不需要修改现有代码。

三、AI 重构大型代码库的策略

策略一:从"热点"文件开始

# 找出改动最频繁的文件(往往是问题最多的)
git log --name-only --pretty=format: | sort | uniq -c | sort -rn | head 20

这些文件是重构的高优先级目标。

策略二:绞杀者模式(Strangler Fig Pattern)

传统重构(危险):
修改旧代码 → 部署 → 希望没破坏什么

绞杀者模式(安全):
旧代码继续运行
新代码并行实现
流量逐步切换
旧代码退役

AI 辅助绞杀者模式

我有一个遗留的 OrderService 类,包含 2000 行代码,无测试。
我想用绞杀者模式逐步替换它。

请帮我:
1. 分析 OrderService 的职责(可能有几个独立的子职责)
2. 建议拆分方案(新的类/模块划分)
3. 生成新旧接口的适配层代码
4. 生成测试用例,用于验证新旧实现行为一致

@src/services/OrderService.legacy.ts

四、重构成果量化

建立重构前后的量化对比:

# 代码质量指标统计脚本
metrics_before = {
    "函数平均行数": 45,
    "最长函数行数": 312,
    "循环复杂度": 18,
    "重复代码率": "23%",
    "测试覆盖率": "0%",
    "TypeScript 类型覆盖率": "34%"
}

metrics_after = {
    "函数平均行数": 12,
    "最长函数行数": 38,
    "循环复杂度": 4,
    "重复代码率": "3%",
    "测试覆盖率": "87%",
    "TypeScript 类型覆盖率": "100%"
}

章节小结:AI 重构遗留代码的核心价值在于"减少认知负担"。理解一段谜一样的代码、生成保护测试、逐步安全重构——这三步原本耗时数天,AI 可以压缩到数小时。记住:重构不是重写,是在保持行为不变的前提下改善代码结构。