最近有段十分流行的代碼,是從江湖傳聞“身懷八蛋”的鐵道部發言人王勇平同志的一句名言:“不管你們信不信,我反正信了……這是生命的奇跡……它就是發生了”所引申出來的。這段代碼雖然只是在調侃,但是圍繞這段代碼也產生了一些討論(如代碼風格,編程規范等等),在此順手記錄一下,就當無聊罷。
這段代碼是這樣的:
try {if (you.believe(it) == true || you.believe(it) == false){I.believe(it);} } catch (Exception ex) {throw new Exception("It's a miracle!"); } finally {it.justHappened(); }
代碼與原文的對應關系不言自明,從命名風格上看,我們默認其為Java代碼。話題主要是圍繞在if條件的寫法上。
書寫風格
先來看看它的書寫風格問題。我說這段代碼不是老鳥寫的,因為老鳥不會把一個布爾表達式跟true和false直接判斷,而會寫成:
if (you.believe(it) || !you.believe(it))
于是有朋友提出,把布爾表達式跟true或false相比較來的更清晰一些,我表示這話并沒有什么道理,因為這種讀代碼的方式是把視角停留在“數據”層面上:一個布爾表達式返回了布爾型的“數據”,于是把它和另外一個“數據”進行比較。如今的編程都在不斷強調“語義”,“語義”的清晰才是真的清晰。我說Java是一門糟糕的語言,主要原因就是指它的表達能力太差,導致寫出來的代碼體現不出問題的解決方式,讓人們把目光都集中在具體每條語句上了,所謂“見木不見林”。C#等現代語言都在強調“做什么”而不是“怎么做”,語義上就有很大提高了。
回到目前這個具體問題上,if里面的語義是“you.believe(it)”的返回結果,而不是它的值與另外一個布爾常量的比較結果。其實這個觀點我從初中搞信息學競賽時就被老師不斷強調,今天我同樣咨詢了同事,他也贊同我的觀點。如果您還繼續堅持這種寫法不太清晰的話,我只能說“這只是不適應而已,要讓自己適應這類寫法”,很多人還覺得LINQ不清晰呢,小學生還覺得高中數學的解法不清晰呢。
還有朋友認為,作為編碼規范,應該要求這么寫,例如:
if (10 == i)
就是說,把常量寫在比較操作的左邊,并認為“這樣更有普遍意義”。其實這也沒有必要,這個習慣是從C語言時代遺傳下來的“陋習”。在C語言里,如果把常量寫在比較右側,并且一不小心把“比較”操作符(兩個等號)寫成“賦值”操作符(一個等號),也可以編譯通過,但是結果卻大不相同,這給錯誤排查也會帶來許多麻煩。但是,在如今的語言里已經比C語言做的安全多了,所以沒必要制定這種規范。把一種語言的標準帶入另一種語言不叫做“有普遍意義”,只是多余。
代碼含義
然后要談的便是代碼與那句話的“映射”關系了,再來仔細讀一下這個if子句:
if (you.believe(it) || !you.believe(it))
{I.believe(it);
}
從“需求”上來理解,我認為代碼應該保證if內部的代碼一定會執行。那么現在這個需求肯定會滿足嗎?不一定,因為you.believe方法可能是有副作用的:如果它第一次調用返回false,而第二次調用時返回true,則if內部的代碼就會整段略過,這顯然不是鐵道部王發言人的意圖。因此,有同學提議代碼應該是這樣的:
if (true || you.believe(it))
這么做的確可以忽略you.believe(it)的結果,因為它已經被短路了根本不會執行。可能它也能滿足需求,但我想更合理的做法可能應該是:
if (you.believe(it) || true)
這段代碼與之前的區別就在于you.believe(it)一定會被調用一次,但是無所謂其結果是如何,這充分符合天朝某些部門喜歡裝摸作樣“咨詢民意”的狀況。
擴展思考
最后再來一道擴展思考題吧:有人把“你愛,或者不愛我,愛就在那里,不增不減”寫成了一段C#代碼:
if (you.Love(me) || !you.Love(me))
{love++;love--;
}
有人說,這段代碼的if條件本身應該被編譯器優化掉,因此會直接執行if內部的代碼。還有人說,if內部的代碼也會被編譯器優化掉。您怎么看,為什么呢?