作为一个程序员,我相信每个人都见过烂代码。

那种一个函数几百行、变量名叫a/b/c、注释和代码对不上、到处复制粘贴、逻辑绕来绕去的烂代码。每次接手这样的代码,都想骂娘,都想重写。但重写有风险,老板不允许,时间不够用,只能在烂代码上继续堆烂代码,越堆越烂。

这两年AI编程工具发展起来之后,代码重构变得容易多了。AI能快速理解代码,给出重构建议,甚至直接帮你改代码。以前需要几天才能完成的重构,现在可能几个小时就能搞定。

但AI重构不是把代码丢给AI说"帮我重构一下"这么简单。用得好,能把烂代码变成优雅代码;用得不好,可能会把烂代码变成更烂的代码,甚至引入一堆bug。

这篇文章,我想分享一下使用AI工具进行代码重构的实战经验。从识别烂代码的特征、制定重构策略到使用AI工具逐步重构,聊聊如何把一堆烂代码改造成优雅、可维护的代码。

先说明一下,我主要用的AI工具是Cursor和Claude 4,也用过GitHub Copilot。不同的工具有不同的特点,但重构的思路和方法是相通的。我的经验主要基于Web开发,其他领域的重构可能会有差异。

什么是烂代码

在讲重构之前,先说说什么是烂代码。

烂代码没有一个严格的定义,但有一些常见的特征。如果你在代码中看到这些特征,那大概率就是烂代码了。

特征一:函数过长

一个函数如果超过50行,就需要警惕了。如果超过100行,那基本就是烂代码了。

过长的函数,通常做了太多事情。它可能既做数据验证,又做业务处理,还做数据持久化,最后还返回结果。这样的函数,很难理解,很难测试,也很难维护。

我见过最夸张的一个函数,有800多行。那个函数里,有十几个嵌套的if-else,有各种临时变量,有复制粘贴的代码块。我花了整整一天才看懂它在做什么,然后花了三天才把它拆成十几个小函数。

特征二:命名混乱

好的命名,能让代码自解释。看到函数名和变量名,就知道它是做什么的。

烂代码的命名,通常很混乱。变量名叫a、b、c、temp、data、info,你根本不知道它存的是什么。函数名叫doSomething、handleData、process,你也不知道它具体做什么。

还有的命名,前后不一致。同一个东西,这里叫userList,那里叫users,别的地方又叫userArr。看代码的时候,你要不断地猜这些名字是不是指同一个东西。

特征三:注释过时或误导

注释本来是用来帮助理解代码的,但烂代码的注释,往往反而会误导你。

有的注释是过时的。代码改了,但注释没改,注释说的是旧的逻辑,代码做的是新的事情。你按照注释去理解代码,就会理解错。

有的注释是废话。比如i++ // i加1,这种注释没有任何信息量,反而干扰阅读。

有的注释是推卸责任。比如// 这里很奇怪,但不要改,改了会出bug,这种注释说明写代码的人自己也没搞懂,只是侥幸没出问题。

特征四:重复代码

重复代码是烂代码最常见的特征之一。同样的逻辑,在多个地方复制粘贴,改的时候要改好几个地方,很容易漏改,导致bug。

我见过一个项目,同样的数据库查询逻辑,在十几个地方复制粘贴。后来要加一个过滤条件,改了十几个地方,还是漏了一个,线上出了bug。

重复代码的危害,怎么强调都不过分。它是bug的温床,是维护的噩梦。

特征五:嵌套过深

嵌套过深的代码,看起来像一个金字塔,越往右越缩进。看到这种代码,你的头就会大。

比如,一个函数里有五层嵌套的if-else,你要不断地在脑子里记住每一层的条件,才能搞清楚代码的执行路径。这种代码,可读性极差,也很容易出bug。

嵌套过深,通常是因为没有提前返回。如果能把一些条件提前判断,不满足就return,嵌套深度就能大大降低。

特征六:魔法数字和字符串

代码中到处都是莫名其妙的数字和字符串,你不知道它们是什么意思。

比如,if (status == 3),这个3是什么意思?是已发货?还是已取消?你要去查数据库字典,或者去问老员工,才能知道。

再比如,if (type.equals("vip_user")),这个字符串是哪里来的?有没有可能拼写错误?如果要改,要改多少个地方?

魔法数字和字符串,应该用常量或者枚举来代替。这样,代码的可读性和可维护性都会大大提高。

特征七:职责不清

一个类或者一个模块,做了太多不相关的事情。

