《重構 改善既有代碼的設計》第9章 簡化條件表達式

前言

條件邏輯有可能十分復雜,本章提供一些重構手法,專門用來簡化它們。

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最近的地方抓住他們。

做法

如果程序員不犯錯,斷言就應該不會對系統運行造成任何影響,所以加入斷言永遠不會影響程序的行為。

如果你發現代碼假設某個條件始終為真,就加入一個斷言明確說明這種情況

注意,不要濫用斷言。

請不要使用它來檢查“你認為應該為真”的條件,請只使用它來檢查“一定必須為真”的條件。你應該常常問自己:如果斷言所指示的約束條件不能滿足,代碼是否仍能夠正常運行?如果可以,就把斷言拿掉。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。