返回文章列表

从零开始:用 AI 重构遗留代码的完整指南

学习如何安全地使用 AI 工具重构老旧代码,包括评估、规划、执行和验证的完整流程。

AIProgHub
2024年12月04日
7 min read
重构遗留代码AI编程最佳实践

遗留代码的痛点

每个程序员都遇到过这种代码:

// 2018 年写的,没人敢动
function processData(d, f, t, opts) {
  var r = [];
  for (var i = 0; i < d.length; i++) {
    if (d[i].type == t || t == null) {
      var x = d[i];
      if (opts && opts.filter) {
        if (opts.filter(x)) {
          if (f) {
            r.push(f(x));
          } else {
            r.push(x);
          }
        }
      } else {
        if (f) {
          r.push(f(x));
        } else {
          r.push(x);
        }
      }
    }
  }
  return r;
}

看不懂?别担心,AI 可以帮你。

重构前的准备

1. 评估代码覆盖率

# 运行测试并生成覆盖率报告
npm test -- --coverage

如果覆盖率低于 60%,先补测试。

2. 让 AI 解释代码

Prompt:

分析以下代码的功能:
1. 解释每个参数的作用
2. 描述函数的主要逻辑
3. 列出潜在的问题和风险

代码:
[粘贴代码]

AI 分析结果:

这个函数的功能是对数据数组进行过滤和转换:

参数说明:
- d: 数据数组
- f: 可选的转换函数
- t: 可选的类型过滤器
- opts: 配置对象,包含额外的过滤函数

问题:
1. 参数命名太简略,难以理解
2. 嵌套 if 太深(4 层),认知负担重
3. 使用 var 而非 let/const
4. 重复代码(push 逻辑出现两次)
5. 缺少类型检查和错误处理

3. 确定重构目标

根据 AI 分析,设定明确目标:

  • [ ] 改善命名
  • [ ] 减少嵌套层级
  • [ ] 使用现代 JavaScript 语法
  • [ ] 添加类型定义(TypeScript)
  • [ ] 消除重复代码

分步重构策略

第一步:补充测试

Prompt:

为这个函数生成全面的单元测试,覆盖:
1. 正常使用场景
2. 边界条件
3. 空值/null 处理
4. 各种参数组合

AI 生成的测试:

describe('processData', () => {
  const sampleData = [
    { id: 1, type: 'A', value: 10 },
    { id: 2, type: 'B', value: 20 },
    { id: 3, type: 'A', value: 30 },
  ];

  describe('基本过滤', () => {
    it('按类型过滤数据', () => {
      const result = processData(sampleData, null, 'A');
      expect(result).toHaveLength(2);
      expect(result.every(item => item.type === 'A')).toBe(true);
    });

    it('type 为 null 时返回所有数据', () => {
      const result = processData(sampleData, null, null);
      expect(result).toHaveLength(3);
    });
  });

  describe('转换功能', () => {
    it('应用转换函数', () => {
      const transform = item => item.value * 2;
      const result = processData(sampleData, transform, null);
      expect(result).toEqual([20, 40, 60]);
    });
  });

  describe('opts.filter', () => {
    it('应用额外过滤器', () => {
      const opts = { filter: item => item.value > 15 };
      const result = processData(sampleData, null, null, opts);
      expect(result).toHaveLength(2);
    });
  });

  describe('边界条件', () => {
    it('空数组返回空数组', () => {
      expect(processData([], null, null)).toEqual([]);
    });

    it('undefined 数据抛出错误', () => {
      expect(() => processData(undefined)).toThrow();
    });
  });
});

运行测试确保全部通过。

第二步:小步重构

原则:每次只改一件事

第一轮:改善命名

function processData(data, transform, typeFilter, options) {
  var results = [];
  for (var i = 0; i < data.length; i++) {
    if (data[i].type == typeFilter || typeFilter == null) {
      var item = data[i];
      // ... 其余逻辑不变
    }
  }
  return results;
}

运行测试 → 全部通过 → 提交

第二轮:现代化语法

function processData(data, transform, typeFilter, options) {
  const results = [];
  for (const item of data) {
    if (item.type === typeFilter || typeFilter === null) {
      // ... 其余逻辑不变
    }
  }
  return results;
}

运行测试 → 全部通过 → 提交

第三轮:减少嵌套

function processData(data, transform, typeFilter, options) {
  const results = [];

  for (const item of data) {
    // 使用 early continue 减少嵌套
    if (typeFilter !== null && item.type !== typeFilter) {
      continue;
    }

    if (options?.filter && !options.filter(item)) {
      continue;
    }

    const result = transform ? transform(item) : item;
    results.push(result);
  }

  return results;
}

运行测试 → 全部通过 → 提交

第三步:AI 辅助最终优化

Prompt:

将以下代码重构为现代 TypeScript,要求:
1. 添加完整类型定义
2. 使用函数式编程风格
3. 添加 JSDoc 文档
4. 保持功能完全相同

代码:
[粘贴第三轮结果]

最终版本:

interface DataItem {
  type: string;
  [key: string]: unknown;
}

interface ProcessOptions<T extends DataItem> {
  filter?: (item: T) => boolean;
}

/**
 * 过滤和转换数据数组
 *
 * @param data - 待处理的数据数组
 * @param transform - 可选的转换函数
 * @param typeFilter - 可选的类型过滤器,null 表示不过滤
 * @param options - 额外配置选项
 * @returns 处理后的数据数组
 *
 * @example
 * const result = processData(
 *   users,
 *   user => user.name,
 *   'admin',
 *   { filter: user => user.active }
 * );
 */
export function processData<T extends DataItem, R = T>(
  data: T[],
  transform?: ((item: T) => R) | null,
  typeFilter?: string | null,
  options?: ProcessOptions<T>
): R[] {
  return data
    .filter(item => typeFilter === null || item.type === typeFilter)
    .filter(item => !options?.filter || options.filter(item))
    .map(item => (transform ? transform(item) : item) as R);
}

重构检查清单

  • [ ] 所有测试通过
  • [ ] 覆盖率没有下降
  • [ ] 性能没有明显退化
  • [ ] 代码可读性提升
  • [ ] 团队成员 review 通过

常见陷阱

1. 一次改太多

❌ 错误:一口气重构整个文件 ✅ 正确:每次只改一件事,频繁提交

2. 忽略测试

❌ 错误:先重构,后补测试 ✅ 正确:先补测试,再重构

3. 盲目相信 AI

❌ 错误:AI 生成什么就用什么 ✅ 正确:每次改动都运行测试验证

总结

AI 让遗留代码重构变得可控:

  1. 理解:让 AI 解释复杂代码
  2. 测试:让 AI 生成测试用例
  3. 重构:让 AI 建议改进方案
  4. 验证:始终运行测试确保正确

记住:AI 是助手,不是替代品。最终的决策权在你手中。


延伸阅读AI 编程技巧 | Prompt 工程指南

相关推荐

查看全部

订阅我们的邮件列表

第一时间获取最新 AI 编程教程和工具推荐

我们尊重你的隐私,不会分享你的邮箱

从零开始:用 AI 重构遗留代码的完整指南 | AIProgHub