这个,我经常发现做开发的同事的代码,出现这个问题。
"但是让小函数容易理解的真正关键在于一个好名字。如果你能给函数起个好名字,读者就可以通过名字了解函数的作用,根本不必去看其中写了些什么。“
-----------起个好名字,看名字知道函数的作用。在需要的时候,比如,调试,检查Bug,这时候,才需要查看。
”最终的效果是:你应该更积极地分解函数。我们遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。我们可以对一组甚至短短一行代码做这件事情。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地那么做。关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。“
----------按照这条原则写代码,分解函数。又学习了一条先进的经验,函数的名称要说明函数的用途。
”如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。如果你尝试运用Extract Method手法,最终就会把许多参数和临时变量当作参数,传递给被提炼出来的新函数,导致可读性几乎没有任何提升。此时,你可以经常运用Replace Temp With Query手法来消除这些临时元素。Introduce Parameter Object和Preserve Whole Object则可以将过长的参数列变得更简洁一些。“
使用场景:一组参数总是被一起传递,作为好几个函数的参数。这一组参数,叫作数据泥团(Data Clumps)。这时就可以使用这个重构手法,缩短参数列表。长的参数列表,导致函数难以理解。
实现的大概步骤:
1.识别出上述的场景,一组参数总是被一起传递,作为函数的参数。
2.创建一个新的类,类的名称描述这组参数的含义。它是一个不可变类。
里面的成员都是final类型的,成员就是引入的参数。
3.为新建类中的每个成员创建get方法。
4.逐步使用新建类的成员替换函数参数。
5.然后,检查函数中的操作,看哪些操作,可以提炼到参数对象中,作为参数对应的行为。
可以提炼到参数对象中,作为参数对象的行为的操作,这一组操作具备如下特征:
1).这组操作,使用到的参数都是参数对象中的成员。
2).这组操作的用途可以用一个动作名称来表达。这个动作名称就是参数对象行为名称了。
效果例子:
getFlowBetween(Date start, Date end)-------Introduce Parameter Object手法---->getFlowBetween(Date range)。
Preserve Whole Object(保持对象完整)
该操作手法的含义:
int low = daysTempRange()*getLow();
int high = daysTempRange()*getHigh();
withinPlan = plan.withinRange(low, high);
|
|应用Perserve Whole Object手法
|
withinPlan = plan.withinRange(daysTempRange());
带来的好处:
1.假设,某个时候,函数withinRange的需要的参数改变了,比如,需要使用某个对象中的三个参数,而不是某个对象中的两个参数。那么,如果之前没有使用该重构,那么,就要逐个修改该函数的被调用点。
2.使函数的参数列表变短。
缺点:
“如果你传的是数值,被调用函数就只依赖于这些数值,而不依赖它们所属的对象。但如果你传递的是整个对象,被调用函数所在的对象就需要依赖参数对象。如果这会使你的依赖结构恶化,那么就不应该使用Preserve Whole Object手法”。
其它方面的认识:
“如果被调用函数使用了来自另一个对象的很多项数据,这可能意味着该函数实际上应该被定义在那些数据所属的对象中。所以,考虑Preserve Whole Object的同时,你也应该考虑Move Method。”
实现步骤:
0.识别出哪些参数是属于某个对象,或者是可以统一作为某个对象的成员。
1.如果函数参数,并不是属于某个对象的,则要使用Introduce Parameter Object手法,创建一个新的对象来包含该组参数。
2.将引用该参数的地方,替换为引用新建对象的取值函数。
通常做完之后,会发现,可以把被调用函数的操作,提炼为参数对象的行为,即参数对象的一个方法,函数。
比如:
class HeatingPlan:
boolean withinRange(TempRange roomRange){
return (roomRange.getLow() >= _range.getLow() && roomRange.getHigh() <= _range.getHigh());
}
-------->
-----------起个好名字,看名字知道函数的作用。在需要的时候,比如,调试,检查Bug,这时候,才需要查看。
---------------------使函数参数列表变短------------------------------------------------------------------------------------
Introduce Parameter Object(引入参数对象)使用场景:一组参数总是被一起传递,作为好几个函数的参数。这一组参数,叫作数据泥团(Data Clumps)。这时就可以使用这个重构手法,缩短参数列表。长的参数列表,导致函数难以理解。
1.识别出上述的场景,一组参数总是被一起传递,作为函数的参数。
2.创建一个新的类,类的名称描述这组参数的含义。它是一个不可变类。
里面的成员都是final类型的,成员就是引入的参数。
3.为新建类中的每个成员创建get方法。
4.逐步使用新建类的成员替换函数参数。
可以提炼到参数对象中,作为参数对象的行为的操作,这一组操作具备如下特征:
1).这组操作,使用到的参数都是参数对象中的成员。
2).这组操作的用途可以用一个动作名称来表达。这个动作名称就是参数对象行为名称了。
getFlowBetween(Date start, Date end)-------Introduce Parameter Object手法---->getFlowBetween(Date range)。
该操作手法的含义:
int low = daysTempRange()*getLow();
int high = daysTempRange()*getHigh();
withinPlan = plan.withinRange(low, high);
|
|应用Perserve Whole Object手法
|
withinPlan = plan.withinRange(daysTempRange());
1.假设,某个时候,函数withinRange的需要的参数改变了,比如,需要使用某个对象中的三个参数,而不是某个对象中的两个参数。那么,如果之前没有使用该重构,那么,就要逐个修改该函数的被调用点。
2.使函数的参数列表变短。
“如果你传的是数值,被调用函数就只依赖于这些数值,而不依赖它们所属的对象。但如果你传递的是整个对象,被调用函数所在的对象就需要依赖参数对象。如果这会使你的依赖结构恶化,那么就不应该使用Preserve Whole Object手法”。
“如果被调用函数使用了来自另一个对象的很多项数据,这可能意味着该函数实际上应该被定义在那些数据所属的对象中。所以,考虑Preserve Whole Object的同时,你也应该考虑Move Method。”
0.识别出哪些参数是属于某个对象,或者是可以统一作为某个对象的成员。
1.如果函数参数,并不是属于某个对象的,则要使用Introduce Parameter Object手法,创建一个新的对象来包含该组参数。
比如:
class HeatingPlan:
boolean withinRange(TempRange roomRange){
---------------------使函数参数列表变短------------------------------------------------------------------------------------
-------------------消除函数内的临时变量,从而使函数体变短-------------------------------------------------------
Replace Temp With Query(以查询取代临时变量)看一个具体的例子:
double getPrice(){
int basePrice = _quantity * _itemPrice;
double discountFactor;
if ( basePrice > 1000 )
discountFactor = 0.95;
else
discountFactor = 0.98;
return basePrice * discountFactor;
}
应用Replace Temp With Query(以查询取代临时变量)的手法,消除basePrice, discountFactor这两个临时变量
效果如下:
double getPrice(){
return basePrice()*discountFactor();
}
实现过程:
1.找出只被赋值一次的临时变量。
--->如果某个临时变量被赋值超过一次,考虑使用Split Temporary Variable将它分割成多个变量。一个临时变量只应当承担一个责任,即是只应当被赋值一次,除了累加器变量和索引变量。
2.将该临时变量声明为final。
3.编译。
这个确保该临时变量的确只被赋值一次。
4.将”对该临时变量赋值“之语句的等号右侧部分提炼到一个独立函数中。
--->首先将函数声明为private。日后你可能会发现有更多类需要使用它,那时放松对它的保护也很容易。
--->确保提炼出来的函数无任何副作用,也就是说该函数并不修改任何对象内容。如果它有副作用,就对它进行Separate
Query From Modifler手法。一个函数,要是是查询,也就是返回一个值;要么就是修改对象内容。不能让一个函数有两个功能,这会让函数代码难以阅读,容易出错。
5.编译测试。
6.在该临时变量身上使用Inline Temp手法。
-------------------消除函数内的临时变量,从而使函数体变短-------------------------------------------------------
如何确定该提炼函数体中的哪一段代码?
分两种信号:
1.寻找注释。如果代码前方有一行注释,就是在提醒你:可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数去。
2.函数题中的条件表达式和循环也是提炼的信号。要将循环提炼到一个独立函数中,这样可以使原有的函数体变小。对于函数体中的条件表达式,则使用Decompose Conditional手法来处理,目的也是使原有的函数体变小。