【重構筆記05】簡化條件表達式

前言

條件邏輯每每十分複雜,咱們今天就看看有哪些怎樣纔可以簡化條件表達式的邏輯
分解條件表達式,能夠將條件邏輯分紅若干小塊,這樣就能夠將分支邏輯與操做細節分離編程

合併條件表達式

咱們代碼中若是出現一系列條件測試,並且他們都得到相同的結果,那麼將這些測試合併爲一個條件表達式,並將這個表達式提煉爲一個獨立函數app

有時候咱們會發現這樣一連串檢查,檢查條件各不相同,最終的行爲卻一致,若是出現這種狀況,就應該使用「邏輯或」和「邏輯與」將它們合併爲一個條件表達式
之因此要合併條件代碼,有兩個重要緣由,首先,合併後的條件代碼會告訴你實際上只有一次條件檢查,只不過有多個並列條件須要檢查而已
從而使這一次檢查的用意更清晰,固然,合併前與合併後的代碼有相同的效果,但原先代碼傳達的信息倒是「這裏有一些各自獨立的條件測試,他們只是恰好同時發生」,這項重構每每能夠爲你提煉方法作準備
可是若是條件檢查倒是是獨立的,不是同一次檢查,就不要使用此項重構ide

 1 function disabilityAmount() {
 2     if (_a < 2) return 0;
 3     if (_b > 12) return0;
 4     if (_c) return0;
 5 }
 6 //改爲這個樣式
 7 function disabilityAmount() {
 8     if (isNotEligibleForDisablity()) return 0;
 9 }
10 function isNotEligibleForDisablity() {
11     return (_a < 2) && (_b > 12) && (_c);
12 }

合併重複的條件片斷

在條件表達式的每一個分支上有着相同的一段代碼,那麼將這段代碼搬到條件表達式外面函數

有時候咱們發現咱們的代碼中一組條件表達式的全部分支都執行了相同的某段代碼,這個時候咱們應該將這段代碼搬到條件表達式外面去
這樣代碼才能更清楚的表達哪些東西隨條件變化而變化,哪些東西保持不變工具

 1 if (isSpecialDeal()) {
 2     total = price * 0.95;
 3     send();
 4 } else {
 5     total = price * 0.98;
 6     send();
 7 }
 8 //重構
 9 if (isSpecialDeal()) {
10     total = price * 0.95;
11 } else {
12     total = price * 0.98;
13 }
14 send();

移除控制標記

在一系列布爾表達式中,某個變量帶有控制標記(control flag)的做用,那麼以break語句或者return取代之
這樣的控制標記帶來的麻煩超過了他帶來的便利,人們之因此會使用這樣的控制標記,是由於結構化編程原則告訴咱們
每一個子程序只能有一個入口和出口
單一入口是對的,可是單一出口則會讓咱們的代碼增長討厭的控制標記,下降程序可讀性,這就是break與continue出現的緣由(用它跳出複雜的條件語句),去掉控制標記的效果會讓你大吃一驚(聽說會讓條件語句清晰很多)學習

怎麼作

① 對控制標記的處理,最顯而易見的辦法就是使用break與continue語句
② 找出跳出邏輯的標記值
③ 找出對標記變量賦值的語句,替換爲break或continue
④ 替換後測試
也可使用如下方法
① 將整段邏輯提煉爲一個函數
② 找出跳出邏輯的控制標記替換爲return測試

return語句能夠清晰的表述清楚再也不執行函數中的其它任何代碼,因而咱們來個例子吧this

 1 function checkSecurity(people) {
 2     var found = false;
 3     for (var i = 0; i < people.length; i++) {
 4         if (!found) {
 5             if (people[i] == 'Don') {
 6                 sendAlert();
 7                 found = true;
 8             }
 9             if (people[i] == 'John') {
10                 sendAlert();
11                 found = true;
12             }
13         }
14     }
15 }
16 
17 //這種狀況控制標記很容易找出,稍微變一下就能夠了:
18 
19 function checkSecurity(people) {
20     for (var i = 0; i < people.length; i++) {
21         if (people[i] == 'Don') {
22             sendAlert();
23             break;
24         }
25         if (people[i] == 'John') {
26             sendAlert();
27             break;
28         }
29     }
30 }
31 
32 //咱們甚至能夠將裏面判斷的邏輯獨立爲一個函數
33 function findInSet(names, arr) {
34     if (typeof names != 'object') names = [names];
35     for (var k in names) {
36         for (var i = 0, len = arr.length; i < len; i++) {
37             if (arr[i] == names[k]) return true;
38         }
39     }
40     return false;
41 }
42 function checkSecurity(people) {
43     for (var i = 0; i < people.length; i++) {
44         if (findInSet(['Don', 'John'], people)) {
45             sendAlert();
46             break;
47         }
48     }
49 }

以衛語句取代嵌套條件表達

