從去年到今年,陸陸續(xù)續(xù)看完了《代碼大全》《重構(gòu)》《代碼整潔之道》《程序員修煉之道》以及《The Art of Readable Code》,獲益匪淺。下面就分享幾條我贊同并信奉的編程哲學(xué),順便廢話幾句。

  本文適合有一定編程經(jīng)驗的讀者閱讀,高手請輕噴:)

  代碼是寫給人看的(Coding for Reading)

  請先思考,評價一段代碼優(yōu)劣最重要的標準是什么?

  有個著名的圖,相信大家都見過,講的是Code Review時被罵WTF的次數(shù)越少,代碼的質(zhì)量就越高。這雖然有點無厘頭,但是卻不無道理。

  《The Art of Readable Code》中告訴我們,評價一段代碼的質(zhì)量的最佳標準是可讀性,即別人理解代碼意圖所需要的時間。

  可讀性有什么用,我寫出一段代碼,能完成目標,能通過測試不就行了嗎?

  不管你是團隊開發(fā),還是一個人單干,只要項目還在運作,代碼總是要有人來維護的。如果有一天你不在或者離開了,別人應(yīng)該能很輕松的看懂你的代碼,而不是猜來猜去,最后還要來問你,甚至棄用整段代碼重新再寫。作為一個負責(zé)任的開發(fā)者,應(yīng)該尊重自己的勞動成果,也尊重別人的時間。

  當你寫下一段代碼時,腦子里有清晰的邏輯(比如這里必須這么做,因為……這里不能那么做,因為……)。而別人看這段代碼時,腦中是一片空白(這個變量是干什么的?為什么要多加一層判斷?這個數(shù)為什么是 2 不是 3 ?),除非能完整復(fù)現(xiàn)你當時腦中的邏輯,否則就不能理解這段代碼。

  所以我們應(yīng)該做的是,把寫代碼時腦中想到的邏輯的每一個細節(jié),盡可能地寫到代碼以及注釋中,從而幫助后來的閱讀者快速復(fù)原整個邏輯。簡單來說可以有以下的方法:

  用常量代替魔術(shù)數(shù)

  在一段代碼中出現(xiàn) if ( level > 3 ) ...的時候,也許你會想:為什么是 3 不是 4 呢?這就是所謂的魔術(shù)數(shù)。通過上下文也許你可以判斷出這個 3 是指最高等級,但是可能這段代碼中 3 出現(xiàn)了好幾次。你會迷惑:它們是不是都是一個意思?當我要把最高等級改成 4 的時候,是不是應(yīng)該修改所有的 3 ?

  如果代碼的作者定義一個常量MAX_LEVEL = 3,同時還有一個常量USERS_PER_PAGE = 3,并在不同的地方使用不同的常量,就不會有這樣的混淆了。

  使用富含信息的類名、變量名和函數(shù)名

doit(); $return = $str2 . ':' . $str3; return $return;

  這樣的代碼,簡直就是人工混淆過的。你會發(fā)現(xiàn)這些代碼根本不能幫助你理解代碼的含義。

  也許你該寫成這樣?

handleError(); $error_message = $error_code . ':' . $error_status; return $error_message;

  在注釋中說明一段代碼存在的原因,而不是行為

$('#item').html(''); //清空item的內(nèi)容

  這樣的注釋有意義么?用自然語言重新描述一次代碼的行為,除了徒增維護時的工作量外沒有任何價值。你應(yīng)該說明為什么這樣做,以供別人看到這段代碼時明白你是怎么想的,并決定如何修改或者對待這段代碼。

