重构:改善既有代码设计——第六章笔记

最近读了《重构:改善代码既有设计》这本经典的重构书,平常靠“感觉”的重构被作者整理成一个个小技巧,在这里加以总结,也方便以后查阅。

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拆分阶段

动机:当看到一段代码同时处理两件不同的事情,可以拆分为各自独立的模块,每次可以处理单独的主题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值