重构(笔记)——第九章

400 阅读4分钟

简化条件表达式

这项重构很重要,使得 “分支逻辑” 和 “操作细节” 分离。

分解条件表达式

从 if 、then 、 else 三个段落中分别提炼出独离函数。

// 书中的示例
if (date.before(SUMER_START) || date.after(SUMER_END)) {
    charges = quantity * _winterRate + _winterServiceCharge;
} else {
    charge = quantity + _sumerRate;
}

// 重构后

if (notSumer(date)) {
    charge = winterCharge(quantity);
} else {
    charge = sumerCharge(quantity);
}

private boolean notSumer(Date date) {
    return date.before(SUMER_START) || date.after(SUMER_END);
}

private double sumerCharge(int quantity) {
    return quantity * _sumerRate;
}

private double winterCharge(int quantity) {
    return quantity * _winterRate + _winterServiceCharge;
}

为了程序的可读性,将大块的代码分解成一个一个的小的独立的函数,然后为新的函数起一个合适的名称。

合并条件表达式

有时候你会发现这样一串条件检查:检查条件各不相同,最终行为却一致。如果发现这种情况,就应该使用“逻辑或” 和 “逻辑与” 将它们合并为一个条件表达式。

// 书中的示例 
double disbilityAmount() {
    if (_seniority < 2) {
        return 0;
    }
    if (_monthsDisable > 12) {
        return 0;
    }
    if (_isPartTime) {
        return 0;
    }
}

// 重构后
double disabillityAmount() {
    if (isNotEligibleForDisability()) {
        return 0;
    }
}

private isNotEligibleForDisability() {
    return ((_seniority < 2) || (_monthsDisabbled > 12) || (_isPartTime));
}

合并重复的条件片段

在条件表达式的每个分支上有着相同的一段代码

if (isSpecialDeal()) {
    total = price * 0.95;
    send();
} else {
    total = price * 0.98;
    send();
}

// 重构后

if (isSpecialDeal()) {
    total = price * 0.95;
} else {
    total = price * 0.98;
}
send();

有时候,一组条件表达的所有分支都执行了相同的某段代码,这样就应该将这段代码搬移到条件表达式外面。这样,代码才能更清楚地表明哪些东西随条件变化而变化、哪些东西保持不变。

  • 如果这些共通代码位于条件表达式起始处,就将它移到条件表达式之前。
  • 如果这些共通代码位于条件表达式尾端,就将它移到条件表达式之后。
  • 如果这些共通代码们于条件表达式中段,就需要观察共通代码之前或之后的代码是否改变了什么东西。如果的确有所改变,应该首先将共通代码向前或向后移动,移到条件表达式的起始处或尾端,再以前面所说的办法来处理。
  • 如果共通代码不止一条语句,应该首先使用 “提取函数” 将共通代码提取到一个独立函数中,再以前面的办法进行处理。

移除控制标记

有一系列布尔表达式中,某个变量带有 “控制标记” 的作用。以 "break" 语句或 "return" 语句取代控制标记。

// 书中的示例
void checkSecurity(String[] people) {
    boolean found = false;
    for (int i = 0; i < people.length; i++) {
        if (!found) {
            if (people[i].equals("Don")) {
                sendAlert();
                found = true;
            }
            if (people[i].equals("John")) {
                sendAlert();
                found = true;
            }
        }
    }
}

// 重构后
void checkSecurity(String[] people) {
    String found = foundMiscreant(people);
    someLaterCode(found);
}

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

// 里面的两个 if 其实是一个查询,可以再进行重构。 
// 再次重构
void checkSecurity(String[] people) {
    sendAlert(people);
    String found = foundPerson(people);
    someLaterCode(found);
}

void sendAlert(String[] people) {
    if (!foundPerson(people).equals("")) {
        // todo 这里的做法,其实会改变原本的语义,只有当 "Don" 和 "John" 的时候才发送,会变成不是 "" 就发送(即调用 sendAlert() 函数)
        sendAlert();
    }
    
    // 不改变语义的做法
    if(foundPerson(people).equals("Don") || foundPerson(people).equals("John")) {
        sendAlert();
        // 那么 if 中的语句又可以进行重构了
    }
}

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

以卫语句取代嵌套条件表达式

如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为 “卫语句”。

  • 所有分支都属于正常行 为
  • 条件表达式提供的答案中,只有一种是正常行为,其他都是不常见的情况。

如果两条分支都是正常行为,就应该使用形如 if ... else ... 的条件表达式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。

// 书中的示例
double getPayAmount() {
    double result;
    if (_isDead) {
        result = deadAmount();
    } else {
        if (_isSeparated) {
            result = separatedAmount();
        } else {
            if (_isRetired) {
                result = retiredAmount();
            } else {
                result = mormalPayAmount();
            }
        }
    }
    return result;
}

// 重构后
double getPayAmount() {
    if (_isDead) {
        return deadAmount();
    }
    if (_isSeparted) {
        return separatedAmount();
	}
    if (_isRetired) {
        return retiredAmount();
    }
    return mormalPayAmount();
}

在很多时候,可以将条件反转,然后再使用 “以卫语句取代嵌套条件表达工” 的手法

以多态取代条件表达式

// 书中的示例

class Employee {
    double getSpeed() {
        switch (_type) {
            case EUROPEAN:
                return getBaseSpeed();
            case AFRICAN:
                return getBaseSpeed() - getLoadFactor() * _numberOfCocounts;
            case NORWEGIAN_BLUE:
                return (_isNailed) ? 0 : getBaseSpeed(_voltage);
        }
        throw new RuntimeException("Should be unreachable");
	}
}

// 重构后
class EmployeeType {
   abstract int getTypeCode();
   int payAmount(Employee emp) {
        switch (getTypeCode()) {
            case EUROPEAN:
                return getBaseSpeed();
            case AFRICAN:
                return getBaseSpeed() - getLoadFactor() * _numberOfCocounts;
            case NORWEGIAN_BLUE:
                return (_isNailed) ? 0 : getBaseSpeed(_voltage);
        }
        throw new RuntimeException("Should be unreachable");
	}
}

class Employee {
     int payAmount() {
        return _type.payAmount(this);
    }
}

class Engineer extends EmployeeType{
    
    int getTypeCode() {
        return Employee.ENGINEER;
    }
    
    int payAmount(Employee emp) {
        return emp.getMonthlaySalary();
    }
}

引入 Null 对象

书中提供了空模式的处理方式,在 Java 8 中提供 Optional<T> 来专门进行 null 的处理。

引入断言

具体可以参考 Spring 中的 Asert 类的处理方式。

注意:断言只能检查 “一定必须为真” 的条件,而不是 “你认为他应该为真” 的条件。