前天講了豆技巧,今天再講一點好了...
假設有個情境是要用數個布林值來判斷接下來要做什麼動作,先從簡單的兩個布林值(a, b)開始。
我們會得到四種組合...或說四個「程式碼的分支」。
if(a == true && b == true){...}
else if(a != true && b == true){...}
else if(a == true && b != true){...}
else{...}
同樣的豆技巧一樣可以用在簡化這個情境上...
這個技巧的原理基本上是以二進位計算為基礎,其實一個八位元數字等於八個布林值所組成,以此類推一個十六位元數字等於十六個布林值所組成,一個三十二位元數字等於三十二個布林值所組成。
但先簡單一點,說說看怎麼用在兩個布林值的簡化上。
0等於「所有布林值皆為false」,那1就等於「第一個布林值為true」。
換個比較好懂的寫法...把0跟1轉化成八位元二進位表達。
00000000等於「所有布林值皆為false」,那00000001就等於「第一個布林值為true」。
再來...
那00000010(2)就等於「第二個布林值為true」。
那00000011(1+2)就等於「第一與第二個布林值皆為true」。
把這個概念應用在判斷上,就不需要寫判斷式,改成如下...
首先定義一個接口來實作每個組合要對應的動作。
interface Act{
void act();
}
然後...
Act[] acts = new Act[4]; //因為有兩個布林值有四種情況
int index = 0;
index += (a)?1:0;
index += (a)?2:0;
acts[index].act();
以此類推,一個index最多可以對應32組布林值判斷。
但其實這種豆技巧反映的是「位元運算」的應用。「位元運算」可以應用的地方還很多,而且不是所有人都喜歡用Strategy模式來解決問題。
更重要的是...會害專案死掉的「布林值判斷」並不在「寫太多」,而在於「寫太雜」。
像下面這種情況一旦發生,後續維護升級擴充就很難成功:
if(a){
funcA();
funcB();
funcD();
}
else{
funcC();
funcB();
funcA();
}
funcA/funB/funC/funcD只是為了方便表達,但如果是尚未集合成函數的散亂程式碼,只是功能相同而已。
這種寫法還可能會有魔改變化形式,如果我們多增加了b和c的布林值...
if(a){
funcA();
if(b)
funcB();
funcD();
}
else{
funcC();
if(b)
funcB();
funcA();
}
維護這種程式碼最大的阻礙,首先在於「程式碼又多又長」,所以後續維護的人經常無法看出結構上可以簡單歸類,所以就不知道funcA同時存在於兩個地方,結果修改了其中一處,但另一處卻沒修改到,結果徒增測試所需要耗費的資源。
(這是根本上「不重構」「不優化」造成的結果。但現階段不想討論這個。)
優化、避免這種寫法出現的技巧,其實是類似的。主要就是避免因為使用「if...else」造成「程式碼出現分支」。
先不要使用「Act」這樣的介面,就單純地看一下沒有分支的程式碼長什麼樣...
if(a){ funcA();}
else{ funcC();}
if(b){ funcB();}
if(a){ funcD();}
else{ funcA();}
雖然還是使用了「if...else」,但程式碼被很清楚的區隔開,後續會比較好閱讀,發現funcA段的程式碼重複、可以獨立成一個函數的機會也能高很多。
(雖然這種寫法的好處很明顯,但我也看過有些工程師堅持「這種寫法不直覺」而否定這種寫法。)
比起測試一個工程師是否懂「MVVM在官方網頁的定義」「某某元件在官方API的解釋」,「能理解」或「能使用」這種豆技巧是否更能反映一個工程師的能力?(先不提程式套件的知識,至少在建構程式思維的靈活度上,這些問題更有指標性才對。)