在函數中的條件邏輯令人難以看清楚正常的執行路徑,這個時候可使用衛語句表現全部特殊狀況spa

 1 function getPayAmount() {
 2     var result;
 3     if (this.isDead) { result = this.deadAmount(); } else {
 4         if (this.isRetired) result = this.retiredAmount();
 5         else result = this.nomalAmount()
 6     }
 7     return result;
 8 }
 9 
10 //
11 function getPayAmount() {
12     var result;
13     if (this.isDead) return this.deadAmount();
14     if (this.isRetired) return this.retiredAmount();
15     return this.nomalAmount()
16 }

根據經驗,條件表達式一般有兩種表現形式,第一種形式是:全部分支都屬於正常行爲,第二種形式則是條件表達式提供的答案中只有一種是正常行爲,其餘都是不正常見的狀況
這兩類條件表達式有不一樣的用途這一點應該經過代碼表現出來,若是兩條分支都是正常行爲,就應該使用if else 了
若是某個分支及其罕見,就應該單獨檢查該條件,並在該條件爲真時從函數返回,這樣的檢查就是傳說中的「衛語句」prototype

該重構方法的精髓就是給某個分支特別重視,若是使用if else,你對if和else重視是同等的
衛語句就告訴用戶,這種狀況罕見,可是真的發生了就須要作一些整理工做了
每一個函數都只能有一個入口和一個出口,這個思想可能須要更新下了,做者認爲代碼清晰最重要(我也認爲單一出口有用......)

以多態取代條件表達式

你手上有多個條件表達式,它根據對象類型的不一樣而選擇不一樣的行爲
那麼將這個條件表達式的每一個分支放進一個子類內的複寫函數中,而後抽象原始函數聲明

面向對象術語中,「多態」一詞尤爲高端大氣上檔次,多態的最大好處就是
若是你須要根據對象的不一樣類型二採起不一樣的行爲,多態讓咱們不一樣寫明顯的條件表達式
多態可以帶來不少好處,若是同一組條件表達式在程序許多地點出現,那麼使用多態的收益是最大的
使用條件表達式時,若是你想添加一種寫類型,就必須查找並更新更多的條件表達式
若是改用多態,只需創建一個新的子類,並在其中提供適當的函數就好了,類的用戶不須要了解這個子類,大大下降依賴

怎麼作

使用簡化條件表達式以前,首先必須有一個繼承關係,咱們通常使用以子類取代類型碼,
但若是須要在對象建立好自豪修改類型碼,就要使用state/strategy模式
① 若是處理的條件表達式是一個更大函數的一部分,首先分析條件表達式,而後提煉函數分離到獨立函數
② 若是有必要使用移動方法將條件表達式放到繼承的頂端
③ 任選一個子類,在其中創建一個函數,使之複寫超類中容納條件表達式的函數,將與該子類相關的條件表達式的分支複製到新函數中
④ 針對條件表達式的每一個分支,重複上述過程,直到全部分支都移動到子類函數

來個例子吧,說多了意義不大

首先這是咱們要用到的工具類:

 1 var base = {};
 2 var slice = [].slice;
 3 var bind = function (scope, fun, args) {
 4     args = args || [];
 5     return function () {
 6         fun.apply(scope, args.concat(slice.call(arguments)));
 7     };
 8 };
 9 
