讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議146:讓註釋正確、清晰、簡潔 >

建議146:讓註釋正確、清晰、簡潔

從我們寫第一個\"Hello World\"程序開始,就被諄諄教導「代碼要有註釋」,而且一加就是好多年,基本上是代碼就有註釋,而且有的還很多,甚至有的是長篇累牘的。我們先來看一些不好的註解習慣。

(1)廢話式註釋

比如這樣的註釋:


/*

*該算法不如某某算法優秀,可以優化,時間太緊,以後再說

*/

public void doSomethong(){

}


想說明什麼?只是表明這個算法需要優化?還是說這是未完成的任務?那可以用TODO標記呀,註釋是給人看的,此類代碼提交上去後,基本上不會再修改它了,除非它出現Bug,或者維護人員碰巧看到這個註釋,然後選擇了優化它——註釋不是留給「撞大運」人員的。

(2)故事式註釋

曾經檢查過一段極品代碼,註釋寫得非常全面,描述的是漢諾塔算法,從漢諾塔的故事(包括最原始的版本和多個變形版本)到算法分析,最後到算法比較和實際應用,寫得那是栩栩如生,而且還不時加入了一些嶄新的網絡用語,幽默而又不失準確,可以這麼說,看完這段註釋基本上對漢諾塔的「前世今生」有了深刻的瞭解,但是我在檢查後的改正意見是:把註釋修改為「實現漢諾塔算法」即可。註釋不是讓你講故事的地方,就這7個字,已經完全可以說明你的代碼了!

我們的代碼是給人看的,但不是給什麼都不懂的外行看的,相信我們代碼的閱讀者一定是具有一定編碼能力的,不是對代碼過敏的「代碼白癡」。

(3)不必要的註釋

有些註釋相對於代碼來說完全沒有必要,算不上是廢話,只能說是多餘的註解,看下面的例子。


class Foo{

//默認值為0

private int num;

//取值

public int getNum(){

return num;

}

//輸入int類型變量,無返回值

public void setNum(int num){

this.num=num;

}

public void doSomething(){

//自增

num++;

}

}


以上四個註解是不必註釋的典型代表(本書的代碼上也有一些類似的註釋,只是為了闡明代碼片段,不用作生產代碼):

第一個默認值完全沒有必要說明,相信代碼的閱讀者這點智商還是有的,他應該明白實例變量初始值為0,即使加上個初始值也完全沒有必要註釋,除非有特殊含義。

第二個註釋在get方法上,如此簡單的代碼,看代碼比看註釋所花費的時間長不了多少,不要低估了代碼閱讀者(很可能就是你,代碼的編寫者)的智商。

第三個註釋描述了輸入和輸出參數類型,相信IDE吧,相信它會這麼「智能化」的提示吧(基本上每個IDE都能實現輸入補全和輸入輸出提示)!不需要我們手工撰寫。這些註釋難道是為那些用記事本編寫代碼的狂人準備的?可是在看到輸入的int類型,輸出的void後,難道他還不能明白嗎?——註釋完全多餘了。

第四個註釋也蔑視了代碼閱讀者的智商,這是編碼的最基本算法,不用註釋。

(4)過時的註釋

註釋與代碼的版本不一致,註釋是1.0版本,而代碼早已竄到了5.0版本,相信讀者對此類註釋深有感觸:代碼一直在升級,但註釋永遠保持不變,直到有一天,某一個「粗心」的傢伙根據註釋修正了一個代碼缺陷,然後發現產生了連鎖的缺陷反應才知道「代碼世界」已經早已發生了變化,而此處的註釋只是描述了最原始的信息。

這類註釋不僅僅會出現在你我的代碼中,同時也會出現在一些非常著名的開源系統中,畢竟註釋不參與運行,只是給人看的,修改代碼而不修改註釋照樣可以運行,添加註釋只是「額外任務」而已。解決此類問題的最好辦法就是保持註釋與代碼同步。

(5)大塊註釋代碼

可能是為了考慮代碼的再次利用,有些大塊註釋掉的代碼仍然保留在生產代碼中,這不是一個好的習慣,大塊註釋代碼不僅僅影響代碼的閱讀性,而且也隔斷了代碼的連貫性,特別是在代碼中的間隔性註釋,更增加了閱讀的難度,會使Bug隱藏得更深。