比如,一个UserService,既做用户管理,又做订单处理,还发邮件、发短信、记日志。这个类会变得越来越大,越来越复杂,最后变成一个"上帝类",什么都做,但什么都做不好。

职责不清的代码,很难测试,很难复用,也很难维护。改一个地方,可能会影响很多不相关的功能。

这些特征,如果你在代码中看到了,那大概率就是烂代码了。烂代码不可怕,可怕的是你意识到了是烂代码,却不去重构,任由它继续烂下去。

重构前的准备

识别出烂代码之后,不要急着让AI重构。重构前的准备工作,非常重要。准备工作做得好,重构的过程就会顺利很多。

准备一:确保有测试

这是最重要的一点。重构的前提是,你有测试来验证重构后的代码行为是否和原来一致。

如果没有测试,重构就是在裸奔。AI改完代码之后,你不知道它有没有改变代码的行为,有没有引入bug。

所以,在重构之前,先确保你有足够的测试覆盖。如果测试不够,先补测试,再重构。

补测试的时候,要针对现有的代码行为来写,不管这个行为是不是你想要的。测试的目的是锁定现有行为,确保重构之后行为不变。等重构完成之后,如果你发现现有行为有问题,再改行为和测试。

准备二:用版本控制,分小步提交

重构的时候,一定要用Git等版本控制工具,而且要分小步提交。

不要一次性重构一大堆代码,然后提交一个巨大的commit。这样出了问题,很难定位是哪次改作出了问题,也很难回滚。

正确的做法是,分小步重构,每完成一个小的重构,就提交一次。每个commit只做一件事情,比如"提取函数"、"重命名变量"、"简化条件表达式"。

这样,出了问题,可以方便地回滚到上一个正常的版本,也可以通过git bisect快速定位是哪次改作出了问题。

准备三:理解代码的业务逻辑

在让AI重构之前,你自己要先理解代码的业务逻辑。

AI能理解代码的语法和结构,但不一定能理解代码背后的业务含义。比如,一段看起来很奇怪的代码,可能是为了处理某个特殊的业务场景,或者是为了兼容某个老版本。如果你不理解,直接让AI重构,AI可能会把这些代码删掉或者改掉,导致业务出问题。

所以,在重构之前,先花时间理解代码的业务逻辑。可以看代码注释、看文档、问老员工,搞清楚每段代码是做什么的,为什么要这么写。

理解了业务逻辑之后,你才能给AI正确的指令,告诉它哪些代码不能动,哪些地方需要特别注意。

准备四:明确重构的目标和范围

重构之前,要明确重构的目标和范围。

你是想提高代码的可读性?还是想提高性能?还是想修复技术债务?还是想升级技术栈?

不同的目标,重构的策略和方法是不一样的。明确了目标,才能给AI正确的指令,让AI朝着正确的方向重构。

同时,也要明确重构的范围。是重构一个函数?一个类?一个模块?还是整个项目?

建议从小范围开始,先重构一个函数或者一个类,熟悉AI的行为和特点,积累经验,然后再逐步扩大范围。

不要一上来就让AI重构整个项目,那样风险太大,很容易出问题。

AI重构的实战步骤

准备工作做好之后,就可以开始用AI重构了。下面是我总结的实战步骤。

步骤一:给AI足够的上下文

AI重构的效果,很大程度上取决于你给它的上下文。

如果你只给AI一个函数,让它重构,它可能不知道这个函数是怎么被调用的,返回值是怎么被使用的,重构的时候可能会改变函数的接口,导致调用方出错。

所以,给AI的上下文要足够多。除了要重构的代码,还要给它相关的代码,比如:

  • 函数的调用方
  • 函数的依赖
  • 相关的类型定义
  • 相关的测试用例
  • 项目的代码规范和风格指南

上下文越充分,AI重构的效果就越好,越不容易出错。

当然,上下文也不是越多越好,要注意AI的上下文窗口限制。要给AI最相关的代码,而不是把整个项目都塞给它。

我的经验是,对于一个函数的重构,至少要给AI这个函数的完整代码,以及它的调用方和被调用方。如果这个函数比较复杂,还要给它相关的类型定义和测试用例。

步骤二:用清晰、具体的指令

给AI的指令要清晰、具体,不要太模糊。

比如,不要说"帮我重构一下这段代码",而是要说"帮我把这个函数拆分成两个小函数,一个负责数据验证,一个负责业务处理,保持函数的输入输出不变"。

具体的指令,能让AI更准确地理解你的意图,给出你想要的重构结果。

