重构(笔记)——第十章

643 阅读5分钟

简化函数调用

函数改名

将复杂的处理过程分解成小函数。

private String getTelphoneNumber() {
    return getTelphoneNumber();
}

private String getOfficeTelphoneNumber() {
    return _officeAreaCode + officeNumber(); 
}

函数改名是一件比较简单的事情,这里有一个注意的地方。我们不可能一次性把所有的调用地方全部修改完,那么此时的做法是:

  • 先将旧函数复制一份,然后修改函数名。
  • 旧函数里面调用新函数。
  • 再找到所有调用旧函数的地方,修改成调用新函数。
  • 全部修改完成后,再
  • 将旧函数直接删除。

添加参数

修改一个函数,为这个函数参加一个参数。

《重构》作者认为,这不是一种好的重构手法,增加参加就意味着要增加复杂度。如果有其他选择,尽量不要增加参数。

移除参数

与上面的重构动作相反,去除调用函数中多余的参数。

将查询函数和修改函数分离

某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,其中一个负责查询,另一个负责修改。

String foundMiscreant(String[] people) {
    for (int i = 0; i < people.length; i++) {
        if (people[i].equals("Don")) {
            sendAlert();
            return "Don";
        }
        if (pople[i].equals("John")) {
            sendAlert();
            reuturn "John";
        }
    }
    return "";
}

void checkSecurity(String[] people) {
    String found = foundMiscreant(people);
    someLaterCode(found);
}

// 重构后

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";
        }
    }
    return "";
}

void foundMiscreant(String[] people) {
    for (int i = 0; i < people.length; i++) {
        if (people[i].equals("Don")) {
            sendAlert();
        	return ;
        }
        if (people[i].equals("John")) {
            sendAlert();
            return ;
        }
    }
}

void checkSecurity(String[] people) {
    foundMiscreant(people);
    String found = foundPerson(people);
    someLaterCode(found);
}

令函数携带参数

两个具有相同行为的函数,只是少数几个值导致的行为不同,这种情况下,可以将其统一起来,通过参数来处理变化情况。

在平时写代码的过程中,这也是我们最常用的做法。

// 书中的示例
class Employee {
    void temPercentRasise() {
        salary *= 1.1;
    }
    
    void fivePercentRasise() {
        salary *= 1.05;
    }
    
    // 重构后
    void raise (double factor) {
        salary *= (1 + factory);
    }
}

以明确函数取代参数

与上面的重构手法相反,一个函数行为取决于函数,将其进行拆分,分别建立独立的函数。

void setValues(String name, int value) {
    if (name.equals("height")) {
        _height = value;
        return;
    }
    if (name.equals("width")) {
        _width = value;
        return;
    }
    Assert.shouldNaverReachHere();
}

// 重构后

void setHeight(int arg) {
    _height = arg;
}

void setWidth(int arg) {
    _width = arg;
}

另外一个示例

static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;

static Employee create(int type) {
    swithc (type) {
        case ENGINEER:
        	 return new Engineer();
        case SALESMAN:
        	return new Salesman();
        case MANAGER:
        	return new Manager();
        default:
        	throw new IllegalArgumentException():
    }
}

// 有函数调用的时候会做出如下调用 

Employee kent = Epmloyee.create(ENGINEER);
Employee jhon = Emplyee.create(MANAGER);

// 重构后

static Employee createEngineer() {
    return new Engineer();
}

static Employee createSalesman() {
    return new Salesman();
}

static Employee createManager() {
    return new Manager();
}

static Employee create(int type) {
    swithc (type) {
        case ENGINEER:
        	 return Employee.createEngineer();
        case SALESMAN:
        	return Employee.createSalesman();
        case MANAGER:
        	return Employee.createManager();
        default:
        	throw new IllegalArgumentException():
    }
}

// 在调用 create 的地方,进行修改
//调用前
Employee kent = Employee.createEngineer();
// 当所有地方都修改完成后,再将 create 方法删除。同时删除对应常量

保持对象完整

传递参数的过程中,传递多个参数,不如将参数封装起来,直接传递这个对象。

// 书中的示例
int low = daysTempRange().getLow();
int high = daysTempRange().getHigh();
withinPlan = plan.withinRange(low, high);

