重生之我是软件开发者:重构代码

什么是重构

重构是一个在不改变已有代码功能性的前提下,改变代码的设计的一个过程。

重构不是重写,它们的区别可以理解为,重构是修复代码,大框架不变。

重写是扔掉原来的,重新设计框架。

作为软件开发者,经常需要面对提升或优化我们代码的需求——无论是为了性能,可读性还是可维护性。

为什么需要重构?

因为代码不是个静态的东西,它会随着时间变得越来越复杂。

什么时候需要重构?

当发现以下几种情况时,应该重构:

一、代码不符合代码规范。

二、有新的实现方式具有更高的效率。

三、你看完代码后觉得应该重构了。

重构是一种习惯,而不是一个任务。

注意事项

一、重构应该是日常行为,应该在每天的任务里预留一部分时间,即可以用来应对临时需求,也可以用来做重构。

二、不要在重构的代码的时候,添加新的功能,不然可能会引发更多问题。

三、在开始重构之前,要先做好测试:既保证重构之前代码可运行的,也能保证出现问题后第一时间发现。

四、小步骤重构:将字段从一个类移动到另一个类,拆分方法,重命名变量。

重构通常涉及对许多局部进行的修改,这些局部修改最终会导致更大范围的修改。

如果保持小步骤,并在每个步骤之后进行测试,就能避免冗长的调试。

五、重构之前要备份,一旦出现问题或者有其他重要事情,还可以还原。

重构代码实践

  1. 确认问题区域
  2. 制定计划
  3. 保持较小的变更:重构代码时,进行小的增量更改。这将有助于确保代码不会变得不稳定,并且也可以在必要时撤销所做的更改。
  4. 编写测试:编写良好的测试将指导你完成重构过程并确保你没有破坏应用。测试可以为你创造一个安全的环境,以使你更专注于重构的目标。
  5. 增量重构:逐步进行小的更改并伴随着测试。
  6. 使用重构工具
  7. 记录你的变更:完成重构后,记录你所做的更改,帮助其他人了解代码更改的原因以及新的结构。
  8. 使用源码控制系统:在重构代码时,使用源代码控制系统,如果出现问题,可以轻松回退到以前的版本。
  9. 进行回归测试:重构代码后进行回归测试,有助于确保更改没有引入任何新的bug。
  10. 时刻准备撤销变更:不是每次重构都会成功的,准备在必要的情况下撤销变更。

重构代码对于每个软件开发人员而言都应该是必备技能。

重构入门

1. 格式化代码

当你发现代码缩进层次不齐,代码块中缺少{}等问题时,就需要考虑代码格式化了

现在的 IDE 工具已经对格式化提供了很好的支持,以 eclipse 为例,选中要格式化的代码,点击以下菜单项就能完成代码格式化。

此外很多源代码管理网站,也提供了格式化工具

在团队开发中,为了保证开发代码样式统一,需要建立编码规范。

我们并不需要重头建立编码规范,可以在大厂的编码规范基础上进行定制。

重要的是,不管选择何种规范,要坚持下去。

2. 注释

在代码开发中,好的注释可以提高程序的可读性。

【重构手法】提炼函数、改变函数声明、引入断言。

好的注释要做到和代码相关、及时更新。

尽量减少不必要的注释。

对于逻辑混乱的代码,如在循环中随意使用 break,复杂的 if 语句嵌套等,首先要做的是理清逻辑,重构代码。

“当你感觉需要撰写注释,请先尝试重构,试着让所有注释都变得多余。”

3. 废弃的代码

随着系统版本的不断迭代,有一些函数或类不再使用后,应该将它及时删除,否则随着时间流逝,会造成代码库臃肿,程序可读性变差。

多先进的 IDE 工具都对查找代码的调用提供了支持。

4. 变量命名

一个好名字对变量、常量、函数和类都很重要。

一个好的名字会让其他开发人员很容易明白其功能是什么。

函数、模块、变量、类等没有一个好的名字。

它的名字含义模糊,不能清晰表达自己的功能和用法,背后可能潜藏着更深的设计问题。

【重构手法】改变函数声明、变量改名、字段改名。

以下是命名的一些注意事项:

  • 类和文件名使用名词,但这个名词要有意义

  • 函数使用动词或短语命名

  • 长名字 vs 无意义名字:在长名字和无意义名字中选择时,请选择长且有意义的名字

  • 命名法则: 常见的有驼峰命名法(camelCase)和蛇形命名法(snake_case),比如文件名使用蛇形是 file_name,驼峰式 fileName。选择一种,所有的命名都按照这个规则,并将其作为编码规范的一部分