另外,指令中要明确告诉AI一些约束条件,比如:

  • 不要改变函数的输入输出
  • 不要改变代码的外部行为
  • 遵循项目的代码规范
  • 保持向后兼容
  • 不要引入新的依赖
  • 用中文写注释

这些约束条件,能防止AI做出一些你不想要的改变。

我的习惯是,每次给AI的指令,都包含三个部分:做什么、怎么做、约束条件。这样,AI就能很清楚地知道我的要求。

步骤三:分步骤重构,不要一步到位

复杂的重构,要分步骤进行,不要指望AI一步到位。

比如,你想把一个大类拆分成多个小类,可以分几步来:

第一步,先提取一些工具函数 第二步,把相关的函数和数据整理到一起 第三步,创建新的类,把相关的代码移过去 第四步,更新调用方,使用新的类

每一步都让AI做一个小的改变,然后运行测试,验证没有问题,再进行下一步。

分步骤重构的好处是,每一步的变化都很小,容易审查和验证,出了问题也容易定位和回滚。

我见过很多人,用AI重构的时候,喜欢一次性让AI做很大的改变。结果AI改完之后,代码面目全非,审查起来很费劲,测试也挂了一堆,最后只能回滚。

所以,不要急,慢慢来。小步快跑,每一步都验证,反而比一步到位更快、更安全。

步骤四:人工审查AI的改作

AI改完代码之后,一定要人工审查,不要直接提交。

AI虽然很强大,但还是会犯错。它可能会改变代码的行为,可能会引入bug,可能会写出不符合项目规范的代码。

人工审查的时候,要重点关注以下几点:

  • 代码的逻辑是否正确
  • 有没有改变代码的外部行为
  • 有没有引入新的bug
  • 代码的可读性是否真的提高了
  • 是否遵循了项目的代码规范
  • 有没有过度重构

审查的时候,不要只看AI改了什么,还要想为什么这么改,这么改有没有道理,有没有更好的改法。

人工审查是AI重构中非常重要的一环,不能省略。AI是助手,人是主导,最终的代码质量还是要靠人来保证。

我的习惯是,AI改完之后,我会逐行看diff,每一行都想清楚为什么这么改。如果有疑问,就问AI为什么这么改。如果觉得改得不好,就让AI重新改,或者自己手动改。

步骤五:及时运行测试

每完成一步重构,都要及时运行测试,验证代码的行为是否正确。

不要等全部重构完了再跑测试,那样出了问题很难定位是哪一步出的错。

每改完一个小部分,就跑一遍相关的测试。如果测试通过了,说明这一步重构是正确的,可以继续下一步。如果测试失败了,说明重构引入了问题,需要修复或者回滚。

及时运行测试,能快速发现问题,避免问题积累,最后难以收拾。

如果你的项目测试覆盖率不高,那更要小心。每改完一步,除了跑测试,还要手动验证一下核心功能是否正常。

实战案例:把一个烂函数重构成优雅代码

说了这么多步骤,下面用一个实际的例子来演示一下,如何用AI把一个烂函数重构成优雅代码。

假设我们有这样一个函数:

def f(a, b, c):
    if a is not None:
        if b is not None:
            if c > 0:
                d = a * b + c
                if d > 100:
                    return "big"
                else:
                    return "small"
            else:
                return "invalid_c"
        else:
            return "no_b"
    else:
        return "no_a"

这个函数,有很多烂代码的特征:

  • 函数名f,不知道做什么的
  • 参数名a、b、c,不知道是什么意思
  • 嵌套过深,有四层if-else
  • 魔法字符串"big"、"small"、"no_a"等
  • 没有注释

我们用AI来重构这个函数。

第一步:理解代码,给AI上下文

首先,我们要理解这个函数在做什么。看代码,它好像是在计算a*b+c,然后根据结果返回"big"或"small",同时做参数验证。

我们给AI的上下文,包括这个函数的代码,以及我们对它的理解。

第二步:给AI具体的指令

我们给AI的指令是:

"请重构这个函数,要求:

  1. 给函数和参数起有意义的名字
  2. 用提前返回降低嵌套深度
  3. 用常量代替魔法字符串
  4. 保持函数的输入输出行为不变
  5. 添加适当的注释"

第三步:AI重构后的结果

AI重构后的代码可能是这样的:

BIG_RESULT = "big"
SMALL_RESULT = "small"
ERROR_NO_A = "no_a"
ERROR_NO_B = "no_b"
ERROR_INVALID_C = "invalid_c"
RESULT_THRESHOLD = 100

