大家好,又见面了,我是全栈君。
重构手法中,很大一部分是对函数进行整理,使之更恰当地包装代码。
重新组织函数
对过长的函数进行拆解,提炼函数,并处理局部变量,使得拆解后的函数更加清晰并且能够更好的工作。
1、提炼函数(Extract Method)
概要
你有一段代码可以被组织在一起并独立起来。 将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。
void print(){
printBanner();
System.out.print("name:"+ _name);
System.out.print("age:"+ age);
}
// 修改后
void print(){
printBanner();
printDetail();
}
void printDetail(){
System.out.print("name:"+ _name);
System.out.print("age:"+ age);
}
动机
提炼函数是最常用的手法之一。将一段独立的代码进行抽离并放入一个独立的函数中,并给这个函数起一个简短而命名良好的名字,这种提炼也可以方便后续的代码的复用。
一个函数多长才算合适?
长度不是问题,关键在于函数名称和函数本体之间的语义距离。
做法
♢创建一个函数,根据这个函数的意图来对它命名。(“做什么”命名,而不是“怎么做”命名)
♢将提炼出的代码从源函数复制新建目标函数中
♢检查是否有需要处理的临时变量
♢处理完成,编译
♢在源函数中,调用提炼的目标函数,编译、测试
【这个重构使用较多,希望一定要能够掌握】
范例
- 无局部变量
比较简单,剪切、粘贴,插入一个函数即可。
- 有局部变量
局部变量:包括传入源函数的参数和源函数所申明的临时变量。 **局部变量的作用域仅限于 源函数。**所以需要额外去处理这些变量。
局部变量最简单的情况:被提炼的函数段只是读取这些变量的值,并不修改它们。这种情况可以简单传参数到目标函数。
如果局部变量是个对象,也可以进行参数传递。但是一定要注意,目标函数是否会对对象赋值.
【对象当作参数传递,要了解按值传递和按引用传递的概念和相关的知识】
- 对局部变量在赋值
♢ 变量只是单纯的初始值,可以在新函数中进行初始化。
♢ 变量如果有其他处理,必须要将它的值作为参数传给目标函数。
如果需要返回的变量不止一个,又该怎么办?
(1) 、提炼另一块代码,每个函数只返回一个值。要安排多个函数,用以返回多个值。
(2)、使用传递对象的方式进行
2、内联函数(Inline Method)
概要
一个函数的本体与名称同样清晰易懂。在函数调用点插入函数本体,然后移除该函数。
【减少函数】
int getRating(){
return (getLargeSize()) ? 2 : 1 ;
}
boolean getLargeSize(){
return _largeSize > 5;
}
// 修改后
int getRating(){
return (_largeSize > 5) ? 2 : 1 ;
}
动机
使用内部代码和函数名称同样清晰易懂,此时就应该去掉这个函数。间接性可能带来帮助,但是非必要的间接性总是让人不舒服。
太多的间接层,使得系统中所有的函数似乎只是对另一个函数的简单委托。使用内联手法,找出那些有用的间接层,同时去掉无用的间接层。
做法
♢ 检查函数,确定它不具有多态性(如果子类继承了这个函数,就不要将此函数内联,因为子类无法复写一个不存在的函数)【此问题目前IDE就可以帮助检查】
♢ 找出函数的所有的被调用点
♢ 将这个函数的所有被调用点替换为函数本体
♢ 编译、测试
♢删除该函数的定义
【内联函数 在真是的使用中可能不是那么容易,就如上面概要中的那个代码例子】,如果有十个函数使用getLargeSize()
,然后将这个函数使用内联,那么带来一个问题,要修改判断条件 ,使用_largeSize > 10
,那就要修改内联后所有函数,而不是只修改一个地方了。
使用的时候还是要仔细判断,谨慎选择。
3、内联临时变量(Inline Temp)
概要
你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。
将所有对该变量的引用动作,替换为对它赋值的那个表达式
double basePrice = order.basePrice();
return (basePrice > 100)
// 修改后
return (order.basePrice() > 100)
动机
唯一单独使用内联临时变量
情况是:发现某个临时变量被赋予某个函数调用的返回值。 一般来说,如果临时变量不妨碍其他重构手法,留在那儿就行,妨碍了(如影响提炼函数
)就应该将它内联化。
做法
♢ 检查该临时变量时候真的只被 赋值一次。
【我一般在代码中如果一个临时变量的赋值表达式多次使用,我会定义一个临时变量,其他地方使用这个临时变量】,例如:
void getPrice(){
if(order.basePrice()){
// do something
}
System.out.print("basePrice :" + order.basePrice());
//这个函数 还有地方也使用 order.basePrice()
}
// 修改后
void getPrice(){
double basePrice = order.basePrice();
if(basePrice){
// do something
}
System.out.print("basePrice :" + basePrice);
//这个函数 还有地方也使用 basePrice
}
4、以查询取代临时变量(Replace Temp with Query)
概要
你的程序以一个临时变量保存某一次的运算结果。将这个表达式提炼到一个独立的函数中,将这个临时变量的所有的引用点替换为新函数的调用。
double basePrice = _quantity * _itemPrice;
if(basePrice > 100){
return basePrice * 0.95;
}else{
return basePrice * 0.98;
}
// 修改后
if(basePrice() > 100){
return basePrice() * 0.95;
}else{
return basePrice() * 0.98;
}
double basePrice(){
return _quantity * _itemPrice;
}
可以思考一下这样的一个操作有什么好处?
【此重构手法和内联函数的重构手法进行对比,思考这些重构手法什么场景使用!】
动机
临时变量的问题在于:它们都是暂时的,而且只能在所属函数内部使用。 如果把临时变量替换为一个查询,那么同一个类都将可以访问获取这份信息。使得类编写更清晰的代码。
做法
♢ 找出只被赋值一次的临时变量。
♢ 将该临时变量申明为final (确保没有地方去修改这个临时变量)
上面例子的代码,不知道你是否仔细思考,这样的改动可以会带来性能问题,这里先不考虑它造成的性能的问题。
范例
5、引入解释性变量(Introduce Explaining Variable)
概述
你有一个复杂的表达式。将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式的用途。
if(platform.toUpperCase().indexOf("MAC") > -1 &&
browser,toUpperCase().indexOf("IF") > -1 &&
wasInitialized() && resize > 0) {
// do something
}
// 修改后
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrower = browser,toUpperCase().indexOf("IF") > -1;
final boolean wasResized = resize > 0;
if(isMacOs && isIEBrower && wasInitialized() && wasResized){
//do something
}
动机
表达式有可能非常复杂而难以阅读。这种情况下,临时变量可以帮助你将表达式分解为比较容易管理的形式。
【阿里巴巴的java规范插件会有相应的校验提示,建议java开发的同学都装一个阿里巴巴Java开发规约插件p3c】
在条件逻辑中,引用解释性变量
特别有价值: 将每个复杂的条件字句提炼出来,以一个良好命名的临时变量来解释对应的条件字句。
如果要解释一段代码的意义,尽量使用 提炼函数
。当局部变量使 提炼函数
难以进行的时候,可以使用 引用解释型变量
。
做法
♢ 声明一个final临时变量 ,将待分解复杂表述式的一部分动作的运算结果赋值给它。
♢ 将表达式 中的 “运算结果” 替换为临时变量
♢ 编译、测试
范例
- 引入解释型变量 范例
- 提炼函数 范例
上面这种情况,两种重构手法都可以使用,那么到底应该在什么时候使用 引用解释型变量
呢?
在 引入 提炼函数
需要花费更大工作量时。也即 如果要处理的是一个拥有大量局部变量的算法,使用提炼函数
绝非易事。
6、分解临时变量(Split Temporary Varibale)
概要
程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。
针对每次赋值,创造一个独立、对应的临时变量。
double temp = 2 * (height + width);
System.out.print(temp);
temp = height * width;
System.out.print(temp);
// 修改为
final double perimeter = 2 * (height + width);
System.out.print(perimeter);
final double area = height * width;
System.out.print(area);
动机
临时变量有各种不同的用途,其中某些用途很自然地导致临时变量被多次赋值。 除 “循环变量”和“结果收集变量([i = i +1],i即为结果赋值变量)”这两种情况外,很多临时变量 用于保存一段冗长的代码的运算结果,以便稍后使用,这种临时变量应该只被赋值一次。 同一个临时变量承担两件不同的事情,会令代码阅读者糊涂。
做法
♢在待分解临时变量的声明及其第一次赋值处,修改其名称。
♢将新的临时变量声明为final
♢在改临时变量第二次赋值的时候在修改,按照首次的套路
♢ 重复上述,编译、测试
范例
7、移除对参数的赋值(Remove Assignmets to Parameters)
概要
对一个参数进行赋值。以一个临时变量取代该参数的位置。
int discount (int inputVal, int quantity ,int yearToDate){
if(inputVal > 50){
return inputVal -= 2;
}
}
// 修改后
int discount (int inputVal, int quantity ,int yearToDate){
int result = inputVal;
if(inputVal > 50){
return result -= 2;
}
}
动机
除非你对“对参数赋值” 非常清楚,也即你非常清楚 java的按值传递和按引用传递。
【在真实的项目代码,很多业务中,一个对象被当作参数在几个方法处理和修改】
做法
♢ 建立一个临时变量,把待处理的参数值赋予它。
♢修改其后所有的引用点为临时变量
♢ 编译、测试
使用的时候一定要注意 按引用传递 参数情况
范例
按值传递和按引用传递 : 如果理解底层的话本质都是按值传! 基本类型拷贝原值,引用类型是拷贝引用的地址(也是值)!
8、以函数对象取代函数(Replace Method with Method Object)
概要
你又一个大型的函数,其中对局部变量的使用使得你无法采用 提炼函数
。
将这个函数放进一个单独的对象中,如此依赖局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小函数。
动机
本书不断强调 小型函数的优美动人。只要将相对独立的代码从大型函数中提炼出来,就可以大大提高代码的可读性。
做法
♢ 新建一个类,根据处理函数用途命名这个类。
♢ 在新类中创建字段和源函数中每个临时变量对应
♢ 新类中建立构造函数,接受原函数所有参数
♢在新类中建立 compute() 函数,将源函数的代码拷贝到compute()中
♢ 编译、测试
范例
9、替换算法(Substitute Algorithm)
概要
你想要某个算法替换为另一个更清晰的算法。 将函数本体替换为另一个算法。
String foundPerson(String[] people){
if(int i = 0; i < people.length; i++){
if("Don".equals(people[i])){
return "Don";
}
if("Jack".equals(people[i])){
return "Jack";
}
//.....
}
}
// 修改后
String foundPerson(String[] people){
List candidates = Arrays.asList(new String {
"Don","Jack"});
if(int i = 0; i < people.length; i++){
if(candidates.contains(people[i])){
return people[i];
}
}
}
动机
解决一个问题一般会有好几种方法,某些方法会比另一些简单。算法也是如此。找到一个更清晰的方式取得复杂的方式。
替换一个巨大而且赋值的算法是非常困难的,只有将它分解为较简单的小型函数,然后在进行算法的替换工作。
做法
♢ 准备好新的算法,替换之前旧的算法,编译、测试
对于每个测试用例,分别以新旧两种算法执行,观察结果是否相同,这可以帮助你看到哪一个测试用例 出现麻烦,以及出现怎么样的麻烦。
总结
本章学习了函数的重构,很多都是和日常开发紧密相关的,从做一件小事的细节看出一个人的品质,从小的代码编写细节,看出一个coder的功底。代码的编写可以持续的精进,从小的细节写出好的代码,脚踏实地,不要好高骛远,眼高手低。做一个好的coder,做好自己的工作。
笔记中有很多重构手法的做法没有书中全,我是按照自己的理解进行整理,如需看详细内容请阅读本书第六章。
如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!
如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!
欢迎访问我的csdn博客和关注的个人微信公众号!
愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人。
不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!
博客首页 : http://blog.csdn.net/u010648555
© 每天都在变得更好的阿飞
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/121049.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...