作为一个程序员,我相信每个人都见过烂代码。
那种一个函数几百行、变量名叫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的指令是:
"请重构这个函数,要求:
- 给函数和参数起有意义的名字
- 用提前返回降低嵌套深度
- 用常量代替魔法字符串
- 保持函数的输入输出行为不变
- 添加适当的注释"
第三步: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指令模板。你可以直接用这些指令,或者根据自己的需要修改。
场景一:提取函数
当一个函数太长或者做了太多事情的时候,需要把其中的一部分逻辑提取成独立的函数。
指令模板: "请把选中的代码提取成一个独立的函数。要求:
- 函数名要能准确描述函数的功能
- 参数和返回值要合理
- 原函数调用新函数,保持行为不变
- 添加适当的注释"
场景二:重命名
当变量、函数或者类的名字不够清晰的时候,需要重命名。
指令模板: "请给这个变量/函数/类起一个更准确、更有描述性的名字。要求:
- 名字要准确描述其用途
- 符合项目的命名规范
- 更新所有引用的地方,不要遗漏
- 不要改变代码的行为"
场景三:简化条件表达式
当条件表达式太复杂的时候,需要简化,或者提取成有意义的变量和函数。
指令模板: "请简化这个条件表达式。要求:
- 把复杂的条件提取成有意义的变量或者函数
- 用提前返回降低嵌套深度
- 保持逻辑完全不变
- 提高可读性"
场景四:消除重复代码
当有重复代码的时候,需要提取成公共的函数或者类。
指令模板: "请找出这些代码中的重复部分,提取成一个公共的函数/类。要求:
- 公共函数/类要有单一职责
- 不要强行合并逻辑不同的代码
- 所有调用方都使用公共函数/类
- 保持行为不变"
场景五:替换魔法数字和字符串
当代码中有魔法数字和字符串的时候,需要用常量或者枚举代替。
指令模板: "请把代码中的魔法数字/字符串替换成常量/枚举。要求:
- 常量名要能准确描述其含义
- 所有使用的地方都替换成常量
- 保持行为不变
- 常量定义放在合适的位置"
场景六:改善命名和注释
当代码的命名和注释不够好的时候,需要改善。
指令模板: "请改善这段代码的命名和注释。要求:
- 给变量、函数、类起更有意义的名字
- 添加清晰的注释,说明代码的用途和逻辑
- 删除过时的、误导性的注释
- 不要改变代码的行为"
这些指令模板,是我平时用得比较多的。你可以根据自己的项目和需求,灵活调整。
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这个工具,把烂代码改成优雅代码,享受编程的乐趣。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录