// 上面的示例,将 low 和 high 做为函数的参数,重构后,将 daysTemRange() 整个参数传递到函数中
// 重构后
withinPlan = plan.withinRange(dayTempRange());

以函数取代参数

如果函数可以通过其他途径获得参数值,那么它就不应该通过参数取得该值。过长的参数列会增加程序阅读者的理解难度,因此我们应该尽可能缩短参数列的长度。

// 书中的示例
public double getPrice() {
    int basePrice = _quantity * _itemPrice;
    int discountLevel;
    if (_quantity > 100) {
        discountLevel = 2;
    } else {
        discountLevel = 1;
    }
    double finalPrice = discountedPrice(basePrice, discountLevel);
    return finalPrice;
}

private double discountedPrice(int basePrice, int discountLevel) {
    if (discountLevel == 2) {
        return basePrice * 0.1;
    } else {
        return basePrice * 0.05;
    }
}

// 重构后
public double getPrice() {
    if (getDiscountLevel() == 2) {
        return getBasePrice() * 0.1;
    } else {
        return getBasePrice() * 0.05;
    }
}

private int getBasePrice() {
    return _quantity * _itemPrice;
}

private int getDiscountLevel() {
    if (_quantity > 100) {
        return 2;
    } else {
        return 1;
    }
}

引入参数对象

运用一个对象,将传递的参数列表中的参数封装起来,然后传递这个对象,这样就可以缩短参数列表。

// 书中的示例
class Entity {
    private double value;
    private Date chargeDate;
    
    Entity(double value, Date chargeDate) {
        this.value = value;
        this.chargeDate = chargeDate;
    }
    
    Date getChargeDate() {
        return this.chargeDate;
    }
    
    double getValue() {
        return this.value;
    }
}

class Account {
    couble getFlowBetween(Date start, Date end) {
        double result = 0;
        Enumeration e = this.entiries.elements();
        while (e.hasMoreElements()) {
            Entity each = (Entity) e.nextElement();
            if (each.getDate().equals(start)
               || each.getDate.equals(end)
               ||(each.getDate().after(start) && each.getDate().before(end))) {
                result += each.getValue();
            }
        }
        return result;
    }
	private Vector entiries = new Vector();
}

// 重构后,将 start, 和 end 参数封装成一个对象 DateRange
class DataRange {
    private final Date start;
    private final Date end;
    
    DateRange(Date start, Date end) {
        this.start = start;
        this.end = end;
    }
    
    Date getStart() {
        return this.start;
    }
    
    Date getEnd() {
        return this.end;
    }
}

class Account {
    couble getFlowBetween(DataRange dataRange) {
        double result = 0;
        Enumeration e = this.entiries.elements();
        while (e.hasMoreElements()) {
            Entity each = (Entity) e.nextElement();
            if (each.getDate().equals(dataRange.getStart())
               || each.getDate.equals(dataRange.getEnd())
               ||(each.getDate().after(dataRange.getStart()) && each.getDate().before(dataRange.getEnd()))) {
                result += each.getValue();
            }
        }
        return result;
    }
	private Vector entiries = new Vector();
}

移除设值函数

期望类中的某个字段,在创建之后,其值就不要再变化,那么就将 setXXX 方法去除,并且将该字段设置为 final ,只在构造器中提供一个设置值的机会。

隐藏函数

有一个函数,从来没有被其他任何类用到,那么将这个函数设置为 private。在现在的 IDE 中已经有了很充足的检查功能,检查到该函数未使用过,都会提示出来。如果检查到该函数的访问修饰符过大,也会提示出来。

以工厂函数取代构造函数

书中的示例是,创建同一个类的子类对象,但是构造器传递的值不一样,创建不同的子类对象。本质上是将原来应该有的判断语句封装到了一个工厂方法中了。这也符合设计原则中的:发现变化并且封装变化的原则。

封装向下转型

在 Java1.5 中新增了泛型,该场景几乎不会遇到。

以异常取代错误码

这一点,是我觉得有争议的地方。这也体现出了仁者见仁,智者见智的代码风格。有人说,异常应该由错误码来代替。

以测试取代异常

其本质的意思就是,防御性检查。如可能会出异常,使用一条 if 语句,就该种情况处理掉,那么异常就永远不会出现。这对程序员的要求就是,得代码得足够健壮。