def calculate_and_classify(a, b, c):
    """
    计算a*b+c的结果,并根据结果大小分类。
    
    参数:
        a: 第一个乘数,不能为None
        b: 第二个乘数,不能为None
        c: 加数,必须大于0
    
    返回:
        "big": 结果大于100
        "small": 结果小于等于100
        "no_a": a为None
        "no_b": b为None
        "invalid_c": c小于等于0
    """
    if a is None:
        return ERROR_NO_A
    
    if b is None:
        return ERROR_NO_B
    
    if c <= 0:
        return ERROR_INVALID_C
    
    result = a * b + c
    
    if result > RESULT_THRESHOLD:
        return BIG_RESULT
    
    return SMALL_RESULT

第四步:人工审查

我们来审查AI的改作:

  • 函数名calculateandclassify,准确描述了函数的功能,好。
  • 参数名还是a、b、c,因为我们不知道它们具体是什么,保持原样也可以。如果知道具体含义,可以改得更有意义。
  • 用了提前返回,嵌套深度从四层降到了一层,可读性大大提高,好。
  • 用了常量代替魔法字符串,好。
  • 添加了详细的注释,好。
  • 函数的输入输出行为没有改变,好。

这个重构,我觉得是合格的。

第五步:运行测试

如果有测试的话,跑一遍测试,验证行为不变。如果测试通过,就可以提交了。

通过这个简单的例子,你可以看到AI重构的基本流程。实际项目中的代码,可能比这个复杂得多,但基本的思路和步骤是一样的。

常见的重构场景和AI指令模板

下面,我整理了一些常见的重构场景,以及对应的AI指令模板。你可以直接用这些指令,或者根据自己的需要修改。

场景一:提取函数

当一个函数太长或者做了太多事情的时候,需要把其中的一部分逻辑提取成独立的函数。

指令模板: "请把选中的代码提取成一个独立的函数。要求:

  1. 函数名要能准确描述函数的功能
  2. 参数和返回值要合理
  3. 原函数调用新函数,保持行为不变
  4. 添加适当的注释"

场景二:重命名

当变量、函数或者类的名字不够清晰的时候,需要重命名。

指令模板: "请给这个变量/函数/类起一个更准确、更有描述性的名字。要求:

  1. 名字要准确描述其用途
  2. 符合项目的命名规范
  3. 更新所有引用的地方,不要遗漏
  4. 不要改变代码的行为"

场景三:简化条件表达式

当条件表达式太复杂的时候,需要简化,或者提取成有意义的变量和函数。

指令模板: "请简化这个条件表达式。要求:

  1. 把复杂的条件提取成有意义的变量或者函数
  2. 用提前返回降低嵌套深度
  3. 保持逻辑完全不变
  4. 提高可读性"

场景四:消除重复代码

当有重复代码的时候,需要提取成公共的函数或者类。

指令模板: "请找出这些代码中的重复部分,提取成一个公共的函数/类。要求:

  1. 公共函数/类要有单一职责
  2. 不要强行合并逻辑不同的代码
  3. 所有调用方都使用公共函数/类
  4. 保持行为不变"

场景五:替换魔法数字和字符串

当代码中有魔法数字和字符串的时候,需要用常量或者枚举代替。

指令模板: "请把代码中的魔法数字/字符串替换成常量/枚举。要求:

  1. 常量名要能准确描述其含义
  2. 所有使用的地方都替换成常量
  3. 保持行为不变
  4. 常量定义放在合适的位置"

场景六:改善命名和注释

当代码的命名和注释不够好的时候,需要改善。

指令模板: "请改善这段代码的命名和注释。要求:

  1. 给变量、函数、类起更有意义的名字
  2. 添加清晰的注释,说明代码的用途和逻辑
  3. 删除过时的、误导性的注释
  4. 不要改变代码的行为"

这些指令模板,是我平时用得比较多的。你可以根据自己的项目和需求,灵活调整。

AI重构的常见坑和如何避免

在AI重构的过程中,我踩了很多坑。这里分享几个最常见的坑,以及如何避免。

坑一:AI改变了代码的行为

这是最常见的坑。AI重构的时候,可能会无意中改变代码的行为,引入bug。

比如,AI可能会把一个有副作用的函数改成纯函数,或者把一个异步的调用改成同步的,或者改变了边界条件的处理。

如何避免:

  • 给AI明确的指令:"不要改变代码的外部行为"
  • 重构之后,仔细审查代码,特别是边界条件和异常处理
  • 及时运行测试,用测试来验证行为是否改变
  • 对于关键的业务逻辑,不要完全依赖AI,要人工仔细审查

坑二:AI过度重构