此類註釋代碼完全可以使用版本管理來實現,而不是在生產代碼中出現。這裡要注意的是,如果代碼臨時不用(可能在下一版本中使用,或者在生產版本固化前可能會被使用),可以通過註釋來解決,如果是廢棄(在生產版本上肯定不用該代碼),則應該完全刪除掉。

(6)流水賬式的註釋

比如有這樣的註釋:


/**

*2010-09-16張三創建該類,實現XX算法

*2010-10-28李四修正算法中的XXXX缺陷

*2010-11-30李四重構了XXX方法

*2011-02-06王五刪除了XXXX無用方法

*2011-04-08馬六優化了XXX的性能

*/

class Foo{

}


相信讀者看到過很多這樣的註釋,而且深以為這樣的註釋是一種好的方式,如果沒有版本管理工具,這確實是一種非常好的註釋,可以很清晰地看出該類的變化歷程,但是有了版本管理工具,此類註釋就不應該出現在這裡了,應該出現在版本提交的註釋上,版本管理可以更加清晰地瀏覽此類變更歷程。

(7)專為JavaDoc編寫的註釋

在註釋中加入過多的HTML標籤,可以使生成的JavaDoc文檔格式整齊美觀,這沒錯,但問題是代碼中的註釋是給人看的,如果只考慮JavaDoc的閱讀者,那代碼的閱讀者(很可能就是一年後的你)就很難看懂代碼上的註釋了,能做的辦法就是生成JavaDoc文檔,然後文檔和代碼分開來閱讀,這是一種讓人迅速崩潰的「絕世良藥」。

建議在註釋中只保留<p>、<code>等幾個常用的標籤,不要增加<font>、<table>、<p>等標籤。

解釋了這麼多不好的註釋,那好的註釋應該是什麼樣子的呢?好的註釋首先要求正確,註釋與代碼意圖吻合;其次要求清晰,格式統一,文字準確;最後要求簡潔,說明該說明的,惜字如金,拒絕不必要的註釋,如下類型的註釋就是好的註釋:

(1)法律版權信息

這是我們在閱讀源代碼時經常看到的,一般都是指向同一個法律版權聲明的。

(2)解釋意圖的註釋

說明為什麼要這樣做,而不是怎麼做的,比如解決了哪個Bug,方法過時的原因是什麼。像這樣一個註釋就是好的:


public class BasicDataSource implements DataSource{

static{

//Attempt to prevent deadlocks-see DBCP-272

DriverManager.getDrivers();

}

}


(3)警示性註釋

這類註釋是我們經常缺乏的,或者是經常忽視的(即使有了,也常常是與代碼版本不匹配),比如可以在註釋中提示此代碼的缺陷,或者它在不同操作系統上的表現,或者警告後續人員不要隨意修改,例如tomcat的源碼org.apache.tomcat.jni.Global中有這樣一段註釋:


public class Global{

/**

*Note:it is important that the APR_STATUS_IS_EBUSY(s)

*macro be used to determine

*if the return value was APR_EBUSY, for portability reasons.

*@param mutex the mutex on which to attempt the lock acquiring.

*/

public static native int trylock(long mutex);

}


(4)TODO註釋

對於一些未完成的任務,則增加上TODO提示,並標明是什麼事情沒有做完,以方便下次看到這個TODO標記時還能記憶起要做什麼事情,比如在DBCP源代碼中有這樣的TODO註釋:


public class DelegatingStatement extends……implements……{

/*

*Note was protected prior to JDBC 4

*TODO Consider adding build fags to make this protected

*unless we are using JDBC 4.

*/

public boolean isClosed()throws SQLException{

return_closed;

}

}


註釋只是代碼閱讀的輔助信息,如果代碼的表達能力足夠清晰,根本就不需要註釋,註釋能夠幫助我們更好地理解代碼,但它所重視的是質量而不是數量。如果一段代碼寫得很糟糕,即使註解寫得再漂亮,也不能解決腐爛代碼帶來的種種問題,記住,註釋不是美化劑,不能美化你的代碼,它只是一副催化劑,可以讓優秀的代碼更加優秀,讓拙劣的代碼更加腐朽。

注意 註釋不是美化劑,而是催化劑,或為優秀加分,或為拙劣減分。