其他
聊聊重构那些事
点击上方 Java后端,选择 设为星标
优质文章,及时送达
(一)重构原则
1、何谓重构
对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
另一种解释是:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
重构不止是代码整理,它提供了一种高效且受控的代码整理技术
2、为何重构
改进软件设计:如果没有重构,程序的设计会逐渐变质,重构很像是在整理代码,你所做的就是让所有的东西回到应处的位置上。 帮助找到bug:对代码进行重构,可以深入理解代码的作为,在搞清楚程序结构的同时,想不把bug揪出来都难。 提高编程速度: 良好的设计是快速开发的根本
,改善设计、提高可读性,减少错误,这些都是提高质量。
3、何时重构
重构应该随时随地的进行。
第一次做某件事情是只管去做;第二次做类似的事情会产生反感;第三次再做类似的事,你就应该重构
最常见的重构时机是想给软件添加新特性的时候;
修改错误的时候 review代码的时重构
计算机科学是这样一门科学:它相信所有的问题都可以通过增加一个间接层来解决。
(二)代码的坏味道
1、重复代码
同一个类中有相同的表达式:提炼出重复的代码,然后让两个地方都调用被提炼出来的那一段代码; 两个互为兄弟的子类内含有相同的表达式:提炼出相同代码,将它推入超类内; 两个毫不相干的类中出现:将重复的代码提炼到一个独立的类中。
2、过长的类
拥有短函数的对象活得比较好、比较长。
间接层所能带来的全部利益——解释能力、共享能力、选择能力——都是由小型函数支持的。3、过大的类
4、过长参数列
5、发散式变化
6、散弹式修改
7、依恋情结
将数据和对数据的操作行为包装在一起
函数对某个类的兴趣高过对自己所处类的兴趣。
某个函数为了计算某个值,从另一个对象那调用几乎半打的取值函数。判断哪个类拥有最大被此函数使用的数据,然后就把这个函数和那些数据放在一起。
8、数据泥团
9、基本类型偏执
它们模糊了横旦与基本数据和体积较大的类之间的界限
10、switch惊悚现身
少用switch语句
11、平行集成体系
让一个继承体系的实例引用另一个继承体系的实例。
12、冗余类
13、夸夸其谈未来性
14、令人迷惑的暂时字段
15、过度耦合消息链
16、中间人
17、狎昵关系
18、异曲同工的类
19、不完美的类库
如果只想修改类的一两个函数,可以引入外加函数。 如果想要添加一大堆额外行为,建立一个新类包含这些额外行为,让其成为子类。
20、纯稚的数据类
21、被拒绝的遗赠
22、过多的注释
当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
(三)重新组织函数
1、提炼函数
创造一个新函数,根据这个函数的意图来命名它; 只要新函数的名称能够以更好的方式昭示代码意图,你也应该提炼它。但如果想不到一个更有意义的名称就别动 将提炼的代码从原函数复制到新建的目标函数中; 将被提炼代码段中需要读取的局部变量,当作参数传递给目标函数; 在源函数中,将被提炼代码段替换为目标函数调用。
2、内联函数
一群组织不甚合理的函数。你可以将它们都内联到一个大函数中,再从中提炼出组织合理的小型函数。 使用的太多的间接层,使得系统中的所有函数都似乎只是对另一个函数的简单委托,造成在委托动作之间晕头转向。
检查函数,确定不具备多态; 如果子类继承了这个函数,就不要将此函数内联,因为子类无法复写一个根本不存在的函数。 找出这个函数的所有调用点; 将这个函数的所有调用点都替换成函数本体。
3、内联临时变量
将所有对该变量的引用动作,替换为对它赋值的那个表达式自身
double basePrice = anOrder.basePrice();
return (base > 10000 );
return (anOrder.basePrice > 1000);
4、以查询取代临时变量
将这个表达式提炼到一个独立的函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。
double basePrice = quantity * timePrice;
if(basePrice > 1000){
return basePrice * 09.5;
} else {
return basePrice * 0.98;
}
if(basePrice() > 1000){
return basePrice * 09.5;
} else {
return basePrice * 0.98;
}
double basePrice(){
return quantity * timePrice;
}
5、引入注释性变量
将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
if ((platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize >0){
//do smothing
}
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize >0;
if(isMacOs && isIEBrowser && wasInitialized() && wasResized){
//do smothing
}
在条件逻辑中,你可以用这项重构将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。另一种情况是:在较长的算法中,可以运用临时变量来解释每一步运算的意义。
6、分解临时变量
针对每次赋值,创造一个独立、对应的临时变量。
double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);
double perimeter = 2 * (height + width);
System.out.println(perimeter);
double area = height * width;
System.out.println(area);
以一个临时变量取代该参数的位置。
int discount (int inputVal, int quantity, int yearToData){
if(inputVal > 50) inputVal -= 2;
}
int discount (int inputVal, int quantity, int yearToData){
int result = inputVal;
if(inputVal > 50) result -= 2;
}
8、替换算法
将函数本体替换成为另一个算法。
String foundPerson(String[] people){
for(int i = 0;i < people.length; i++){
if(people[i].equals("Don")){
return "Don";
}
if(people[i].equals("John")){
return "John";
}
if(people[i].equals("Kent")){
return "Kent";
}
}
return "";
}
String foundPerson(String[] people){
List candidates = Arrays.asList(new String[]{"Don", "John", "Kent"});
for(int i = 0;i < people.length; i++){
if(candidates.contains(people[i])){
return prople[i];
}
}
return "";
}
决定把责任放在哪儿
是即使不是最重要的事,也是最重要的事之一。常常只使用
搬移函数
和搬移字段
简单地移动对象行为,就可以解决这些问题。如果这两个重构手法都需要用到,我会首先使用搬移字段
,再使用搬移方法
。如果一个类承担了太多责任而变得臃肿不堪,这种情况下会使用
提炼类
将一部分责任分离出去。如果一个类变得太不负责任,使用将类内联化
将它融入到另一个类中。1、搬移函数
在该函数最长引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或者将旧函数完全移除。
如果一个类有太多行为,或如果一个类与另一个类有太多合作而高度耦合,就需要搬移函数。可以是系统中的类更简单
2、搬移字段
在目标类新建一个字段,修改原字段的所有用户,令他们改用新字段
3、提炼类
建立一个新类,将相关字段和函数从就类搬到新类。
4、将类内联化
将这个类的所有特性搬移到另一个类中,然后移除原类。
5、隐藏“委托关系”
在服务类上建立客户所需要的所有函数,用来隐藏委托关系。
封装意味每个对象都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的对象就会比较少。
如果某个客户先通过服务对象的字段得到另一个对象,然后调用后者的函数。那么客户就必须知晓这一层委托关系。万一委托关系变化,客户也要相应变化。
6、移除中间人
让客户直接调用委托类。
每当客户要使用手委托类的新特性时,你就必须在服务端添加一个简单委托函数。随着受委托类的特性越来越多,这一过程会让你很痛苦。
7、引入外加函数
在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。
Date newStart = new Date(year, month, date + 1);
Date newStart = nextDay(nowDate);
private static Date nextDay(Date arg){
retrun new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。
(五)重新组织数据
1、自封装字段
为这个字段建立取值/设值函数,并且只以这些函数来访问字段。
private int low, high;
boolean includes(int arg){
retrun arg >= low && arg <= high;
}
private int low, high;
boolean includes(int arg){
retrun arg >= getLow() && arg <= getHigh();
}
int getLow(){
retrun low;
}
int getHigh(){
return high;
}
在该变量定义所在的类中,你可以自由的访问。 即使在这个类中你也应该只使用访问函数间接访问。
间接访问的好处是:子类可以通过复写一个函数而改变获取数据的途径;它支持更灵活的数据管理方式,例如延迟初始化。
2、以对象取代数据值
将数据项变为对象。
一开始你肯能会用一个字符串来表示“电话号码”概念,但是随后你会发现,电话号码需要“格式化”、“区号”之类的行为。这时候就需要为带替换的数值新建一个类。
3、将值对象改为引用对象
将这个值对象变成引用对象。
4、将引用对象改为值对象
将它变成一个值对象。
5、以对象取代数组
以对象替换数组。对于数组中的每个元素,以一个字段来表示
6、复制“被监视数据”
将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
7、将单向关联改为双向关联
添加一个反向指针,并使修改函数能够同时更新两条连接。
8、将双向关联改为单向关联
去除不必要的关联。
9、以字面常量取代魔数
创造一个常量,根据其意义为它命名,并将上述的字面数值替换为常量。
10、封装字段
将它声明为private,并提供相应的访问函数。
11、封装集合
让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
(六)简化条件表达式
1、分解条件表达式
从if、then、else三个段落中分别提炼出独立函数。
2、合并表达式
将这些测试合并为一个条件表达式,并将这个条件表达式提炼成一个独立函数。
3、合并重复的条件代码
将这段重复代码搬移到条件表达式之外。
4、移除控制标记
以break/return语句取代控制标记。
5、以多态取代条件表达式
将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数
(七)简化函数调用
1、函数改名
修改函数名称。
2、添加参数
为此函数添加一个对象参数,让该对象带仅函数所需信息。
3、移除参数
去除参数。
4、分离查询函数和修改函数
建立两个不同函数,其中一个负责查询,另一个负责修改。
5、令函数携带参数
建立单一函数,以参数表达那些不同的值。
有这样两个函数:它们做着类似的工作,但因少数几个值致使行为略有不同。在这种情况下,你可以将这些各自分离的函数同一起来,并通过参数来处理那些变化情况,用以简化问题。
6、以明确函数取代参数
针对该参数的每一个可能值,建立一个独立函数。
如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同参数值做出不同的行为,那么就应该使用本项重构。
7、保持对象完整
改为传递整个对象。
8、以函数取代参数
让参数接受者去除该参数,直接调用前一个函数。
9、引入参数对象
以一个对象取代这些参数。
10、移除设值函数
去掉该字段的所有设值函数。
11、隐藏函数
将函数修改为private。
12 、以工厂函数取代构造函数
将构造函数替换为工厂函数。
(八)处理概括关系
1、字段上移
将该字段移至超类。
2 、函数上移
将该函数移至超类。
3 、构造函数本体上移
在超类中新建一个构造函数,并在子类构造函数中调用它。
4、函数下移
将函数移到相关的子类中。
5、字段下移
将字段移到需要它的子类中。
6、提炼子类
新建一个子类,将上述部分的特性移到子类中。
7、提炼超类
为这两个类建立一个超类,将相同特性移至超类。
8、提炼接口
将相同的子集提炼到一个独立接口中。
9、折叠继承体系
将它们合为一体。
10、塑造模板函数
将操作放进独立函数(保持签名相同),然后将它们移至超类。
11、以委托取代继承
子类新建字段保存超类,调整子类函数为委托超类,取消继承关系。
12、以继承取代委托
链接:https://www.jianshu.com/p/3f04b6aebad2