2022-07-05
第六章 重新组织函数
6.1提炼函数
看见过长的函数或需要注释才能使人理解的代码,标志着需要提炼。将函数细粒度的切分为多个独立函数,函数被复用的机会越大,对函数的覆写也更容易。另外,切分出的独立函数取一个对功能描述准确的名字以增强可读性。不要害怕函数名太长,哪怕函数名比提炼出的代码还要长也没关系。
步骤
- 创建一个新函数,以他的功能命名
如果不能概括他的功能那就别动他 - 从源函数复制代码
- 检查提炼出的代码是否引用了作用域仅限于源代码的变量
作为参数传给提炼的函数 - 检查是否有仅限于提炼代码使用的变量,将其声明在提炼出的函数中
- 检查提炼函数是否改变了某些局部变量
如果后面用到了被改变的变量,可以将作为它返回值。
如果需要返回的不是一个值,那尽可能再拆分为多个有单一返回值的函数 - 编译测试
临时变量太多往往使提炼变得举步维艰,所以提炼之前尽量先减少临时变量数量
6.2内联函数
以下情况需要用到内联函数
1.有些小函数,他的代码读起来和函数名一样清晰易读,这个时候可以不作为独立函数
2.有一堆组织不合理的小函数,可以将其组成一个大函数,然后重新拆分为组织合理的小函数
步骤
- 检查函数,确定它不具备多态性
- 找出调用这个函数的点
- 将调用的地方换成函数体
- 编译测试
- 删除函数定义
如果做的时候不像上面说的这么轻松,遇到了很多难以处理的问题(如递归等)。就说明不应该用这种手法重构
6.3内联临时变量
临时变量往往是用来替换某个查询值。临时变量的存在往往会影响重构
唯一需要使用临时变量的情况是临时变量被赋予一个函数返回值,这种基本没有什么危害。
步骤
- 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用
- 如果临时变量没有被声明为final,则声明为final然后编译(可以确定临时变量是否真的只被赋值一次)
- 找到引用点,替换为赋值时的表达式
- 每次修改后编译测试
- 删除临时变量声明和赋值语句
- 编译测试
6.4以查询取代临时变量
将临时变量赋值提炼为一个有返回值的函数
临时变量的问题在于:他们是暂时的,且只能在所属函数内使用。导致所在函数变长。把临时变量替换为查询,使得同一个类中所有函数都可以使用。在提炼函数之前往往需要这么做。
步骤
- 找出只赋值一次的临时变量
- 声明为final
- 编译
- 将对临时变量赋值的语句等号右边提炼到一个独立函数中
可以先声明为private如果将来还有其他人用,再修改也很容易
确保提炼出来的函数没有副作用(修改对象的内容) - 编译测试
- 将临时变量改为内联临时变量
6.5引入解释性变量
将复杂表达式的结构放进一个临时变量,以变量名解释用途
这个看起来与消除临时变量的理念相悖,所以要确定遇到的情形需要用到这个重构手法。
对于提取复杂表达式的重构手法更推荐使用提炼函数,如果算法拥有大量局部变量,直接提取函数很难。这时就需要先用引入解释性变量清理代码,搞清楚算法逻辑后再用以查询取代临时变量的手法重构。
步骤
- 声明一个final变量,将复杂表达式结果赋给它
- 将用到复杂表达式的地方用临时变量替换
- 编译测试
6.6分解临时变量
如果一个临时变量被赋值了多次,表示它承担了多个责任此时它应该被分解为多个变量。因为同一个变量承担多个责任会使代码阅读者糊涂
步骤
- 在第一次赋值处修改名称,声明为final
- 用新的变量替换第二次赋值之前对其的引用
- 在第二次赋值处重新声明临时变量
- 重复
6.7移除对参数的赋值
对于按值传递的情况,对参数的任何修改都不会影响原值,在函数中修改参数会使代码变得难以阅读,所以需要修改时将参数赋给一个临时变量,需要返回时,将临时变量返回即可。
步骤
- 建立一个临时变量,把要处理的参数赋给它
- 将对参数的引用修改为对临时变量的引用
- 修改赋值语句为给临时变量赋值
- 编译测试
6.8以函数对象取代函数
对于一些需要拆解但是由于局部变量太多导致难以拆解的可以使用这种方式
步骤
- 建立一个新类,以拆解出的函数功能为这个类命名
- 类中新建一个final字段用以存放原函数所在类,用来访问原函数所在类的字段
- 新类中建一个构造函数接受源对象,及原函数所有参数,及局部变量
- 新类中建一个compute函数
- compute函数中复制原函数代码,需要使用源对象属性和局部变量都更改为使用新对象的属性
- 修改原函数代码,改为创建新类的一个对象而后调用compute函数
- 编译,测试
这个步骤过后可以在这个新类里轻松拆解compute函数,所有访问临时变量可以改为访问类属性
6.9替换算法
对于现有算法,如果有更清晰的解决方式,就应该用更清晰的方式取代复杂的方式
这么做的前提是已经充分理解了原函数
步骤
- 准备好一个替换算法让其通过编译
- 测试新算法和原函数结果相同
- 替换原函数