跟狗屎一样的代码,到底该如何重构?
阅读本文大概需要 18 分钟。
来自:网络,侵删
一、重构原则
1、何谓重构
2、为何重构
良好的设计是快速开发的根本
,改善设计、提高可读性,减少错误,这些都是提高质量。3、何时重构
重构应该随时随地的进行。
第一次做某件事情是只管去做;第二次做类似的事情会产生反感;第三次再做类似的事,你就应该重构
计算机科学是这样一门科学:它相信所有的问题都可以通过增加一个间接层来解决。
二、代码的坏味道
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);
7、移除对参数的赋值
以一个临时变量取代该参数的位置。
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);
}
8、引入本地扩展
建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。
五、重新组织数据
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、以继承取代委托
评论