有时候,AI会为了重构而重构,把简单的代码改得很复杂,反而降低了可读性。

比如,AI可能会把一个简单的三目运算符改成一个复杂的函数,或者把一个直接的计算改成设计模式,反而让代码更难理解。

如何避免:

  • 给AI明确的指令:"只做我要求的重构,不要做额外的改变"
  • 审查的时候,关注代码的可读性是否真的提高了
  • 如果AI的改作让代码更复杂了,就不要接受,保持原来的代码
  • 记住,重构的目的是提高可读性和可维护性,不是为了用更多的设计模式

坑三:AI不理解业务逻辑

AI能理解代码的语法,但不一定能理解代码背后的业务逻辑。有些看起来很奇怪的代码,可能是为了处理某个特殊的业务场景,AI可能会把它当成冗余代码删掉。

如何避免:

  • 重构之前,先理解代码的业务逻辑
  • 给AI指令的时候,告诉它哪些代码不能动,哪些地方有特殊的业务含义
  • 对于关键的业务逻辑,人工审查要特别仔细
  • 可以在代码中加注释,说明业务逻辑,AI看到注释就不会乱改了

坑四:上下文不足导致的错误

AI的上下文窗口是有限的。如果你只给AI一小段代码,它可能看不到相关的依赖和调用方,重构的时候就会出错。

比如,AI可能会改变一个函数的参数,但没有更新所有的调用方,导致编译错误。

如何避免:

  • 给AI足够的上下文,包括调用方、依赖、类型定义等
  • 对于大型项目,可以分模块重构,每个模块给AI足够的上下文
  • 重构之后,要编译代码,检查有没有编译错误
  • 运行测试,验证功能是否正常

坑五:代码风格不一致

AI生成的代码,风格可能和项目的代码风格不一致。比如,命名方式、缩进、引号、分号等。

如果不注意,重构之后的代码会和原来的代码风格不一致,影响可读性。

如何避免:

  • 给AI项目的代码规范,让AI遵循
  • 重构之后,用代码格式化工具(比如Prettier、ESLint)格式化代码
  • 审查的时候,关注代码风格是否一致
  • 可以在CI中加入代码风格检查,防止风格不一致的代码被提交

重构后的验证

重构完成之后,验证工作也很重要。不要重构完就觉得完事了,要做充分的验证。

验证一:运行所有测试

这是最基本的验证。重构之后,要运行所有的测试,包括单元测试、集成测试、端到端测试。

如果所有测试都通过了,说明重构没有改变代码的行为,基本是安全的。

如果有测试失败了,说明重构引入了问题,需要修复。

验证二:代码审查

重构之后,要做仔细的代码审查。

可以自己审查,也可以让同事审查。审查的时候,重点关注:

  • 代码的逻辑是否正确
  • 有没有改变代码的外部行为
  • 代码的可读性是否提高了
  • 有没有引入新的技术债务
  • 是否遵循了项目的代码规范

代码审查是保证重构质量的重要环节,不能省略。

验证三:性能测试

如果重构涉及到性能敏感的代码,要做性能测试,确保重构没有降低性能。

有时候,AI为了提高可读性,可能会把一些高性能的写法改成普通的写法,导致性能下降。对于性能敏感的代码,要特别注意。

可以用性能测试工具,对比重构前后的性能,确保没有明显的下降。

验证四:灰度发布

对于大型项目的重构,建议灰度发布。

先把重构后的代码发布给一小部分用户,观察有没有问题。如果没有问题,再逐步扩大范围,最后全量发布。

灰度发布能降低重构的风险,即使出了问题,影响的范围也有限。

写在最后

AI重构,是这两年AI编程给我们带来的最大红利之一。以前需要几天甚至几周才能完成的重构,现在可能几个小时就能搞定。这让我们有更多的精力去做更有价值的事情。

但AI重构不是银弹。它能提高效率,但不能代替人的思考和判断。AI是助手,人是主导。最终的代码质量,还是要靠人来保证。

用AI重构的关键,是掌握正确的方法。重构前做好准备,重构中用正确的策略,重构后做充分的验证。同时,要避免常见的坑,不要完全依赖AI。

我相信,随着AI技术的不断发展,AI重构的能力会越来越强。但不管技术怎么变,核心的原则不会变:测试是保障,人工审查是关键,小步快跑是策略,代码质量是目标。

最后,用一句话来结束这篇文章:"好的代码不是写出来的,而是改出来的。AI让改代码变得更容易了,但写出好代码的,还是人。"

愿你能用好AI这个工具,把烂代码改成优雅代码,享受编程的乐趣。