10 base.Class = function (supClass, childAttr) {
11     //如果第一個是類,即是繼承;若是第一個是對象,第二個參數無心義,即是新建一個類
12     if (typeof supClass === 'object') {
13         childAttr = supClass;
14         supClass = function () { };
15     }
16     //新建臨時類,最後做爲新類返回,多是繼承多是新類
17     /***
18     這裏很是關鍵,爲整個方法的入口,必定得看到初始化後,這裏會執行構造函數
19     ***/
20     var newClass = function () {
21         //每一個類都會使用該函數,做爲第一步初始化,告訴類有哪些屬性
22         this._propertys_ && this._propertys_();
23         //第二步初始化,至關於子類的構造函數,比較重要,初始化方法不必定會出現
24         this.init && this.init.apply(this, arguments);
25     };
26     //發生繼承關係,可能爲空類
27     newClass.prototype = new supClass();
28 
29     //新建類一定會包含初始化函數,要麼繼承,若是沒繼承,這裏也會新建
30     var supInit = newClass.prototype.init || function () { };
31     //傳入的子對象可能包含他的初始化方法,若是有必定要使用,至於父類使用與否看子類心情
32     var childInit = childAttr.init || function () { };
33     //父類的properys方法即是指定會具備哪些屬性,必定會執行
34     var _supAttr = newClass.prototype._propertys_ || function () { };
35     //子類的初始化也必定會觸發,先執行父類再執行子類
36     var _childAttr = childAttr._propertys_ || function () { };
37 
38     //爲新建類(可能繼承可能新建)初始化原型,上面的會重寫,沒有就無論他
39     for (var k in childAttr) {
40         childAttr.hasOwnProperty(k) && (newClass.prototype[k] = childAttr[k]);
41     }
42 
43     //處理繼承狀況
44     if (arguments.length && arguments[0].prototype && arguments[0].prototype.init === supInit) {
45         //根據父類重寫新建類構造時會用到的方法
46         newClass.prototype.init = function () {
47             var scope = this;
48             var args = [function () {
49                 //第一個參數爲父類的初始化函數,執行與否看子類心情
50                 supInit.apply(scope, arguments)
51             } ];
52             childInit.apply(scope, args.concat(slice.call(arguments)));
53         };
54     }
55     //前面說到的,父類與子類的初始化方法必定會執行,先父後子
56     newClass.prototype._propertys_ = function () {
57         _supAttr.call(this);
58         _childAttr.call(this);
59     };
60 
61     //成員屬性也得繼承
62     for (var k in supClass) {
63         supClass.hasOwnProperty(k) && (newClass[k] = supClass[k]);
64     }
65     return newClass;
66 };
View Code
 1 var EmployeeType = base.Class({
 2     _propertys_: function () {
 3     },
 4     init: function (code) {
 5         switch (code) {
 6             case EmployeeType.ENGINEER: return new Engineer();
 7             case EmployeeType.SALESMAN: return new Saleman();
 8             case EmployeeType.MANAGER: return new Manager();
 9             default: throw 'incorrect employee code';
10         }
11     },
12     payAmount: function (emp) {
13         switch (this.getType()) {
14             case EmployeeType.ENGINEER: return rmp.getMonthlySalary();
15             case EmployeeType.SALESMAN: return emp.getMonthlySalary() + emp.getCommission();
16             case EmployeeType.MANAGER: return emp.getMonthlySalary() + emp.getBonus();
17             default: throw 'incorrect employee';
18         }
19     }
20 });
21 EmployeeType.ENGINEER = 0;
22 EmployeeType.SALESMAN = 1;
23 EmployeeType.MANAGER = 2;
24 
25 var Employee = base.Class({
26     _propertys_: function () {
27         this.monthlySalary;
28         this.commission;
29         this.bonus;
30     },
31     init: function (type) {
32         this.type = new EmployeeType(type);
33     },
34     getMonthlySalary: function () {
35         return this.monthlySalary;
36     }, 
37     getCommission: function () {
38         return this.commission;
39     }, 
40     getBonus: function () {
41         return this.bonus;
42     },
43     payAmount: function () {
44         return this.type.payAmount();
45     }
46 });

簡單調整

 1 /*邏輯代碼*/
 2 var EmployeeType = base.Class({
 3     _propertys_: function () {
 4     },
 5     getType: function (code) {
 6         switch (code) {
 7             case EmployeeType.ENGINEER: return new Engineer();
 8             case EmployeeType.SALESMAN: return new Saleman();
 9             case EmployeeType.MANAGER: return new Manager();
10         }
11     },
12     payAmount: function (emp) {
13         //        switch (this.getType()) {
14         //            case EmployeeType.ENGINEER: return rmp.getMonthlySalary();
15         //            case EmployeeType.SALESMAN: return emp.getMonthlySalary() + emp.getCommission();
16         //            case EmployeeType.MANAGER: return emp.getMonthlySalary() + emp.getBonus();
17         //            default: throw 'incorrect employee';
18         //        }
19     }
20 });
21 EmployeeType.ENGINEER = 0;
22 EmployeeType.SALESMAN = 1;
23 EmployeeType.MANAGER = 2;
24 
25 var Engineer = base.Class(EmployeeType, {
26     init: function (_superInit_) {
27     },
28     payAmount: function (emp) {
29         return emp.getMonthlySalary();
30     }
31 });
32 var Saleman = base.Class(EmployeeType, {
33     init: function (_superInit_) { },
34     payAmount: function (emp) {
35         return emp.getMonthlySalary() + emp.getCommission();
36     }
37 });
38 var Manager = base.Class(EmployeeType, {
39     init: function (_superInit_) { },
40     payAmount: function (emp) {
41         return emp.getMonthlySalary() + emp.getBonus();
42     }
43 });
44 
45 var Employee = base.Class({
46     _propertys_: function () {
47         this.monthlySalary = 11;
48         this.commission = 22;
49         this.bonus = 33;
50     },
51     init: function (type) {
52         this.type = new EmployeeType();
53         this.type = this.type.getType(type);
54     },
55     getMonthlySalary: function () {
56         return this.monthlySalary;
57     },
58     getCommission: function () {
59         return this.commission;
60     },
61     getBonus: function () {
62         return this.bonus;
63     },
64     payAmount: function () {
65         return this.type.payAmount(this);
66     }
67 });
68 
69 var e0 = new Employee(0);
70 var e1 = new Employee(1);
71 var e2 = new Employee(2);
72 
73 console.log(e0.payAmount());
74 console.log(e1.payAmount());
75 console.log(e2.payAmount());

結語

此次是初次學習重構,一些沒明白的,咱們二次學習再搞懂吧

相關文章
相關標籤/搜索