在之前一節里,我提到了自己寫的代碼里面很少出現只有一個分支的if語句。我寫出的if語句,大部分都有兩個分支,所以我的代碼很多看起來是這個樣子:
~~~
if (...) {
if (...) {
...
return false;
} else {
return true;
}
} else if (...) {
...
return false;
} else {
return true;
}
~~~
使用這種方式,其實是為了無懈可擊的處理所有可能出現的情況,避免漏掉corner case。每個if語句都有兩個分支的理由是:如果if的條件成立,你做某件事情;但是如果if的條件不成立,你應該知道要做什么另外的事情。不管你的if有沒有else,你終究是逃不掉,必須得思考這個問題的。
很多人寫if語句喜歡省略else的分支,因為他們覺得有些else分支的代碼重復了。比如我的代碼里,兩個else分支都是`return true`。為了避免重復,他們省略掉那兩個else分支,只在最后使用一個`return true`。這樣,缺了else分支的if語句,控制流自動“掉下去”,到達最后的`return true`。他們的代碼看起來像這個樣子:
~~~
if (...) {
if (...) {
...
return false;
}
} else if (...) {
...
return false;
}
return true;
~~~
這種寫法看似更加簡潔,避免了重復,然而卻很容易出現疏忽和漏洞。嵌套的if語句省略了一些else,依靠語句的“控制流”來處理else的情況,是很難正確的分析和推理的。如果你的if條件里使用了`&&`和`||`之類的邏輯運算,就更難看出是否涵蓋了所有的情況。
由于疏忽而漏掉的分支,全都會自動“掉下去”,最后返回意想不到的結果。即使你看一遍之后確信是正確的,每次讀這段代碼,你都不能確信它照顧了所有的情況,又得重新推理一遍。這簡潔的寫法,帶來的是反復的,沉重的頭腦開銷。這就是所謂“面條代碼”,因為程序的邏輯分支,不是像一棵枝葉分明的樹,而是像面條一樣繞來繞去。
另外一種省略else分支的情況是這樣:
~~~
String s = "";
if (x < 5) {
s = "ok";
}
~~~
寫這段代碼的人,腦子里喜歡使用一種“缺省值”的做法。`s`缺省為null,如果x<5,那么把它改變(mutate)成“ok”。這種寫法的缺點是,當`x<5`不成立的時候,你需要往上面看,才能知道s的值是什么。這還是你運氣好的時候,因為s就在上面不遠。很多人寫這種代碼的時候,s的初始值離判斷語句有一定的距離,中間還有可能插入一些其它的邏輯和賦值操作。這樣的代碼,把變量改來改去的,看得人眼花,就容易出錯。
現在比較一下我的寫法:
~~~
String s;
if (x < 5) {
s = "ok";
} else {
s = "";
}
~~~
這種寫法貌似多打了一兩個字,然而它卻更加清晰。這是因為我們明確的指出了`x<5`不成立的時候,s的值是什么。它就擺在那里,它是`""`(空字符串)。注意,雖然我也使用了賦值操作,然而我并沒有“改變”s的值。s一開始的時候沒有值,被賦值之后就再也沒有變過。我的這種寫法,通常被叫做更加“函數式”,因為我只賦值一次。
如果我漏寫了else分支,Java編譯器是不會放過我的。它會抱怨:“在某個分支,s沒有被初始化。”這就強迫我清清楚楚的設定各種條件下s的值,不漏掉任何一種情況。
當然,由于這個情況比較簡單,你還可以把它寫成這樣:
~~~
String s = x < 5 ? "ok" : "";
~~~
對于更加復雜的情況,我建議還是寫成if語句為好。