把遗留 C# 项目交给 AI 重构之前,先做这四项准备
「让 AI 把这个十年老项目重构一下」是我今年听到最多的请求之一,也是翻车最多的一类。问题不在 AI 的重构能力——提取方法、消除重复这些它做得比大多数人好——而在遗留项目的典型特征:没有测试、文档缺失、到处是隐式约定。缺了上下文和护栏,AI 的每一次「优化」都是在开盲盒。以下四项准备,做完再动手。
一、先补一层特征测试,把当前行为「钉死」
遗留代码的正确性标准不是「应该怎样」,而是「现在怎样」。重构前后行为必须完全一致——这就是特征测试(characterization tests)的目的:它不判断对错,只固化现状。
[Fact]
public async Task CancelOrder_Should_Keep_Current_Behavior()
{
// 固化重构前的行为,不管它"合理"与否
var order = await _service.CancelAsync(SampleOrder.PaidOrder);
Assert.Equal("Cancelled", order.Status);
Assert.NotNull(order.CancelledAt);
Assert.Equal(0, order.RefundAmount); // 现状就是 0,就钉死为 0
}
优先覆盖资金、状态机、边界输入这些「改了会出事故」的路径。让 AI 帮你批量生成这些测试完全没问题——但生成之后你要人工核对每条断言确实是「现状」而不是「想当然」。
二、建立回滚与性能基线
两个基线,缺一不可:
- 版本基线:独立分支上开工,AI 的每一步改动单独成 commit,出问题随时回到任意一个中间状态;
- 性能基线:重构前用 BenchmarkDotNet 记录关键路径耗时,重构后对比。AI 重构最容易出现的隐性退化就是「代码变漂亮了,但多了一次全表扫描」。
[MemoryDiagnoser]
public class OrderQueryBenchmark
{
[Benchmark(Baseline = true)]
public void Before() => LegacyQueryRunner.Run();
[Benchmark]
public void After() => RefactoredQueryRunner.Run();
}
三、给 AI 喂上下文,尤其是「为什么」
AI 缺的从来不是代码生成能力,而是对业务约束的理解。动手前我会整理一份简短的上下文材料放进会话(或直接写进 AGENTS.md):
- 领域术语表:什么是「结算单」,和「订单」是什么关系;
- 历史决策:哪些看似糟糕的写法其实是绕过某个历史 bug 的 workaround,不能动;
- 依赖地图:哪个模块被外部系统调用,改动影响面有多大。
哪怕只有半页纸,AI 的改动质量都会有肉眼可见的提升。
四、控制改动粒度,一次只做一件事
「把这个类重构一下」是最危险的指令。正确姿势是把重构拆成正交的原子操作,一次只做一个:
- 提取方法 / 消除重复 → 跑测试 → commit;
- 统一命名 → 跑测试 → commit;
- 替换过时 API → 跑测试 → commit。
一条改动跨越三个以上文件,我会要求 AI 拆开分别提交。这样 review 时的 diff 才看得过来,出问题时也才能一眼定位。
最后说句实话:重构不是目的,可维护才是。有些代码 AI 看完也会建议——重写比重构便宜。识别出这部分,同样是准备工作的价值。