如果你要对已有代码中错误的命名方式进行修改,eclipse 提供了很好地支持:选择要修改的类、函数或变量,选择 Refactor ——> Rename 可以同时修改该变量在声明和使用处的名称:

5. 常量命名

6. 负值条件的重构

在条件或循环语句中,使用负值条件,会让代码难以理解、容易出错。

重构方法是将条件改成正值,并调换 if/else 语句代码块的顺序。

7. {} 作为单独的一行

如果你没有将括号作为单独的一行,你得到的好处只是减少了一行代码,但是当你设置断点调试时,断点将不能精确定位到你想调试的部分。

8. 变量定义和使用距离太远

变量的定义和使用不要离得太远,一般不要超过 20 行,函数也类似。

如果意识到这个问题,但并不能缩短定义和使用的距离,那代表这是个大函数(big function),需要对函数做拆分。

在进行重构时,最重要的规则是:每次只做微小修改,并保证测试能正确运行(小步快跑)。

重构进阶

1. 重复代码

当发现相同的代码块在三个地方都出现时,就需要考虑重构代码了。

相同的代码结构出现在一个以上的地点。

一旦有重复代码,阅读和修改这些代码都需要额外留意,它们之间的差异。

【重构手法】提炼函数、移动语句、函数上移。

对于同一个类中重复的代码块,可使用提取方法(extract method:将重复代码提取出单独的函数)来完成;对于一组相关类如父类、子类 A、子类 B 中的重复函数,通过上移方法(pull method:将子类中的方法移入父类中)和模板方法(template method:父类方法定义模板,子类编写不同实现)来完成。

Eclipse 提供了相关功能,如图所示:

2. 函数参数

函数越长,越难理解。

越短小的函数能带来更好的阐释力、更易于分享、更多的选择。

【重构手法】提炼函数、引入参数对象、以查询取代临时变量、保持对象完整、以命令取代函数、分解条件表达式、以多态取代条件表达式、拆分循环。

参数越多,越难维护。

变得越来越长的参数列表会越来越难维护,令人迷惑。

【重构手法】以查询取代参数、保持对象完整、引入参数对象、移除标记参数、函数组合成类

  • 开关参数的滥用(boolean parameters):函数的形参中有一个是 boolean 类型,函数体根据该参数为 true 或者 false 执行不同的代码块。这种方式会导致重构的另一个坏味道——大函数(big function)的形成,从而增加代码的复杂性。重构方法是:去掉这个开关参数,将函数拆分成两个函数。

  • 在函数内修改了参数: 参数用于外界向函数体传递信息,好的做法是参数对于函数是只读的。如果在函数内修改参数,会造成函数功能难以理解,如果函数内多次修改参数,这个函数会变成一座迷宫,重构方法是:将参数赋值给局部变量,对局部变量修改

  • 参数太多:函数的参数最多有三个是合理的,超过三个就需要提高警惕了。重构方法是:根据逻辑拆分函数;引入参数对象(parameter object:构造参数类,将原来传递的参数作为类的属性,调用方传入该类的一个对象)

3. 变量多余

当定义的变量没太多含义,而且没有赋值操作

类内部某字段仅为某种特定情况而设置,大部分时候不会使用到。

【重构手法】提炼类、搬移函数、引入特例。

4. 缺少变量

某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。如果它们被赋值超过一次,就意味着它们在函数中承担了一个以上的职责。如果临时变量承担多个责任,它就应该被替换为多个临时变量,每个变量只承担一个责任。

重构方法:针对每次赋值,创造一个独立、对应的临时变量

5. 复杂条件

由 && || 构成的复杂的多行条件。复杂条件可读性很差,调试和修改也很麻烦。

一个简单的重构方式是:将这块代码抽取出来,变成一个单独的判断函数

其他重构

1、全局数据

存在过多的全局数据,污染了代码。

全局数据造成了诡异的Bug,但问题的根源却在遥远的别处。

不限于全局变量,类变量和单例也有这个问题。

【重构手法】封装变量

2、可变数据

对一处数据的改变影响到了另一处的功能。

需要约束数对数据的更新,降低其风险。

【重构手法】封装变量、拆分变量、移动语句、提炼函数、函数和修改函数分离、移除设值函数、以查询取代派生变量、函数组合成类、函数组合成变换、将引用对象改为值对象。

3、发散式变化

功能逻辑耦合,上下文边界不清晰,模块因为不同的原因在不同方向上发生变化。

【重构手法】拆分阶段、搬移函数、提炼函数、提炼类。

4、霰弹式修改

