6、重新组织你的函数
目的:处理 Long Methods(过长函数)。
6.1 提炼函数(Extract Method)
描述:
你有㆒段代码可以被组织在㆒起并独立出来。将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。
判断:
看见㆒ 个过长的函数或者㆒ 段需要注释才能让人理解用途的代码
原因:
1、小函数被复用的机会更大。
2、每个小函数的名称都准确描述其功能,使大函数更容易理解,就像读注释。
3、小函数更容易被override
注意点:
函数名:关键在于函数名称和函数本体之间的语义距离,而与长短无关。
函数长度:哪怕提炼出的函数只有一句,也可以。
难点:
1、对局部变量再赋值( Reassigning)
这个变量只在被提炼码区段㆗ 使用。果真如此,你可以将这个临时变量的声明式移到被提炼码㆗ ,然后㆒ 起提炼出去。
被提炼码之外的代码也使用了这个变量。这又分为两种情况:
如果这个变量在被提炼码之后未再被使用,你只需直接在目标函数㆗ 修改它就可以了;
如果被提炼码之后的代码还使用了这个变量,你就需要让目标函数返回该变量改变后的值。
如果返回的变量不止一个,尽量避免这种情况。
2、临时变量往往为数众多,甚至会使提炼工作举步维艰。
先运用 Replace Temp with Query( 120)减少临时变量。
动用 Replace Method with Method Object( 135)
6.2 函数内联化(Inline Method)
描述:
㆒ 个函数,其本体( method body)应该与其名称( method name)同样清楚易懂。
在函数调用点插入函数本体,然后移除该函数。
质疑:
我认为做这个重构动作的用途不大,除非是跟其他重构方法配套使用。
更好的一个场景是:如果一个函数原本提炼的不太好,可以把这个函数删掉,代码还原回调用函数,然后重新提炼一个新的函数。
6.3 临时变量内联化(Inline Temp)
描述:
如果临时变量只被㆒ 个简单表达式赋值㆒ 次,而它妨碍了其它重构手法。把临时变量去掉,直接用表达式。
原因:
为了 : Replace Temp with Query
技巧:
如何判断临时变量只被赋值了一次:声明为final,还能通过编译。
6.4 查询函数代替临时变量(Replace Temp with Query)
描述:
你的程序以㆒ 个临时变量( temp)保存某㆒ 表达式的运算结果。
将这个表达式提炼到一个独立函数中。将这个临时变量的所有「被引用点」替换为「对新函数的调用」。
注: 这一般用于这个临时变量在后面被用于不同的分支,而不是在同一分支中反复使用。
临时变量只被赋值㆒ 次,或者赋值给临时变量的表达式不受其它条件影响。
举例:
double getPrice() {
int basePrice = _quantity * _itemPrice;
double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
重构后:
double getPrice() {
return basePrice() * discountFactor();
}
private double discountFactor() {
if (basePrice() > 1000) return 0.95;
else return 0.98;
}
private int basePrice() {
return _quantity * _itemPrice;
}
6.5 解释型变量 (Introduce Explaining Variable)
描述:
你有㆒ 个复杂的表达式。 将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
动机:
易理解。
在条件逻辑( conditional logic) ㆗ , Introduce Explaining Variable( 124)特别有价值:你可以用这项重构将每个条件子句提炼出来,以㆒ 个良好命名的临时变量来解释对应条件子句的意义。
使用这项重构的另㆒ 种情况是,在较长算法㆗ ,可以运用临时变量来解释每㆒ 步运算的意义。
局限性:
毕竟临时变量只在它所处的那个函数㆗ 才有意义,局限性较大,函数则可以在对象的整个生命㆗ 都有用,并且可被其它对象使用。
所以更推荐用函数。
6.6 拆分临时变量(Split Temporary Variable)
描述:
你的程序有某个临时变量被赋值超过㆒ 次,它既不是循环变量,也不是㆒ 个集用临时变量( collecting temporary variable)。
针对每次赋值,创造一个独立的、对应的临时变量。
动机:
如果它们被赋值超过㆒ 次,就意味它们在函数㆗ 承担了㆒ 个以㆖ 的责任。如果临时变量承担多个责任,它就应该被替换(剖解)为多个临时变量,每个变量只承担㆒ 个责任。
6.7 不对入参赋值(Remove Assignments to Parameters)
描述:
你的代码对㆒ 个参数进行赋值动作。 以一个临时变量取代该参数的位置。
注:这里不是说不允许改入参的某个属性,而不不允许把入参指向另外一个对象。新建一个临时变量即可。
原因:
使代码难以理解,且容易误解。
6.8、Replace Method with Method Object(以函数对象取代函数)
描述:
你有一个大型函数,其中对局部变量的使用,使你无法釆用 Extract Method。
将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的值域(field) 然后你可以在同一个对象中将这个大型函数分解为数个小型函数。
动机:
一个函数特别复杂,且由于参数过多上面的方法都无法处理,那么就写一个类来包装这个函数,把参数转成这个类的变量,类似于策略模式的做法。
6.9、Substitute Algorithm(替换你的算法)
描述:
确实有更好的实现能够替代现在的实现,那么换一种实现方式。
注意:
1、要充分读懂以前算法的含义
2、要有足够的测试。
6.10、原作者总结
我的重构手法中,很大一部分是对函数进行整理,使之更恰当地包装代码。
几乎所有时刻,问题都源于Long Method(过长函数)。
这很讨厌,因为它们往往包含太多信息,这些信息又被函数错综复杂的逻辑掩盖,不易鉴别。
对付过长函数,一项重要的重构手法就是Extract Method,它把一段代码从原先函数中提取出 来,放进一个单独函数中。
Inline Method 正好相反:将一个函数调用动作替 换为该函数本体。
如果在进行多次提炼之后,意识到提炼所得的某些函数并没有做任何实质事情,或如果需要回溯恢复原先函数,我就需要Inline Method。
Extract Method 最大的困难就是处理局部变量,而临时变量则是其中一个主要的困难源头。
处理一个函数时,我喜欢运用Replace Temp with Query 去掉所有可去掉的临时变量。
如果很多地方使用了某个临时变量,我就会先运用Split Temporary Variable 将它变得比较容易替换。
但有时候临时变量实在太混乱,难以替换。这时候我就需要使用Replace Method with Method Object。它让我可以分解哪怕最混乱的函数,代价则是引入一 个新class。
参数带来的问题比临时变量稍微少一些,前提是你不在函数内赋值给它们。如果你已经这样做了,就得使用Remove Assignments to Parameters。
函数分解完毕后,我就可以知道如何让它工作得更好。也许我还会发现算法可以改进,从而使代码更清晰。这时我就使用Substitute Algorithm 引入更清晰的算法。
6.11、我的总结
目的:处理过长的函数。
处理手法1:提炼函数。
如果不好提炼,一般是由于局部变量引起的,那么先用这几个方法后,再提炼:1、函数内联化,2、临时变量内联化,3、查询函数代替临时变量,4、拆分临时变量。
如果仍然不好提炼,那么可以考虑用函数对象替代函数。
处理手法2:在不提取函数的情况下,提升代码的可读性的方法:1、引入解释型变量,2、不对入参赋值,3、替换算法。