注:正文中的引用是直接引用作者Bob大叔的話,兩條橫線中間的段落的是我自己的觀點,其他大約都可以算是筆記了。
作者Bob大叔在這一節中就注釋展開討論,如果你之前曾經仔細閱讀過一些開源軟件的注釋,你會發現很多本文中將要談到的壞注釋
的例子在很多著名的開源軟件的源代碼中都出現過。作者也清晰的表明是自己的立場,即是「這些注釋都是邪惡的
」。沒錯,一種代碼寫作風格出現在某些著名的開源軟件中,并不代表這些風格就是好的。
Don't comment bad code — rewrite it.
---Brian W. Kernighan and P. J. Plaugher
適當的注釋對于理解代碼的意圖很有幫助,但是不好的代碼卻會使得代碼變得十分雜亂不堪,甚至會對代碼造成傷害。而通常的情況是,大部分的代碼注釋都十分糟糕。
「Clean Code」的作者(Bob大叔)對注釋整體上是持反對意見的,他認為寫代碼就像是寫文章(這里的文章通常指的是記敘文),好的代碼應該是能清晰地自解釋的,所以在好的代碼里不應該出現注釋。來看這段闡述:
所以當你發現自己在某個項目的一個地方不得不使用注釋時,停下來好好想想,看是否還有另一種方法去改變當前的狀況,進而使用代碼(而不是注釋)來解釋你要表達的意思。
注釋并不能彌補代碼的爛
往往如果我們寫了一個模塊,之后發現它結構混亂,代碼本身令人費解,然后我們對自己說「哦,那我加條注釋就好了」。這是不對的,與其把時間花在寫注釋上,還不如花點時間把代碼修改得更好一點。
好注釋的例子
這里列舉了一些「好的注釋」的例子
1. 版權信息
有時必須要把版權信息以注釋的方式寫進代碼里去。
2. 提供信息的注釋
比如代碼3-1中有個很復雜的正則表達式,這時我們需要添加適當的注釋告訴讀者這個表達式匹配的具體格式有哪里。
//代碼3-1
// format matched kk:mm:ss EEE, MMM dd, yyyy Pattern
Pattern timeMatcher = Pattern.compile(
"\\\\d*:\\\\d*:\\\\d* \\\\w*, \\\\w* \\\\d*, \\\\d*");
3. 解釋意圖
有時注釋要表達的不僅僅是代碼實現(implementation)的信息,還有作者使用這種實現方法(implementation)的意圖(intent)。
4. 說明
把一些「本身意圖的表達」不是很清晰的代碼的「真實意圖」表達出來
這條主要是在低級語言中比較適用,在如Java這樣的高級語言的使用中,大部分的情況都可以通過函數封裝、修改命名或其他方式來消除這種注釋的場景的。
5. 對于嚴重后果的警告
比如某個函數的功能是刪除某個重要的數據,且不能恢復,這種情況添加對于此函數的后果的警告注釋是十分必要的。
6. TODO注釋
有時候需要添加TODO注釋來標識對于將來開發工作的規劃,或者解釋為什么「在這里暫時使用了一個不好的實現方法」,或者表達對于某段代碼將來的實現的期許,如代碼3-2中所示。
//代碼3-2
//TODO-MdM these are not needed
// We expect this to go away when we do the checkout model
protected VersionInfo makeVersion() throws Exception {
return null;
}
7. 使某段代碼更醒目
8. 公共API的javadoc
壞代碼
大多數的注釋可以被歸入這一類,它們通常都只是壞代碼的遮羞布。
1. 自言自語
這類注釋通常只有代碼的作者自己能真正的理解其含義,對于其他人來說則是一籌莫展。
所以如果你要在某處寫注釋,一定要表達清楚此注釋的上下文,不然這條注釋對于代碼閱讀者來說就是沒有意義的。
作者在這里又舉了一個FitNesse
的例子(代碼3-3),這個例子中的注釋其實是對于閱讀代碼有幫助的,但是表述的還是不夠清晰,會造成讀者的誤解,搞不清楚。
//代碼3-3
public void loadProperties() {
try {
String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
FileInputStream propertiesStream = new FileInputStream(propertiesPath);
loadedProperties.load(propertiesStream);
} catch (IOException e) {
// No properties files means all defaults are loaded
//
}
}
這里的注釋并沒有表達清楚默認值是在哪里被加載進來的,如果別人或者作者自己在一段時間后要來修改默認值的配置,但他只看到這段注釋,還是要再進一步去代碼里找相應的實現。
2. 冗余注釋
在有的代碼中,代碼本身其實已經寫的很好了,但是開發者還是會在其中添加大量的注釋,去解釋用途或功能,在作者看來,這種注釋是冗余的,同時它們又沒有代碼本身能表達的準確度,無端地增加了代碼的閱讀難度。
//代碼3-4
public abstract class ContainerBase implements Container, Lifecycle, Pipeline, MBeanRegistration, Serializable {
/**
* The processor delay for this component.
*/
protected int backgroundProcessorDelay = -1;
/**
* The lifecycle event support for this component.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
作者在這里拿了Tomcat源碼(代碼3-4)來舉例,可能很多人都看過這樣的代碼,如果我們從Bob大叔的這一整套的編程哲學出發,這種注釋確實是冗余的。
Bob大叔的這條規則其實有些代碼潔癖的感覺。在某種程度上,他說的都是正確的,但是在一般的編程場景中,又很難完全避免對于代碼添加冗余的代碼,因為我們總是不能保證team中每個人還有將來可能維護或使用這段代碼的人都具有這種良好的閱讀代碼的習慣。
3. 令人誤解的注釋
有些注釋會表達的意思和函數的本意是不同的,就會導致代碼的閱讀者對于代碼產生誤解。
4. 強制性注釋
作者認為強制的
對于每個函數
都添加入代碼3-5中所示的javadoc
是一件愚蠢的事情,
代碼3-5
/**
*
* @param title The title of the CD
* @param author The author of the CD
* @param tracks The number of tracks on the CD
* @param durationInMinutes The duration of the CD in minutes
*/
public void addCD(String title, String author,int tracks, int durationInMinutes) {
CD cd = new CD();
cd.title = title;
cd.author = author;
cd.tracks = tracks;
cd.duration = duration;
cdList.add(cd);
}
?
5. 日志性質的注釋
有些人會有這種習慣:每次去編輯某個模塊之后,都會在模塊的最開始加入一段類似日志性質的注釋,在版本控制如此普及的今天,這種注釋是完全沒有必要的。
6. 噪音注釋
這種注釋會讓我們讀起代碼來十分煩躁,你去仔細地閱讀它們吧,它們表達的意思在代碼中都是顯而易見的;你不去閱讀它們吧,又怕會漏掉什么細節。所以這種注釋在寫代碼時應該是盡力避免的。
7. 可怕的噪音
基本同上條
8. 如果可以使用一個函數或者變量,就不要使用注釋。
有些注釋是為了讓讀者能更好地理解代碼要表達的意義,那么這種情況就應該使用合適的函數名和變量名來表示這種意思,而不是寫一個晦澀難懂的函數,然后再寫一堆注釋去解釋你的意圖。
9. 標識位置的注釋
作者認為這類的注釋如果合適的使用,是能給代碼帶來好處的,但是很容易被濫用。
10. 大括號末尾的注釋
有些人習慣在一個大括號的末尾(往往是函數或者if
while
等代碼塊的結尾)添加一些注釋,來方便自己快速定位函數或代碼塊的結尾。但是這種情況,往往是你的函數或者代碼塊設計的不合理,應該通過重構寫出更小、更精簡的函數。
11. 署名注釋
有些人喜歡在完成某一個功能時添加一些注釋來表示「誰在什么時候為什么修改了某些東西」,這種事情還是交給版本控制系統來完成吧。
12. 對于代碼的注釋
//代碼3-6
InputStreamResponse response = new InputStreamResponse();
response.setBody(formatter.getResultStream(), formatter.getByteCount());
// InputStream resultsStream = formatter.getResultStream();
// StreamReader reader = new StreamReader(resultsStream);
// response.setContent(reader.read(formatter.getByteCount()));
在有版本控制軟件的今天,這種對于代碼的注釋是完全沒有必要的,并且對于閱讀代碼帶來了很大的干擾。
13. HTML注釋
在注釋中使用HTML標簽是令人厭惡的,應該完全禁止的行為。
14. 非本地的信息
要保證我們的注釋只對于就近代碼的解釋。
15. 表達信息量太大的注釋
有些注釋會寫的很長很長,把一些歷史討論和不相干的細節都描述下來,這種注釋是非常不利于對于代碼的閱讀的。
16. 表述不清晰的注釋
有些注釋本身就很難理解,更加不適合拿來當注釋。
17. 函數頭
短函數不需要寫任何的描述信息。如果你寫的所有函數都很短,并且他們的名字起的都非常良好(像第一個筆記中講的那樣),那么完全不需要在這種函數的頭部添加注釋。
18. 非公共API的代碼中加入javadoc
這種情況之前討論過,完全沒必要。