/**
@startTime 2020-12-20 15:30
@endTime 2020-12-20 21:00
@startPage 103
@endPage 130
@efficiency 130/5 = 26页/天
@needDays 412/26 = 16天
@overDay 2020-12-16 + 16天 = 2020-12-31
*/
第5~12章构成了一份重构列表草案,其中所列的重构手法来自作者数年的编程心得。
不要盲目的进行查找-替换,应该检查每一个引用点,确保它的确指向你想要替换的东西。
可以让编译器帮助你捕捉引用点,但是这也会存在一些问题,比如:
小步前进、频繁测试。
设计模式,为重构提供了目标。
模式和重构之间有着一种与生俱来的关系,模式是你希望达到的目标,重构则是到达之路。
我的重构手法中,很大一部分是对函数进行整理,使之更恰当地包装代码。
将一段代码放进一个独立函数中,并让函数名称解释该函数的用途。
「动机」
在函数调用点插入函数本体,然后移除该函数。
将所有对该变量的引用动作,替换为对它赋值那个表达式本身。
将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。
将复杂表达式的结果放进一个临时变量,以此变量名称来解释表达式用途。
「动机」
在条件逻辑中,引入解释性变量特别有价值,你可以用这项重构将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。使用这项重构的另一种情况是,在较长算法中,可以运用临时变量来解释每一步运算的意义。
针对每次赋值,创造一个独立、对应的临时变量。
「动机」
如果临时变量被赋值超过一次,就意味着它们在函数中承担了一个以上的责任。如果临时变量承担多个责任,它就应该被替换为多个临时变量,每个变量只承担一个责任。同一个临时变量承担两件不同的事情,会另代码阅读者糊涂。
/**
@startTime 2020-12-21 21:30
@endTime 2020-12-21 23:50
@startPage 131
@endPage 188
@efficiency 188/6 = 31.3页/天
@needDays 412/31.3 = 13天
@overDay 2020-12-16 + 13天 = 2020-12-28
*/
以一个临时变量取代该参数的位置。
首先,我要确定大家都清楚“对参数赋值”这个说法的意思。如果你把一个名为user的对象作为参数传给某个函数,那么“对参数赋值”意味着改变user,使它引用另外一个对象。如果你在“被传入对象”身上进行什么操作,那没问题,我也总是这样干。我只针对“user被改而指向另一个对象”这种情况来讨论。
你有一个大型函数,其中对局部变量的使用使你无法采用提取函数,将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段,然后你可以在同一个对象中将这个大型函数分解为多个小型函数。
「动机」
我在本书中不断向读者强调小型函数的优美动人。只要将相对独立的代码从大型函数中提炼出来,就可以大大提高代码的可读性。
「做法」
将函数本体替换为另一种算法。
你的程序中,有个函数与其所在类之外的另一个类进行更多的交流,在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托关系,或是将旧函数完全移除。
“搬移函数”是重构理论的支柱。如果一个类有太多行为,或如果一个类与另一个类有太多合作而形成高度耦合,我就会搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。
某个类做了应该由两个类做的事,建立一个新类,将相关的字段和函数从旧类搬移至新类。
某个类没有做太多的事情,将这个类的所有特性搬移至另一个类中,然后移除该类。
客户端通过一个委托类来调用另一个对象时,在服务类上建立客户所需的所有函数,用以隐藏委托关系。
public Person getManager(){
return _department.getManager();
}
只有完成了对Department所有函数的委托关系,并相应修改了Person的所有客户,我就可以移除Person中的访问函数getDepartment()了。
某个类做了过多的简单委托动作时,让客户直接调用受委托类。
Date start = new Date(previous.getYear(),previous.getMonth(),previous.getDate()+1);
变为
Date start = nextDay(previous);
private static Date nextDay(previous.getYear(),previous.getMonth(),previous.getDate()+1);
貌似懂了一点。
外加函数终归是权宜之计,如果有可能,仍然应该讲这些函数搬移至它们应该在的地方。
你需要为服务类提供一些额外函数,但你无法修改这个类,建立一个新类,使它包含这些额外函数,让这个扩展类成为原类的子类或包装类。
「动机」
在子类和包装类之间做选择时,我通常首选子类,因为这样的工作量比较少。制作子类的最大障碍在于,如果原数据是可修改的,一个修改动作无法改变两个副本,这时就必须改用包装类
「做法」
为字段建立取值/设置函数,并且只以这些函数来访问字段。
你有一个数据项,需要与其它数据和行为一起使用才有意义,将数据项变成对象。
你有一个引用对象,很小且不可变,而且不易管理,此时,将它变成一个值对象。
要在引用对象和值对象之间做出选择,有时并不容易。做出选择后,你常会需要一条回头路。
如果引用对象开始变得难以使用,也许就应该将它变为值对象。引用对象必须被某种方式控制,你总是必须向其控制者请求适当的引用对象。它们可能造成内存区域之间错综复杂的关联。在分布式系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题。
以对象取代数组,对于数组中的每一个元素,以一个字段来表示。
「动机」
数组是一种常见的用以组织数据的结构。不过,它们应该只用于“以某种顺序容纳一组相似对象”。有时你会发现,一个数组容纳了多种不同对象,这会给用户带来麻烦,因为它们很难记住像“数组的第一个元素是人名”这样的约定。对象就不同了,你可以运用字段名称和函数名称来传达这样的信息,也无需使用注释。而且使用对象,你还可以将信息封装起来,并使用“搬移函数”将它加上相关行为。
我之前做过一个数据上传系统,有这么个结构:
Map<String,String[]> hashMap = new HashMap<String,String[]>();
String[] data = new Data[4];
data[0] = "1";//状态state
data[1] = "1";//延展状态exstate
data[2] = "1";//数值
data[3] = "2020-12-21 23:22:00";//数据生成时间
String key = "01A01";//测点编号
hashMap.put(key,data);
...
for(String key : hashMap.keySet){
//状态state、延展状态exstate、数值、数据生成时间
String[] data = hashMap.get(key);
String state = data[0];
String exstate = data[1];
String value = data[2];
String time = data[3]'
}
因为数据存在基本数据、实时数据、分钟累计数据,这种结构的代码项目中比比皆是,写的那叫一个烂,这个上传程序折磨的我苦不堪言,往事不堪回首。
「做法」
@Data
public class BasicData(){
private int state;
private int exstate;
private double value;
private Date time;
}
Map<String,BasicData> hashMap = new HashMap<String,BasicData>();
BasicData basicData = new BasicData();
basicData.setState = "1";
basicData.setExstate = "1";
basicData.setValue = 3.12;
String str = "2020-12-21 23:22:00";
basicData.setTime = DateUtil.StringToDate(str,"yyyy-MM-dd hh:mm:ss");
String key = "01A01";//测点编号
hashMap.put(key,basicData);
...
for(String key : hashMap.keySet){
//状态state、延展状态exstate、数值、数据生成时间
BasicData basicData = hashMap.get(key);
String state = basicData.getState();
String exstate = basicData.getExstate();
double value = basicData.getValue();
Date time = basicData.getTime();
}
多么痛的领悟。
其实很简单。
/**
@startTime 2020-12-22 21:30
@endTime 2020-12-22 22:50
@startPage 189
@endPage 235
@efficiency 235/7 = 33.6页/天
@needDays 412/33.6 = 12天
@overDay 2020-12-16 + 12天 = 2020-12-27
*/
你有一些领域数据置身于GUI控件中,而领域函数需要访问这些数据。将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
「动机」
一个分层良好的系统,应该讲处理用户界面和处理业务逻辑的代码分开。之所以这样做,原因有以下几点:
两个类需要使用对方特性,但其间只有一条单向连接。
添加一个反向指针,并使修改函数能够同时更新两条连接。
去掉不必要的关联。
你有一个字面数值,带有特殊的含义。创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。
将字段封装为private,并提供相应的访问函数。
记录型结构是许多编程环境的共同性质,你可能处理一些遗留程序,也可能需要通过传统API与记录结构交流,或者是从数据读出的记录。这些时候你就有必要创建一个接口类,用以处理这些外来数据。将这些数据搬移到Java的bean中。
借助多态处理变化行为。
你有一个类型码,它会影响类的行为,但你无法通过继承手法消除它。此时,就可以以状态对象取代类型码。
你的各个子类的唯一差别就只在“返回常量数据”的函数身上,修改这些函数,使它们返回超类中的某个新增字段,然后销毁子类。
上一篇:《重构 改善既有代码的设计 1》重构原则
下一篇:【编写高质量代码:改善Java程序的151个建议】