重构和测试
要正确地进行重构,前提是得有一套稳固的测试集合,以帮助开发人员发现难以避免的疏漏。
重构名录
-
提炼函数
动机:如果需要花费时间浏览一段代码才能弄清它的作用,就应该将其提炼到一个函数中。以至于再浏览这个函数时,很清楚该函数的作用,不需要关心函数是如何实现的。
做法:- 创建一个新函数,根据这个函数的意图来命名(以它“做什么”来命名,而不是“怎样做”命名)。
- 将待提炼的代码从源函数复制到新建的函数中。
- 检查提炼代码中是否引用了作用域限于源函数、在新函数中无法访问的变量。如果有,就以参数的形式传递给新函数。
- 将源函数中被提炼的代码段替换成对目标函数的调用。
- 测试。
-
内联函数
动机:- 某个函数内部代码和函数名称清晰易读,但是又重构了该函数的内部实现。
- 代码中有太多间接层,使得系统中的所有函数似乎只是对另一个函数的简单委托(过度的重构)。
做法:
- 检查函数,确定它不具有多态性。
- 找出这个函数的所有调用点。
- 将这个函数的所有调用点都替换成函数本体。
- 测试。
- 删除该函数的定义。
-
提炼变量
动机:表达式有可能非常复杂而难以阅读。这种情况下,使用提取变量给表达式命名。在一个类中,最好将其提炼成方法。
做法:- 确认要提炼的表达式没有副作用。
- 声明一个不可修改的变量,复制要提炼的表达式并将该表达式的结果值给变量赋值。在一个类中声明一个函数,将表达式的值返回。
- 用新变量取代原来的表达式。
- 测试。
-
内联变量
动机:有时候变量名并不比表达式本身更具有表现力,有时候甚至会妨碍重构附近的代码。这时候直接使用表达式更好。
做法:- 检查确认变量赋值语句的右侧表达式没有副作用。
- 如果变量没有被声明为不可修改,先将其变为不可修改,测试。
- 找到第一处使用改变量的地方,将其替换为直接使用赋值语句的右侧表达式。
- 测试。
- 重复前两步,逐一替换其他所有使用改变量的地方。
- 删除声明点和赋值语句。
- 测试。
-
改变函数声明
动机:意义不明的函数名或参数列表。
做法:- 简单做法
- 如果要移除一个参数,需要先确定函数体内没有使用该参数。
- 修改函数声明,使其成为你期望的状态。
- 将旧函数声明的地方,改成新的函数声明。
- 测试。
- 迁移式做法
- 如果有必要,先对函数体内部加以重构,是后面的提炼步骤易于展开。
- 使用提炼函数将函数体提炼成一个新函数。
- 如果提炼出的函数需要新增参数,用前面的简单做法添加即可。
- 测试。
- 对旧函数使用内联函数。
- 如果新函数使用了临时的名字,再次使用改变函数声明将其改为原来的名字。
- 测试。
- 简单做法
-
封装变量
动机:如果要更改数据,就必须同时修改所有引用该数据的代码,否则程序就不能运行。如果数据的可访问范围较小,那还不成问题。如果数据的可访问范围很大,重构难度会随之增大。封装数据能够提供一个清晰的观测点,可以由此监控数据的变化和使用情况。
做法:- 创建封装函数,在其中访问和更新变量值。
- 执行静态检查。
- 注意修改使用该变量的代码,将其改为调用合适的封装函数。每次替换之后,执行测试。
- 限制变量的可见性。
- 测试。
-
变量改名
动机:意义不明的变量名。使用范围越广,命名的好坏就越重要。
做法:- 如果变量被广泛使用,考虑运用封装变量将其封装起来。
- 找出所有使用该变量的代码,逐一修改。
- 测试。
-
引入参数对象
动机:一组数据总是一起出现,将数据组织成结构会让数据项之间的关系变得清晰明了。并且使用数据结构,参数列表也能缩短。
做法:- 创建一个合适的数据结构,测试。
- 使用改变函数声明给原来的函数新增一个参数,类型是新建的数据结构,测试。
- 调整所有调用者,传入新数据结构的适当实例。每修改一处,执行测试。
- 用新数据结构中的每项元素,逐一取代参数列表中与之对应的参数项,然后删除原来的参数,测试。
-
函数组合成类
动机:如果一组函数形影不离地操作同一块数据(通常是将这块数据作为参数传递给函数),就可以组件一个类。类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用。
做法:- 运用封装记录对多个函数共用的数据记录加以封装。
- 对于使用该记录结构的每个函数,将其移入新类中。
- 处理该数据记录的逻辑可以用提炼函数提炼出来,并移入新类。
-
拆分阶段
动机:一段代码在同时处理两件不同的事情,就可以将其拆分成各自独立的模块。
做法:- 将第二阶段的代码提炼成独立的函数,测试。
- 引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中,测试。
- 逐一检查提炼出的“第二阶段函数”的每个参数。如果某个参数被第一阶段用到,就将其移入中转数据结构,测试。
- 对第一阶段的代码运用提炼函数,让提炼的函数返回中转数据结构。