最近读了《重构:改善代码既有设计》这本经典的重构书,平常靠“感觉”的重构被作者整理成一个个小技巧,在这里加以总结,也方便以后查阅。
6.1提炼函数
动机:将意图与实现分开,通过函数名可以看出其用途
情况:
- 无局部变量
- 有局部变量:通过函数参数传递
- 对局部变量再赋值:
- 若只有一个值,采用返回值返回。
- 若有多个值,可以返回一个自定义的结构,也可以用多个函数
- 更好的方法还有:查询取代临时变量和拆分变量
思考:编译器优化让我们不用过于担心性能问题,即使是只有几行的代码,也是值得抽取的
6.2内联函数
动机:若函数内部代码简短,且可读性与函数名称差不多,可以直接内联
若函数组织不合理导致有很多小函数,可以先内联再重构
情况:
//重构前
int ratring(Driver driver) {
return moreThanFiveLateDeliveries(driver)? 2 : 1;
}
boolean moreThanFiveLateDeliveries(Driver driver) {
return driver.numberOfLateDeliveries>5;
}
//重构后
int ratring(Driver driver) {
return driver.numberOfLateDeliveries>5? 2 : 1;
}
6.3提炼变量
动机:引入局部变量,将表达式分解为更易理解的形式。
情况:
-
位于一个函数中
//重构前 int price(Order order) { return order.quanity * order.itemPrice - Math.max(0,order.quantity-500)*order.itemPrice *0.05 + Math.min(order.quantity * order.itempPrice*0.01 , 100); } //重构后 int price(Order order) { int basePrice = order.quanity * order.itemPrice; int quantityDiscount = Math.max(0,order.quantity-500)*order.itemPrice *0.05; int shipping = Math.min(order.quantity * order.itempPrice*0.01 , 100); return basePrice - quantityDiscount + shipping; }
-
位于一个类中 Order.java
int getQuantity() {return this.quantity}; ... int getBasePrice() { return this.quantity * this.itemPrice;}; ... int getPrice() { return getBasePrice() - getQuantityDiscount() + getShipping();}
6.4内联变量
动机:若表达式本身比变量名更能表达意义,应选择内联变量
tips:可先将变量变为不可修改,确保变量只被赋值一次,然后再继续重构
//重构前
int basePrice = order.basePrice;
return basePrice > 1000
//重构后
return order.basePrice > 100;
6.5改变函数声明
动机:一个好的函数名可以一眼看出函数的用途,而不必查看其代码而好的参数列表也可以描述好上下文
情况:
-
简单做法
//重构前 double circum(double redius){ return 2*Math.PI*redius; } //重构后 double circumference(double redius){ return 2*Math.PI*redius; } //并将所有调用者改名——问题:必须一次修改所有调用者
-
迁移式做法
//重构前 double circum(double redius){ return 2*Math.PI*redius; } //重构后 double circum(double redius){ return circumference(redius); } double circumference(double redius){ return 2*Math.PI*redius; }
-
添加参数
//重构前 void addResevation(Customer customer) { this.stack.push(customer); } //重构后 void addResevation(Customer customer) { this.zz_addResevation(customer , false); } void zz_addResevation(Customer customer, Boolean isPriority) { this.stack.push(customer); } //之后再修改调用方,最后再把函数改回原来的名字(参考上方简单做法或者迁移式做法)
-
把参数改为属性
若参数传入一个对象,而函数却只用到对象的一个熟悉,可以改为直接传入属性
6.6封装变量
动机:函数可以改成旧函数调用新函数作为转发,而数据却不可用,重构难度较大,所以最好的办法就是以函数封装对数据的访问,把问题从“重新组织数据”转化为“重新组织函数”
此外,封装函数也利于监控数据修改和使用情况,因为都是调用同一个函数
方法:为变量编写set和get函数
6.7变量改名
动机:好的变量名可以描述好作用
6.8引入参数对象
动机:一组经常一起出现的数据称为数据泥团,可以将这一组数据封装成一个数据对象
这项重构的意义在于:催生代码中更深层次的改变的改变,一旦识别出新的数据对象,就可以抽取出围绕这个数据对象的一组函数,从而与数据结合,作为一个新的抽象概念(可以理解为一个有独立行为的类)
6.9函数组合成类
是引入参数对象的进一步引申,即识别出新的数据对象,就可以抽取出围绕这个数据对象的一组函数,从而与数据结合,作为一个新的抽象概念(可以理解为一个有独立行为的类)
//假设有一个数据对象如下
reading = {customer : "ivan", quantity : 10, month:5, year 2017};
//=========重构前==============
//客户端1
Reading aReading = acquireReading();
int baseCharge = baseRate(aReading.month,aReading.year)*aReading.quantity;
//客户端2
Reading aReading = acquireReading();
int base = (baseRate(aReading.month,aReading.year)*aReading.quantity)*aReading.quantity;
int textableCharge = Math.max(0,base - taxThreadHold(aReading.year));
//客户端3
Reading aReading = acquireReading();
int basicChargeAmount = calculateBaseCharge(aReading);
int calculateBaseCharge(Reading aReading){
return baseRate(aReading.month,aReding.year)*aReding.quantity;
}
//=====重构过程=======
//可以将客户端1、2改为调用客户端3的函数,但是其他作者并不一定知道客户端3中有该函数
//所以不如直接抽取出一个新的类,专门负责处理相关内容
class CalcuReading{
Reading aReading;
CalcuReading(Reading reading){
this.aReading = reading;
}
int calculateBaseCharge(){
return baseRate(aReading.month,aReding.year)*aReding.quantity;
}
}
6.10函数组合成变换
动机:我们经常把A数据喂给函数,再由A数据计算出其他派生数据,与其这样,不如将计算派生数据的逻辑收拢到一处,这样可以在固定的地方找到和更新逻辑,这样其实与函数组合成类有点类似。不同的是:使用变换,派生数据会被存储在新生成的记录中。
//假设有一个数据对象如下
reading = {customer : "ivan", quantity : 10, month:5, year 2017};
//=========重构前==============
//客户端1
Reading aReading = acquireReading();
int basicChargeAmount = calculateBaseCharge(aReading);
int calculateBaseCharge(Reading aReading) {
return baseRate(aReading.month,aReding.year)*aReding.quantity;
}
//=====重构后========
//创建一个变换函数,输入是原始的读书,而输出是增强的读数
Reading enhanceReading(Reading reading) {
Reading result = reading.coty();
result.baseCharge = calculateBaseCharge(reading);
return result;
}
//客户端使用
Reading aReading = acquireReading();
aReading = enhanceReading(reading);
int basicChargeAmount = aReading.baseCharge;
6.11拆分阶段
动机:当看到一段代码同时处理两件不同的事情,可以拆分为各自独立的模块,每次可以处理单独的主题。