$('#item').html(''); //先清空容器的內(nèi)容,否則可能導(dǎo)致內(nèi)容重復(fù)

  寫得越少越好(Less is More)

  這個規(guī)則的使用性太強了,我簡單說說減少邏輯層次和縮小函數(shù)體這兩個方面吧。

  減少邏輯層次

  當邏輯層次超過三層時,理解這段代碼的難度會急劇上升。我相信誰也不喜歡去讀一個n層括號的表達式,或者面對n層縮進的條件判斷/循環(huán)。

  對于復(fù)雜的表達式,通過提取中間變量來降低表達式的邏輯層次,保證每個表達式的邏輯層次不超過二層。

  對于多層條件判斷,大多數(shù)情況可以用防御式編程將其簡化成單層的條件判斷,盡早return或者exit。此外,單行的if-else判斷往往可以用三元操作符替換。如果判斷實在太多,也許你該重新設(shè)計一下結(jié)構(gòu)了。

  縮小函數(shù)體

  記得有一位語言的創(chuàng)始人說過:“我不喜歡比我的頭還大的函數(shù)”。

  事實上,大家都喜歡短小精干、一眼就能看到底的代碼。簡潔明快的代碼有助于別人迅速理解代碼的意圖,也方便快速定位問題。如果一個函數(shù)要滾動屏幕才能看全,那你往往要不斷地來回滾動,并強迫自己記住一些信息,再返回去看另一部分,這樣做會非常累。

  隨著函數(shù)體不斷膨脹,理解它所需要的時間隨之增加,出錯的幾率也會大大提升。而且越大的函數(shù),可維護性和復(fù)用性越差。當部分代碼邏輯需要修改時,不能快速定位到要修改的位置,也難以確定函數(shù)體其他位置是否也需要對應(yīng)的修改。

  當函數(shù)尺寸失控時,首先要想到的是,有沒有其他方法,用更少的代碼完成這個任務(wù)?能不能用正則表達式?能不能用查表法?有沒有內(nèi)置的庫函數(shù)可以利用?

  面對一個無法再簡化的流程,將其拆分成細粒度的步驟,將每個步驟的相關(guān)代碼分離出來,提取成子函數(shù),再給子函數(shù)起一個漂亮的名字。這樣可以降低理解主流程的難度,在做修改時也可以通過函數(shù)名快速定位,而且因為相關(guān)的代碼都在一起,不容易漏改。

  也許你會懷疑調(diào)用函數(shù)所造成的性能損失,我想說現(xiàn)在這個時代,手機都馬上四核了……

  不要重復(fù)(Don't Repeat Yourself)

  看過《重構(gòu)》一書后,我看到代碼中任何重復(fù)的地方都如見眼中釘。

  重復(fù)是萬惡之源,當你發(fā)現(xiàn)你在對代碼的不同部分進行同樣的修改時就要警惕了。改的地方越多,就越可能出錯。也許忘了改一個地方,也許錯改或者刪除了周邊的代碼……永遠不要讓這種事情發(fā)生!

  將重復(fù)的代碼提煉成子函數(shù)

$('#count').text( + $('#count').text() + 1 ); ... $('#count').text( + $('#count').text() + 1 ); ...

  如果一段相同的代碼出現(xiàn)兩次,基本上你還會第三次用到它,所以很有必要將其提煉成子函數(shù)。這樣不僅可以減少代碼量,還可以降低維護的難度。

countPlusOne(); ... countPlusOne(); ... function countPlusOne() {     $('#count').text( + $('#count').text() + 1 ); //以后修改只用改這里就好了 }

  如果有幾段代碼很相似,往往可以提取其共性邏輯,使用不同的調(diào)用參數(shù)進行區(qū)分。

countChange( +1 ); ... countChange( -1 ); ... function countChange( change ) {     $('#count').text( + $('#count').text() + change );  }

  用循環(huán)減少重復(fù)

$item[ 'id' ] = $_POST[ 'id' ]; $item[ 'name' ] = $_POST[ 'name' ]; $item[ 'mail' ] = $_POST[ 'mail' ]; $item[ 'qq' ] = $_POST[ 'qq' ];

  如果有大塊邏輯雷同只有一兩個地方更改的代碼,往往可以用循環(huán)來解決:

foreach ( array( 'id', 'name', 'mail', 'qq' ) as $key ) {     $item[ $key ] = $_POST[ $key ]; }