從去年到今年,陸陸續續看完了《代碼大全》、《重構》、《代碼整潔之道》、《程序員修煉之道》以及《The Art of Readable Code》,獲益匪淺。下面就分享幾條我贊同並信奉的編程哲學,順便廢話幾句。
本文適合有一定編程經驗的讀者閱讀,高手請輕噴:)
代碼是寫給人看的(Coding for Reading)
請先思考,評價一段代碼優劣最重要的標准是什么?
有個著名的圖,相信大家都見過,講的是Code Review時被罵WTF的次數越少,代碼的質量就越高。這雖然有點無厘頭,但是卻不無道理。
《The Art of Readable Code》中告訴我們,評價一段代碼的質量的最佳標准是可讀性,即別人理解代碼意圖所需要的時間。
可讀性有什么用,我寫出一段代碼,能完成目標,能通過測試不就行了嗎?
不管你是團隊開發,還是一個人單干,只要項目還在運作,代碼總是要有人來維護的。如果有一天你不在或者離開了,別人應該能很輕松的看懂你的代碼,而不是猜來猜去,最后還要來問你,甚至棄用整段代碼重新再寫。作為一個負責任的開發者,應該尊重自己的勞動成果,也尊重別人的時間。
當你寫下一段代碼時,腦子里有清晰的邏輯(比如這里必須這么做,因為……這里不能那么做,因為……)。而別人看這段代碼時,腦中是一片空白(這個變量是干什么的?為什么要多加一層判斷?這個數為什么是 2 不是 3 ?),除非能完整復現你當時腦中的邏輯,否則就不能理解這段代碼。
所以我們應該做的是,把寫代碼時腦中想到的邏輯的每一個細節,盡可能地寫到代碼以及注釋中,從而幫助后來的閱讀者快速復原整個邏輯。簡單來說可以有以下的方法:
用常量代替魔術數
在一段代碼中出現 if ( level > 3 ) ...的時候,也許你會想:為什么是 3 不是 4 呢?這就是所謂的魔術數。通過上下文也許你可以判斷出這個 3 是指最高等級,但是可能這段代碼中 3 出現了好幾次。你會迷惑:它們是不是都是一個意思?當我要把最高等級改成 4 的時候,是不是應該修改所有的 3 ?
如果代碼的作者定義一個常量MAX_LEVEL = 3,同時還有一個常量USERS_PER_PAGE = 3,並在不同的地方使用不同的常量,就不會有這樣的混淆了。
使用富含信息的類名、變量名和函數名
doit(); $return = $str2 . ':' . $str3; return $return;
這樣的代碼,簡直就是人工混淆過的。你會發現這些代碼根本不能幫助你理解代碼的含義。
也許你該寫成這樣?
handleError(); $error_message = $error_code . ':' . $error_status; return $error_message;
在注釋中說明一段代碼存在的原因,而不是行為
$('#item').html(''); //清空item的內容
這樣的注釋有意義么?用自然語言重新描述一次代碼的行為,除了徒增維護時的工作量外沒有任何價值。你應該說明為什么這樣做,以供別人看到這段代碼時明白你是怎么想的,並決定如何修改或者對待這段代碼。
$('#item').html(''); //先清空容器的內容,否則可能導致內容重復
寫得越少越好(Less is More)
這個規則的使用性太強了,我簡單說說減少邏輯層次和縮小函數體這兩個方面吧。
減少邏輯層次
當邏輯層次超過三層時,理解這段代碼的難度會急劇上升。我相信誰也不喜歡去讀一個n層括號的表達式,或者面對n層縮進的條件判斷/循環。
對於復雜的表達式,通過提取中間變量來降低表達式的邏輯層次,保證每個表達式的邏輯層次不超過二層。
對於多層條件判斷,大多數情況可以用防御式編程將其簡化成單層的條件判斷,盡早return或者exit。此外,單行的if-else判斷往往可以用三元操作符替換。如果判斷實在太多,也許你該重新設計一下結構了。
縮小函數體
記得有一位語言的創始人說過:“我不喜歡比我的頭還大的函數”。
事實上,大家都喜歡短小精干、一眼就能看到底的代碼。簡潔明快的代碼有助於別人迅速理解代碼的意圖,也方便快速定位問題。如果一個函數要滾動屏幕才能看全,那你往往要不斷地來回滾動,並強迫自己記住一些信息,再返回去看另一部分,這樣做會非常累。
隨着函數體不斷膨脹,理解它所需要的時間隨之增加,出錯的幾率也會大大提升。而且越大的函數,可維護性和復用性越差。當部分代碼邏輯需要修改時,不能快速定位到要修改的位置,也難以確定函數體其他位置是否也需要對應的修改。
當函數尺寸失控時,首先要想到的是,有沒有其他方法,用更少的代碼完成這個任務?能不能用正則表達式?能不能用查表法?有沒有內置的庫函數可以利用?
面對一個無法再簡化的流程,將其拆分成細粒度的步驟,將每個步驟的相關代碼分離出來,提取成子函數,再給子函數起一個漂亮的名字。這樣可以降低理解主流程的難度,在做修改時也可以通過函數名快速定位,而且因為相關的代碼都在一起,不容易漏改。
也許你會懷疑調用函數所造成的性能損失,我想說現在這個時代,手機都馬上四核了……
不要重復(Don't Repeat Yourself)
看過《重構》一書后,我看到代碼中任何重復的地方都如見眼中釘。
重復是萬惡之源,當你發現你在對代碼的不同部分進行同樣的修改時就要警惕了。改的地方越多,就越可能出錯。也許忘了改一個地方,也許錯改或者刪除了周邊的代碼……永遠不要讓這種事情發生!
將重復的代碼提煉成子函數
$('#count').text( + $('#count').text() + 1 ); ... $('#count').text( + $('#count').text() + 1 ); ...
如果一段相同的代碼出現兩次,基本上你還會第三次用到它,所以很有必要將其提煉成子函數。這樣不僅可以減少代碼量,還可以降低維護的難度。
countPlusOne(); ... countPlusOne(); ... function countPlusOne() { $('#count').text( + $('#count').text() + 1 ); //以后修改只用改這里就好了 }
如果有幾段代碼很相似,往往可以提取其共性邏輯,使用不同的調用參數進行區分。
countChange( +1 ); ... countChange( -1 ); ... function countChange( change ) { $('#count').text( + $('#count').text() + change ); }
用循環減少重復
$item[ 'id' ] = $_POST[ 'id' ]; $item[ 'name' ] = $_POST[ 'name' ]; $item[ 'mail' ] = $_POST[ 'mail' ]; $item[ 'qq' ] = $_POST[ 'qq' ];
如果有大塊邏輯雷同只有一兩個地方更改的代碼,往往可以用循環來解決:
foreach ( array( 'id', 'name', 'mail', 'qq' ) as $key ) { $item[ $key ] = $_POST[ $key ]; }