條件邏輯有可能十分復(fù)雜,復(fù)雜的條件邏輯可能讓復(fù)雜度快速上升,并有可能導(dǎo)致代碼難以理解。因此,需要一些手段,來簡(jiǎn)化它們。
Decompose Conditional(分解條件表達(dá)式)
帶有復(fù)雜邏輯的函數(shù)中,代碼會(huì)告訴你發(fā)生的事情。但是十分冗長(zhǎng)的條件表達(dá),往往讓后來者摸不著頭腦。這種情況下,可以將它分解為獨(dú)立的函數(shù),并且為新函數(shù)命名(命名可以起到自注釋的作用)。在原函數(shù)中調(diào)用新建函數(shù),從而更清楚地表達(dá)自己的意圖。舉個(gè)例子
if(date.before(SUMMER_START) || date.after(SUMMER_END))
charge=quantity*_winterRate+_winterServiceCharge;
else
charge=quantity*_summerRate;
可以重構(gòu)為
if(notSummer(date))
charge=winterCharge(quantity);
else
charge=summerCharge(quantity);
上面的例子就是將date.before(SUMMER_START) || date.after(SUMMER_END) 替代為notSummer(date),這種情況就一目了然。概括下這種重構(gòu)方法的步驟:
- 將if段落提煉出來,構(gòu)成一個(gè)獨(dú)立的函數(shù)。
- 將then段落和else段落都提煉出來,各自構(gòu)成一個(gè)獨(dú)立的函數(shù)。
- 如果發(fā)現(xiàn)嵌套的條件邏輯,應(yīng)該先看看是否可以使用以衛(wèi)語句取代嵌套條件式。如果不行才開始分解其中的每個(gè)條件。
Consolidate Conditional Expression(合并條件表達(dá)式)
如果有一串條件檢查,如果檢查條件各不相同,但是最終導(dǎo)致的結(jié)果卻一致。那么,就使用“邏輯或”和“邏輯與”將它們合并為一個(gè)條件表達(dá)式。舉例:
double disabilityAmount() {
if (_seniority < 2) {
return 0;
}
if (_monthsDisabled > 12) {
return 0;
}
if (_isPartTime) {
return 0;
}
// compute the disability amount
// ...
}
由于都是返回0,因此可以將條件表達(dá)式合并
double disabilityAmount() {
if (isNotEligibleForDisability()) {
return 0;
}
// compute the disability amount
// ...
}
boolean isNotEligibleForDisability() {
return ((_seniority < 2) || (_monthsDisabled > 12) || (_isPartTime));
}
需要注意的是,要分清返回0是系統(tǒng)的行為是否在邏輯上一致,還是只是同時(shí)返回相同數(shù)值,在未來?xiàng)l件變動(dòng)時(shí)還會(huì)有所改動(dòng)??偨Y(jié)這種重構(gòu)的做法為:
- 確定這些條件語句都沒有副作用。
- 使用適當(dāng)?shù)倪壿嫴僮鞣瑢⒁幌盗邢嚓P(guān)條件表達(dá)式合并為一個(gè)。
- 編譯,測(cè)試。
- 對(duì)合并后的條件表達(dá)式實(shí)施Extract Method。
Consolidate Duplicate Conditional Fragments(合并重復(fù)的條件片段)
你有時(shí)候會(huì)發(fā)現(xiàn),條件語句不同分支執(zhí)行了相同的片段。如果是這樣,你就需要將這段代碼搬移到條件表達(dá)式外面。舉例子:
if (isSpecialDeal()) {
total = price * 0.95;
send();
} else {
total = price * 0.98;
send();
}
重構(gòu)后,可以將send()提取出來
if (isSpecialDeal()) {
total = price * 0.95;
} else {
total = price * 0.98;
}
send();
同樣的,如果try catch中都有同樣的代碼,就可以放在final中去執(zhí)行。總結(jié)這種重構(gòu)的做法為:
- 鑒別出”執(zhí)行方式不隨條件變化而變化”的代碼。
- 如果這些共通代碼位于條件表達(dá)式起始處,就將它移到條件表達(dá)式之前。
- 如果這些共通代碼位于條件表達(dá)式尾端,就將它移到條件表達(dá)式之后。
- 如果這些共通代碼位于條件表達(dá)式中段,就需要觀察共通代碼之前或之后的代碼是否改變了什么東西,如果的確有所改變,應(yīng)該首先將共通代碼向前或向后移動(dòng),移至條件表達(dá)式的起始處或尾端,再以前面所受的辦法來處理。
- 如果共通代碼不止一條語句,應(yīng)該先使用Extract Method(提煉函數(shù))將共通代碼提煉到一個(gè)獨(dú)立函數(shù)中,再以前面所說的辦法來處理。
Remove Control Flag(移除控制標(biāo)記)
在一系列的布爾表達(dá)式中,某個(gè)變量帶有“控制標(biāo)記”的作用,那么用break或者return語句取代控制標(biāo)記。這樣的控制標(biāo)記帶來的麻煩通常超過帶來的便利。結(jié)構(gòu)化編程告訴我們一個(gè)原則:每個(gè)子程序都只能有一個(gè)入口和一個(gè)出口,但是“單一出口”原則通常讓我們?cè)诖a中加入許多的控制標(biāo)記,這樣就大大降低了表達(dá)式的可讀性。使用break或者return可以讓程序條理更加清晰。舉例:
function checkSecurity(peoples) {
String found = '';
for(let i = 0; i < peoples.length; i++) {
if(!found) {
if(peoples[i] === 'Don' || peoples[i] === 'John') {
sendAlert();
found = peoples[i];
}
}
}
someLaterCode(found);
}
重構(gòu)為
function checkSecurity(peoples) {
String found = foundMiscreant(peoples);
someLaterCode(found);
}
function foundMiscreant(peoples) {
for(let i = 0; i < peoples.length; i++) {
if(peoples[i] === 'Don' || peoples[i] === 'John') {
sendAlert();
return peoples[i];
}
}
return '';
}
這種重構(gòu)的一般做法為:
使用extract method,整段邏輯提煉到一個(gè)獨(dú)立函數(shù)中
- 找出跳出這段邏輯的控制標(biāo)記值
- 找出對(duì)標(biāo)記變量賦值的語句,代以恰當(dāng)?shù)膔eturn語句.
- 替換完成后,編譯并測(cè)試。
Replace Nested Conditional with Guard Clauses(以衛(wèi)語句取代嵌套條件表達(dá)式)
如果if else分支都屬于正常行為,那么建議正常使用if else去表達(dá)。但是如果else分支中的情況極其罕見,那么建議采用衛(wèi)語句進(jìn)行表現(xiàn)。
function getPayAmount () {
let result
if (isDead) result = deadAmount()
else {
if (isSeparated) result = separatedAmount()
else {
if (isRetired) result = retiredAmount()
else result = normalPayAmount()
}
}
return result
}
可以重構(gòu)為:
function getPayAmount () {
if (isDead) return deadAmount()
if (isSeparated) return separatedAmount()
if (isRetired) return retiredAmount()
return normalPayAmount()
}
這樣代碼條理更加清晰,可讀性更高。這種重構(gòu)方法的做法為:
- 對(duì)于每個(gè)檢查,放進(jìn)一個(gè)衛(wèi)語句(衛(wèi)語句要不就從函數(shù)返回,要不就拋出一個(gè)異常)。
- 編譯測(cè)試。
Replace Conditional with Polymorphism(以多態(tài)取代條件表達(dá)式)
“多態(tài)”是面向?qū)ο蟮木杷冢鄳B(tài)可以避免為不同類型代入不同行為。舉個(gè)例子:
var makeSound = function(animal) {
if(animal instanceof Duck) {
console.log('嘎嘎嘎');
} else if (animal instanceof Chicken) {
console.log('咯咯咯');
}
}
var Duck = function(){}
var Chiken = function() {};
makeSound(new Chicken());
makeSound(new Duck());
重構(gòu)后
var makeSound = function(animal) {
animal.sound();
}
var Duck = function(){}
Duck.prototype.sound = function() {
console.log('嘎嘎嘎')
}
var Chiken = function() {};
Chiken.prototype.sound = function() {
console.log('咯咯咯')
}
makeSound(new Chicken());
makeSound(new Duck());
多態(tài)背后的思想是將”做什么“和”誰去做以及怎樣去做分開“。通常的做法是:
- 如果要處理的條件表達(dá)式是一個(gè)更大函數(shù)中的一部分。首先,對(duì)條件表達(dá)式進(jìn)行分析,然后使用extract method將其提煉到一個(gè)獨(dú)立函數(shù)去。
- 如果由必要,使用move method、將條件表達(dá)式放置到繼承結(jié)構(gòu)的頂端。
- 任選一個(gè)子類,在其中建立一個(gè)函數(shù),使之覆寫超類中容納條件表達(dá)式的那個(gè)函數(shù)。將與該子類相關(guān)的條件表達(dá)式分支復(fù)制到新建函數(shù)中,并對(duì)其進(jìn)行適當(dāng)調(diào)整。
可能需要將超類中某些private字段聲明為protected。 - 超類中刪掉條件表達(dá)式內(nèi)被復(fù)制了的分支。
- 將超類中容納條件表達(dá)式的函數(shù)聲明為抽象函數(shù)。
Introduce Null Object(引入Null對(duì)象)
這種重構(gòu)方法在前端中不知如何使用,暫忽略。
Introduce Assertion(引入斷言)
這種情況在程序中一般使用是非空判斷,為了減少圈復(fù)雜度。我建議進(jìn)行直接的空判斷,而不是非空判斷。
舉例
if(response){
//do something
}
可以重構(gòu)為
if(!response){
return;
}
//do something
本章重構(gòu)方法比較簡(jiǎn)單,總的原則就是讓代碼清晰可懂,減少復(fù)雜度。這樣的代碼維護(hù)和交接都是一件非常愉悅的事情。