遇到某种变化,需要在不同类内做出许多小修改。

如果散布到各处,不但很难找到它们,也很容易错过某个重要的修改。

【重构手法】搬移函数、搬移字段、函数组合成类、函数组合成变换、拆分阶段、内联函数、内联类。

5、依恋情节

一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处内部的交流。

【重构手法】搬移函数、提炼函数

6、数据泥团

有一些零散的数据常常一起结伴出现。

其中一个数据删除后,通常其他的数据也失去意义。

【重构手法】提炼类、引入参数对象、保持对象完整。

7、基本类型偏执

没有为业务类型创建专门的类,而只用编程语言提供的基本类型。

【重构手法】以对象取代基本类型、以子类取代类型码、以多态取代条件表达式。

8、重复的switch

每当你想要增加一个选择分支时,必须找到所有的switch。

类似上方提到的霰弹式修改。

【重构手法】以多态取代条件表达式。

9、循环语句

现代编程语言支持管道操作,能够替换循环语句,提高代码可读性

【重构手法】以管道取代循环。

10、冗赘的元素

由于种种原因,程序中存在了冗余的结构。

可能是过度设计、可能是没有及时清理、可能是单纯的多了一个中间层。

【重构手法】内联函数、内联类、折叠继承体系。

11、夸夸其谈通用性

为假想的非必要的情况而过度设计。

【重构手法】内联函数、内联类、折叠继承体系、改变函数声明、移除死代码。

12、过长的消息链

层层请求对象、取设置等过长的消息传递。请求对象客户端代码将与查找过程中的导航结构紧密耦合。

【重构手法】隐藏委托关系、提炼函数、搬移函数。

13、中间人委托

过度委托。如某个类的接口有一半都是委托给其他类。

【重构手法】移除中间人、内联函数。

14、内幕交易

两个类彼此使用对方的私有的东西。

【重构手法】搬移函数、搬移字段、隐藏委托关系、以委托取代子类、以委托取代超类。

15、过大的类

单个类做的事太多了:有太多的字段。

【重构手法】提炼类、提炼超类、以子类取代类型码。

16、异曲同工的类

两个实现相似/相同功能的类,有不同的接口。

【重构手法】改变函数声明、搬移函数、提炼超类。

17、纯数据类

数据类与它的行为分离,它的行为往往被放在了错误的地方。

数据类成为一种不会说话的数据容器。

【重构手法】封装记录、移除设值函数、搬移函数、提炼函数、拆分阶段。

18、继承

继承体系设计错误,子类没有利用好父类的元素。

没有遵守父类规定的“玩法”。

【重构手法】函数下移、字段下移、以委托取代子类、以委托取代超类。

老旧代码的重构

在进行代码重构时,需要考虑测试代码是否能覆盖重构的功能,如果没有,需要增加测试用例覆盖所做的修改,否则重构可能会破坏已有的功能。

但如何重构测试用例没有完全覆盖的代码,如老旧代码?建议是只做必要的重构,如当需要修正 bug 或者增加新的功能,这种情况下,先为遗留代码编写测试用例,在理解的基础上重构代码,为代码修改做好准备,然后进行代码修改。

进行任何类型代码的重构:一次只做一步重构,从小的容易的重构做起,并频繁测试。

利用工具

重构代码需要花费时间,当项目工期很紧时,很难下定决心去做重构。

为了让重构变得更容易,市面上提供了大量相关工具,如 pylint( Python 代码分析工具)、Checkstyle(代码规范工具)、Sonarqube(代码质量管理的开源工具)

此外,你要保证你的测试用例跑的足够快,否则你会没有耐心等待测试运行结果,或者直接就不运行了。

理想情况下,程序在构建后部署到测试环境前,可以借助 CI/CD(持续集成/持续部署)工具实现代码质量检查、代码样式检查、潜在 bug 监测等模块的自动化运行。

最简单的重构

一、重命名

好的命名能传达准确的信息,如果你发现命名不能传达准确的信息,那么就修改它。

二、提取重复代码

你以为你已经修复问题了,但后面发现问题还在,就是因为有很多重复代码,导致其他地方调用的方法并没有修改。

三、提炼函数

一个函数过长会显得十分复杂,方法和类都应该遵守单一职责原则。

书籍推荐

鲍勃大叔编写的《代码整洁之道》

马丁·福勒(Martin Fowler)编写的《重构(Refactoring)》

“出来混迟早是要还的”

推荐网站:

"guru"icon-default.png?t=N7T8https://link.zhihu.com/?target=https%3A//refactoringguru.cn/refactoring/catalog

  • 17
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值