【《重构 改善既有代码的设计》学习笔记6】重新组织函数

本篇文章的内容来自《重构 改善既有代码的设计》一书学习笔记整理并且加上自己的浅显的思考总结!重构手法中,很大一部分是对函数进行整理,使之更恰当地包装代码。重新组织函数对过长的函数进行拆解,提炼函数,并处理局部变量,使得拆解后的函数更加清晰并且能够更好的工作。1、提炼函数(Extract Method)概要你有一段代码可以被组织在一起并独立起来。 将这段代码放进一个独立函数中,并让函…

大家好,又见面了,我是全栈君。

本篇文章的内容来自《重构 改善既有代码的设计》一书学习笔记整理并且加上自己的浅显的思考总结!

重构手法中,很大一部分是对函数进行整理,使之更恰当地包装代码。

重新组织函数

对过长的函数进行拆解,提炼函数,并处理局部变量,使得拆解后的函数更加清晰并且能够更好的工作。

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博客和关注的个人微信公众号!

愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人。

【《重构 改善既有代码的设计》学习笔记6】重新组织函数

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

© 每天都在变得更好的阿飞

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/121049.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Xshell 连接linux主机

    Xshell 连接linux主机0 前言使用 Xshell 连接远程服务器 文件 新建 出现如下图标 主机即为需要连接的 Linux 服务器的 ip 地址 端口号为 22 无须修改 但需要确认远程服务器的 22 端口已经打开 点击左边的 用户身份验证 输入用户名和密码 点击确认后 即可连接 总体流程 就是这个样子 问题就在于 ip 地址 用户名 密码怎么填写 下面几个章节就展示了如何查看远程 Linux 服务器的 ip 用户名和密码等 1 查看 ip 地址查看 ip 地址使用命令 ifconfig 确保能够 ping 通在连接之前 需要确保本地能够 p

    2025年7月11日
    3
  • Java 变量命名规则[通俗易懂]

    Java 变量命名规则[通俗易懂]变量命名必须满足一系列的条件,不能随意命名示例1:命名规则变量命名只能使用:字母数字$_ 变量第一个字符只能使用:字母$_ 变量第一个字符不能使用:数字 注:_是下划线,不是-减号或者——破折号inta=5;inta_12=5;int$a43=5;inta434=5;//第一个是数字,是不行的int34a=5;示例…

    2025年6月21日
    2
  • 博科SAN交换机zone配置(华为SNS系列交换机为例OEM博科)[通俗易懂]

    博科SAN交换机zone配置(华为SNS系列交换机为例OEM博科)[通俗易懂]一、zone的定义Zone是Brocade交换机上的标准功能,FCSWITCH上的Zone功能类似于以太网交换机上的VLAN功能,它是将连接在SAN网络中的设备(主机和存储),逻辑上划到为不同的区域内,使得不同区域中的设备相互间不能FC网络直接访问,从而实现网络中的设备之间的相互隔离。二、Zone的功能包含以下两点:1.防止主机节点访问未经授权的存储。Zone中的设备只能访问同一Zone中连接到Fabric的其它设备。不在Zone中的设备不能被Fabric中的其他设备访问。2.隔离不必要状态

    2022年5月21日
    361
  • MATLAB学习笔记 plotyy双y轴

    MATLAB学习笔记 plotyy双y轴一、线型设置:t=0:0.1:8;[ax,h1,h2]=plotyy(t,sin(t),t,cos(t));% plotyy(X1,Y1,X2,Y2):以左、右不同纵轴绘制X1-Y1、X2-Y2两条曲线。set(h1,’linestyle’,’-‘,’marker’,’o’,’color’,’r’);set(h2,’linestyle’,’:’,’marker’,’x’,’color’…

    2022年6月17日
    28
  • Oracle-Oracle数据库备份与恢复

    Oracle-Oracle数据库备份与恢复Oracle数据库备份与恢复下面通过一些简单的例子来了解一下:Oracle数据库各种物理备份的方法。Oracle数据库各种物理恢复的方法利用RMAN工具进行数据库的备份与恢复。数据的导出与导入操作。(1)关闭BOOKSALES数据库,进行一次完全冷备份。selectfile_namefromdba_data_files;selectmemberfromv…

    2022年5月14日
    42
  • Android图片裁剪之自由裁剪

    Android图片裁剪之自由裁剪

    2021年12月4日
    39

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号