重構——條件邏輯判斷

1.案例分析

如何去除If,else,switch條件判斷javascript

對於具備必定複雜邏輯的代碼實現,避免不了出現if,else,switch等邏輯判斷。當邏輯分支愈來愈多的時候,大大大加大了閱讀的難度。這種狀況,咱們該如何處理呢?

2.switch 與if else誰快

對同一個變量的不一樣值做條件判斷時,能夠用switch語句與if語句,哪一個語句執行效率更高呢,答案是switch語句,尤爲是判斷的分支越多越明顯。(具體測試的代碼,小夥伴能夠試一下)
public static void main(String[] args) {
        testIF("12");
        testSwitch("12");
    }
    public static void testIF(String arg) {
        long t1 = System.nanoTime();
        if ("1".equals(arg)) {
            System.out.println(arg);
        } else if ("2".equals(arg)) {
            System.out.println(arg);
        } else if ("3".equals(arg)) {
            System.out.println(arg);
        } else if ("4".equals(arg)) {
            System.out.println(arg);
        } else if ("5".equals(arg)) {
            System.out.println(arg);
        } else if ("6".equals(arg)) {
            System.out.println(arg);
        } else if ("7".equals(arg)) {
            System.out.println(arg);
        } else if ("8".equals(arg)) {
            System.out.println(arg);
        } else if ("9".equals(arg)) {
            System.out.println(arg);
        } else if ("10".equals(arg)) {
            System.out.println(arg);
        } else if ("11".equals(arg)) {
            System.out.println(arg);
        } else if ("12".equals(arg)) {
            System.out.println(arg);
        } else if ("13".equals(arg)) {
            System.out.println(arg);
        } else if ("14".equals(arg)) {
            System.out.println(arg);
        } else {
            System.out.println(arg);
        }
        long t2 = System.nanoTime();
        System.out.println("test if : " + (t2 - t1));
    }
    public static void testSwitch(String arg) {
        long t1 = System.nanoTime();
        switch (arg) {
            case "1":
                System.out.println(arg);
                break;
            case "2":
                System.out.println(arg);
                break;
            case "3":
                System.out.println(arg);
                break;
            case "4":
                System.out.println(arg);
                break;
            case "5":
                System.out.println(arg);
                break;
            case "6":
                System.out.println(arg);
                break;
            case "7":
                System.out.println(arg);
                break;
            case "8":
                System.out.println(arg);
                break;
            case "9":
                System.out.println(arg);
                break;
            case "10":
                System.out.println(arg);
                break;
            case "11":
                System.out.println(arg);
                break;
            case "12":
                System.out.println(arg);
                break;
            case "13":
                System.out.println(arg);
                break;
            case "14":
                System.out.println(arg);
                break;
            default:
                System.out.println(arg);
                break;
        }
        long t2 = System.nanoTime();
        System.out.println("test switch: " + (t2 - t1));
    }
最終現實結果
12
test if : 482713
12
test switch: 24870

3.邏輯分支多爲何看起來費勁呢?

複雜!複雜!代碼圈複雜度高!
什麼是代碼圈複雜度?前端

圈複雜度

1)概念:
  • 用來衡量一個模塊斷定結構的複雜程度,數量上表現爲獨立現行路徑條數,即合理的預防錯誤所需測試的最少路徑條數,圈複雜度大說明程序代碼可能質量低且難於測試和維護,根據經驗,程序的可能錯誤和高的圈複雜度有着很大關係。
1)計算公式:
  • 計算公式爲:V(G)=e-n+2。其中,e表示控制流圖中邊的數量,n表示控制流圖中節點的數量。 其實,圈複雜度的計算還有更直觀的方法,由於圈複雜度所反映的是「斷定條件」的數量,因此圈複雜度實際上就是等於斷定節點的數量再加上1,也即控制流圖的區域數,對應的計算公式爲:V(G)=區域數=斷定節點數+1
  • 對於多分支的CASE結構或IF-ELSE結構,統計斷定節點的個數時須要特別注意一點,要求必須統計所有實際的斷定節點數,也即每一個 ELSEIF語句,以及每一個CASE語句,都應該算爲一個斷定節點。斷定節點在模塊的控制流圖中很容易被識別出來,因此,針對程序的控制流圖計算圈複雜度 V(G)時,最好仍是採用第一個公式,也即V(G)=e-n+2;而針對模塊的控制流圖時,能夠直接統計斷定節點數,這樣更爲簡單。

4.如何重構這樣的代碼呢?

1) NULL Object空對象模式

  • 描述: 當你在處理可能會出現null的對象時,可能要產生相對乏味的代碼來作相應的處理,使用空對象模式能夠接受null,並返回相應的信息。
  • 代碼示例:
interface ILog {
     void log();
}
class FileLog implements ILog {
    public void log() {
    }
}
class ConsoleLog implements ILog {
    public void log() {
    }
}

class NullObjectLog implements ILog {
    public void log() {
    }
}
public class LogFactory {
    static ILog Create(String str) {
        ILog log = new NullObjectLog();
        if ("file".equals(str))
            log = new FileLog();
        if ("console".equals(str))
            log = new ConsoleLog();
        return log;
    }
}

2) 表驅動法(Table-Driven Approach)

  • 描述:表驅動法是一種設計模式,可用來代替複雜的if、else邏輯判斷。
