函数重构几乎都是源自于Long Methods。这导致了函数包含的信息过多,信息带来的逻辑错综复杂。
1 Extract Method(提炼函数)
<b>Summary:</b>将一段代码放进一个独立函数中,并让函数名称解释该函数的用途。
<b>Motivation:</b>将过长的函数分割成独立的函数,改善代码清晰度。
<b>Routine:</b>
- 创造一个新函数,并根据意图命名;
- 将提炼出的代码从源函数复制到新建的目标函数中;
- 仔细检查提炼其中是否有源函数作用域的变量。
- 检查是否有“仅用于被提炼代码段”的临时变量,有则在被提炼函数中将之声明位临时变量。
- 检查被提炼代码段,看看是否任何局部变量的值被他改变。如果有临时变量的值被改变了,看看是否可将被提炼代码段提炼位一个查询,并将结果赋值给相关变量。如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动地连出来。可能需要用到Split Temporary Variable,然后再尝试提炼。也可以使用Replace Temp with Query。
- 被提炼代码段中需要读取的局部变量,当做参数传给目标函数。
- 替代
2 Inline Method
<b>Summary</b>:再函数调用点插入函数本体,然后移除该函数。
<b>Motivation</b>:某些函数其内部结构和函数名同样清晰易读,也肯能是重构之后使其内容和名称变得同样清晰;手底下一些错误拆分的函数内联到一个大型函数中,再重新从中提炼小函数;中间层级过于多,导致了函数只是一个其余函数的代理。这三类是需要使用内联手法进行refacting。
<b>Routine</b>:
- 检查函数,确定函数不具有多态性。
- 找出函数的所有调用点。
- 替换所有调用点的函数为其本体。
- 编译,测试。
- 删除函数定义。
3 Inline Temp 和 Replace Temp With Query
3.1 InlineTemp
<b>Summary:</b>将所有对该变量的引用动作替换位对它赋值的那个表达式自身。
<b>Motivation:</b>一般是作为Replace Temp with Query的一部分使用的,所以真正的动机是后者。唯一单独使用Line Temp的情况,某个临时变量被赋予某个函数调用的返回值,并阻碍了其他的重构手法。
<b>Routine:</b>
- 检查给临时变量赋值的语句,确保等号右侧无副作用。
- 如果这个临时变量未被声明位final,那就将它声明位final编译,从而验证赋值状况。
- 找到所有调用点并替换。
- 编译测试。
- 删除赋值语句和临时变量。编译测试。
3.2 Replace Temp With Query(以查询代替临时变量)
Summary:将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换位对新函数的调用。此后,新函数就可以被其他函数调用。
double basePrice =_quantity * _itemPrice;
if(basePrice>1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
……
double basePrice(){
return _quantity * _itemPrice;
}
<b>Motivation:</b>临时变量是暂时的,只能在所属函数内使用。由于临时变量只在所属函数内可见,所以它们会驱使你写更长的函数。如果把临时变量替换位一个查询,那么同一个类中的所有函数都将可以获得这份信息。这将带来极大的帮助,写除清晰的代码。
<b>Routine:</b>
- 找出只被赋值一次的临时变量(如果赋值超过一次,可以考虑Split Temporary Variable)。
- 将临时变量声明位final。
- 编译。
- 提炼等号右侧部分到一个独立函数中。
- 编译,测试。
- 在该临时变量身上使用Inline Temp。
4 Introduce Explaining Variable(引入解释性变量)
<b>Summary:</b>将复杂表达式(或者其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
<b>Motivation:</b>在条件逻辑中,判定条件往往相当复杂,引入解释性变量来对应其意义来明晰代码逻辑;另外,在较长的算法中,可以运用临时变量来解释每一步运算的意义。
<b>Routine:</b>
- 声明一个final临时变量,将待分解之复杂表达式中的一部分动作的运算结果赋值给它。
- 将运算结果的相应部分替换位上述变量。
- 编译,测试。
- 重复上述过程。
5 Split Temporary Variable(分解临时变量)
<b>Summary:</b>针对每次赋值,创造一个独立,对应的临时变量。
<b>Motivation:</b>临时变量有各种不同用途,其中某些用途会很自然地导致临时变量被多次赋值。循环变量和循环收集变量就是其中的典型例子。除了这两种情况,还有很多临时变量用于保存一段冗长代码的运算结果,以便稍后使用。这种临时变量应该只有一次赋值,赋值多次就代表这临时变量承担多个责任,就应该被分解替换。
<b>Routine:</b>
- 在待分解变量的声明及其第一次被赋值处,修改其名称。(如果是结果收集变量和循环变量则不要分裂)
- 将新的临时变量声明位final。
- 以该临时变量的第二次赋值动作位界,修改此前对该临时变量的所有引用点,让它们引用新的临时变量。
- 在第二次赋值处,重新声明原先那个临时变量。
- 编译,测试。
- 重复执行上述过程。
6 Remove Assignments to Parameters(移除对参数的赋值)
<b>Summary:</b>以一个临时变量取代该参数的位置。
<b>Motivation:</b>这会降低代码的清晰度,会让人搞不清楚是参数的值域被修改还是新的被指向的值域被修改。
<b>Routinue:</b>
- 建立一个临时变量,把待处理的参数值赋予给它。
- 以此位置为界,将其后所有对此参数的引用点,全部替换为此临时变量的引用。
- 修改赋值语句,使其改为对新建值临时变量的赋值。
- 编译,测试。
7 Replace Method with Method Object(以函数对象取代函数)
<b>Summary:</b>将函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后可以在一个对象中将大型函数切分位多个小型函数。
<b>Motivation:</b>如果一个函数之中局部变量泛滥成灾,那么像分解这个函数是非常困难的,当Replace Temp with Query无法更笨上拆解一个函数时,应该用对象。
<b>Routinue:</b>
- 建立一个新类,根据职责命名。
- 在新类中建立一个final字段,泳衣保存原先大型函数所在对象,针对原函数的每个临时变量和参数,在新类中建立对应的字段保存。
- 在新类的构造函数传入源对象和所有参数。
- 在新类中建立函数,在函数中粘贴进源代码。
- 编译。
- 将旧函数的函数本体替换位创建一个新对象,并调用相应函。
8 Substitute Algorithm(替换算法)
<b>Summary:</b>把某个算法替换位更清晰的算法。
<b>Motivation:</b>替换更好或者更易修改的函数。
<b>Routinue:</b>
- 准备好替换算法。
- 执行新算法。
- 以旧算法位标准修正新算法。