前言
條件邏輯有可能十分復雜,本章提供一些重構手法,專門用來簡化它們。
1. Decompose Conditional(分解條件表達式)
概要:針對復雜的條件(if-then-else)語句
做法:從if、then、else三個段落中分別提煉出獨立函數
2. Consolidate Conditional Expression(合并條件表達式)
一系列條件檢查,都得到相同的結果。
合并為一個條件表達式,并將這個條件表達式提煉成一個獨立函數。
重構前
double disabilityAmount() {
if(seniority < 2) return 0;
if(monthsDisabled > 12) return 0;
if(isPartTime) return 0;
// compute the disability amount
}
重構后
double disabilityAmount() {
if(seniority < 2 || monthsDisabled > 12 || isPartTime)
return 0;
// compute the disability amount
}
注意不適用條件:這些檢查的確彼此獨立。
3. Consolidate Duplicate Conditional Fragments(合并重復的條件片段)
重構前
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();
一組條件表達式的所有分支都執行了相同的代碼段,此時應該把這個代碼段移到條件表達式的外面,才能更清楚地看到哪些東西隨條件變化,哪些不變。
4. Remove control flag(移除控制標記)
在一系列布爾表達式中,某個變量帶有“控制標記’的作用。
以break或return語句取代控制標記。
動機:在一系列條件表達式中,常常會看到用以判斷何時停止條件檢查的控制標記。這樣的標記帶來的麻煩超過了它所帶來的便利。人們之所以會使用這樣的控制標記,因為結構化編程原則告訴他們:每個子程序只能有一個入口和出口。“單一出口“原則會讓你在代碼中加入讓人討厭的控制標記,大大降低條件表達式的可讀性。這就是編程語言提供break和continue語句的原因:用它們跳出復雜的條件語句。去掉控制標記所產生的效果往往讓你大吃一驚:條件語句真正的用途會清晰得多。
做法:
1、對控制標記的處理,最顯而易見的辦法就是使用break或continue語句。
2、找出讓你跳出這段邏輯的控制標記值。
3、找出對標記變量賦值的語句,代以恰當的break或continue語句。
4、每次替換后,編譯并測試。
重構前
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) {
for(int i=0;i < people.length; i++) {
if(people[i].equals("Don")) {
sendAlert();
break;
}
if(people[i].equals("John")) {
sendAlert();
break;
}
}
}
5. Replace Nested Conditional with Cuard Clauses(以衛語句取代嵌套條件表達式)
重構前
double getPayAmount() {
double result;
if(isDead) result = deadAmount();
else {
if(isRetired) result = retiredAmount();
else result = normalAmount();
}
return result;
}
重構后
double getPayAmount() {
if(isDead) return deadAmount();
if(isRetired) return retiredAmount();
return normalAmount();
}
6. Replace Conditional with Polymorphism(以多態取代嵌套條件表達式)
重構前
public class Employee {
private EmployeeType type;
private int monthlySalary = 3000;
private int commission = 2000;
private int bonus = 5000;
public Employee(EmployeeType employeeType) {
this.type = employeeType;
}
int payAmount() {
switch (getType()) {
//根據不同員工類型,返回不同的薪水
case EmployeeType.ENGINEER:
return monthlySalary;
case EmployeeType.SALESMAN:
return monthlySalary + commission;
case EmployeeType.MANAGER:
return monthlySalary + bonus;
default:
throw new RuntimeException("Incorrect Employee");
}
}
int getType() {
return type.getTypeCode();
}
abstract class EmployeeType {
public static final int ENGINEER = 1;
public static final int SALESMAN = 2;
public static final int MANAGER = 3;
abstract int getTypeCode();
}
class Engineer extends EmployeeType {
@Override
int getTypeCode() {
return ENGINEER;
}
}
class Salesman extends EmployeeType {
@Override
int getTypeCode() {
return SALESMAN;
}
}
class Manager extends EmployeeType {
@Override
int getTypeCode() {
return MANAGER;
}
}
}
重構后
public class Employee {
private EmployeeType type;
public Employee(EmployeeType employeeType) {
this.type = employeeType;
}
int payAmount() {
return type.payAmount(this);
}
abstract class EmployeeType {
public static final int ENGINEER = 1;
public static final int SALESMAN = 2;
public static final int MANAGER = 3;
abstract int payAmount(Employee employee);
}
class Engineer extends EmployeeType {
@Override
int payAmount(Employee employee) {
return employee.getMonthlySalary();
}
}
class Salesman extends EmployeeType {
@Override
int payAmount(Employee employee) {
return employee.getMonthlySalary() + employee.getCommission();
}
}
class Manager extends EmployeeType {
@Override
int payAmount(Employee employee) {
return employee.getMonthlySalary() + employee.getBonus();
}
}
public int getMonthlySalary() {
return 3000;
}
public int getCommission() {
return 2000;
}
public int getBonus() {
return 5000;
}
}
優化動機:
根據對象的不同類型而采取不同的行為,多態可以使你不必編寫明顯的條件表達式。
獲得的收益:
類的用戶不需要了解這個子類,大大降低了系統各部分之間的依賴,易擴展。
7. Introduce Null Object(引入Null對象)
你需要再三檢查某對象是否為null
一家公用事業公司的系統以Site表示地點,庭院宅第house和集體公寓apartment都使用該公司的服務。任何時候每個地點都擁有一個顧客,顧客信息以Customer表示。
重構前
public class Site {
private Customer customer;
Customer getCustomer() {
return customer;
}
class Customer {
private String name, plan;
public String getName() {
return name;
}
public String getPlan() {
return plan;
}
}
}
上面的各種取值函數允許客戶取得各種數據。
Customer customer = site.getCustomer();
String plan;
if (customer == null) {
plan = "nullPlan";
} else {
plan = customer.getPlan();
}
String name;
if (customer == null) {
name = "nullName";
} else {
name = customer.getName();
}
但有時候一個地點的顧客搬走了,新顧客還沒搬進了,此時這個地點就沒有顧客。由于這種情況有可能發生,所以我們必須保證Customer的所有用戶都能處理“Customer對象等于null”的情況。
此時就是使用空對象的時候了。
重構后
public class Site {
private Customer customer;
//這是重點
Customer getCustomer() {
return customer == null ? new NullCustomer() : customer;
}
class Customer {
private String name, plan;
public String getName() {
return name;
}
public String getPlan() {
return plan;
}
}
class NullCustomer extends Customer {
public String getName() {
return "nullName";
}
public String getPlan() {
return "nullPlan";
}
}
}
接下來在訪問Customer對象的地方,直接調用對應方法即可。
Customer customer = site.getCustomer();
String plan = customer.getPlan();
String name = customer.getName();
8. Introduce Assertion(引入斷言)
某一段代碼需要對程序狀態做出某種假設
重構前
double getExpenseLimit() {
return expenseLimit != NULL_EXPENSE ? expenseLimit : primaryProject.getMemeberExpenseLimit();
}
重構后
double getExpenseLimit() {
Assert.isTrue (expenseLimit != NULL_EXPENSE || primaryProject != null);
return expenseLimit != NULL_EXPENSE ? expenseLimit : primaryProject.getMemeberExpenseLimit();
}
優化動機
常常會有這樣一段代碼:只有當某個條件為真時,該段代碼才能正常運行。例如平方根計算只對正值才能進行,又例如某個對象可能假設其字段至少有一個不等于null。這樣的假設通常并沒有在代碼中明確表現出來,使用斷言能夠明確標明這些假設。
獲得的收益
斷言可以作為交流與調試的輔助(實際上,程序最后的成品往往將斷言統統刪除)
交流角度,斷言可以幫助程序閱讀者理解代碼所做的假設。
調試角度,斷言可以在距離bug最近的地方抓住他們。
做法
如果程序員不犯錯,斷言就應該不會對系統運行造成任何影響,所以加入斷言永遠不會影響程序的行為。
如果你發現代碼假設某個條件始終為真,就加入一個斷言明確說明這種情況
注意,不要濫用斷言。
請不要使用它來檢查“你認為應該為真”的條件,請只使用它來檢查“一定必須為真”的條件。你應該常常問自己:如果斷言所指示的約束條件不能滿足,代碼是否仍能夠正常運行?如果可以,就把斷言拿掉。