简化条件表达式
这项重构很重要,使得 “分支逻辑” 和 “操作细节” 分离。
分解条件表达式
从 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 类的处理方式。
注意:断言只能检查 “一定必须为真” 的条件,而不是 “你认为他应该为真” 的条件。