觀察下面的一維數組的形式能夠發現,定義了2個變量。
例如:int a[12],a[x]=y;
至關於函數 y=f(x) (在此例子中,x和y爲均爲int類型),因而就變成了咱們平時熟悉的普通的c函數。而函數通常經過數學表達式和邏輯判斷的形式得出結果,而這樣的結果通常的來講有數學規律,例如像n!就很適合於使用函數實現。
而表驅動法的函數關係是人爲定義的,若是採用函數,通常會出現不少的if、else判斷。因此表驅動法適合於去實現「人造邏輯」的函數。
例子1:假設你要編寫一個計算醫療保險費用的程序,其中保險費用是隨着性別、年齡、婚姻情況 和是否吸菸而變化的。(這是代碼大全書上的例子,原版是Pasca版本,方便閱閱讀改爲Java辦)。這時候,第一反應可能就是一大堆的if-else語句,以下:
enum SexStatus {
    Female, Male
}
enum MaritalStatus {
    Single, Married
}
enum SmokingStatus {
    NonSmoking, Smoking
}
public double ComputeInsuranceCharge(SexStatus sexStatus, MaritalStatus maritalStatus, SmokingStatus smokingStatus, int age) {
    double rate = 1;
    if (sexStatus.equals(SexStatus.Female)) {
        if (maritalStatus.equals(MaritalStatus.Single)) {
            if (smokingStatus.equals(SmokingStatus.NonSmoking)) {
                if (age < 18) {
                    rate = 40.00;
                } else if (age == 18) {
                    rate = 42.50;
                } else if (age == 19) {
                    rate = 45.00;
                }
                ...
                else if (age > 65) {
                    rate = 150.00;
                }
            } else if (smokingStatus == SmokingStatus.Smoking) {
                if (age < 18) {
                    rate = 44.00;
                } else if (age == 18) {
                    rate = 47.00;
                } else if (age == 19) {
                    rate = 50.00;
                }
                ...
                else if (age > 65) {
                    rate = 200.00;
                }
            }
        } else if (maritalStatus == MaritalStatus.Married) {
            //......
        }
    }
    return rate;
}

可是仔細看一下代碼,其實保險費率和性別、婚姻、是否抽菸、年齡這個幾個因素有必定的關係,尤爲年齡的變化區間是至關大,按照上述的寫法,可想而知,代碼的複雜會達到什麼樣子的程度。
這時候,確定有人會想不須要對每一個年齡進行判斷,並且將保險費用放入年齡數組中,這樣將極大地改進上述的代碼。不過,若是把保險費用放入全部影響因素的數組而不只僅是年齡數組的話,將會使程序更簡單,相似於能夠設計一個費率表格,來下降代碼的複雜度呢。java

  • 定義好費率表格以後, 你就須要肯定如何把數據放進去。你能夠用從文件中讀入費率表格數據。
  • 當你創建好數據以後, 便作好了計算保險費用的一切工做。如今就能夠用下面這個簡單的語句來代替前面那個複雜的邏輯結構了。
Table<Integer, RateFactor, Double> rateTable = HashBasedTable.create();
enum RateFactor {
    MALE_SINGLE_NONSMOKING,
    MALE_SINGLE_SMOKING,
    MALE_MARRIED_NONSMOKING,
    MALE_MARRIED_SMOKING,
    FEMALE_SINGLE_NONSMOKING,
    FEMALE_SINGLE_SMOKING,
    FEMALE_MARRIED_NONSMOKING,
    FEMALE_MARRIED_SMOKING,
}
public double ComputeInsuranceCharge(RateFactor rateFactor, int age) {
    int ageFactor;
    if (age < 18) {
        ageFactor = 0;
    } else if (age > 65) {
        ageFactor = 65 - 17;
    } else {
        ageFactor = age - 17;
    }
    return rateTable.get(ageFactor, rateFactor);
}
例子2:前端語言能夠採用表驅動法簡化邏輯嗎?

原先代碼:設計模式

switch (something) {  
  case 1:  
    doX();  
  break;  
  case 2:  
    doY();  
  break;  
  case 3:  
    doN();  
  break;  
  // And so on...  
}

重構後代碼:數組

var cases = {   
   1: doX,   
   2: doY,   
   3: doN  
 };  
 if (cases[something]) {   
      cases[something]();  
 }

關於表驅動法,仍是其餘靈活的使用方法,能夠參考《代碼大全》函數

3) 繼承子類的多態

使用繼承子類的多態,它們使用對象的間接性有效地擺脫了傳統的狀態判斷。
使用繼承子類多態的方式,一般對於某個具體對象,它的狀態是不可改變的(在對象的生存週期中)。測試

  • 原先代碼:
public class Method {
    private int type;
    public static final int POST = 0;
    public static final int GET = 1;
    public static final int PUT = 2;
    public static final int DELETE = 3;
    public Method(int type) {
        this.type = type;
    }
    public String getMethod() throws RuntimeException {
        switch (type) {
            case POST:
                return "這是 POST 方法";
            case GET:
                return "這是 GET 方法";
            case PUT:
                return "這是 PUT 方法";
            case DELETE:
                return "這是 DELETE 方法";
            default:
                throw new RuntimeException("方法類型調用出錯");
        }
    }
}
  • 重構方法: 如今使用四個子類分別表明四種類型的方法。這樣就可使用多態將各個方法的具體邏輯分置到子類中去了

4) 使用state模式

若是但願對象在生存週期內,能夠變化本身的狀態,則能夠選擇state模式。
重構方法:這裏抽象狀態爲一個接口MethodType,四種不一樣的狀態實現該接口。
this

相關文章
相關標籤/搜索