別給糟糕的代碼添加注釋——重新寫吧陨溅。
若編程語言足夠有表達(dá)力终惑,或者我們長于用這些語言來表達(dá)意圖,就不那么需要注釋——也許根本不需要门扇。
注釋的恰當(dāng)用法是彌補(bǔ)我們在用代碼表達(dá)意圖時(shí)遭遇的失敗雹有。如果你發(fā)現(xiàn)自己需要寫注釋,再想想看是否有辦法通過代碼來表達(dá)臼寄。
本文不提倡寫太多注釋霸奕,能用代碼表達(dá)就不要寫注釋。
1. 注釋不能美化糟糕的代碼
寫注釋的常見動(dòng)機(jī)之一是糟糕的代碼的存在吉拳。
帶有少量注釋的整潔而有表達(dá)力的代碼质帅,比帶有大量注釋的零碎而復(fù)雜的代碼像樣的多。與其花時(shí)間編寫解釋你搞出的糟糕的代碼的注釋,不如花時(shí)間清潔那堆糟糕的代碼临梗。
2. 用代碼來闡述
有時(shí)候涡扼,代碼本身不足以解釋其行為。幸運(yùn)的是盟庞,很多時(shí)候我們都可以把代碼寫地能解釋自己的行為吃沪。
// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_DAY) &&
(employee.age > 65))
改成這樣怎么樣?
if (employee.isEligibleForFullBenefits())
3. 好注釋
有些注釋是必須的什猖,也是有利的票彪。不過要記住,唯一真正的好注釋是你想辦法不去寫的注釋不狮。
3.1 法律信息
版本及著作權(quán)聲明就是有理由在每個(gè)源文件開頭注釋處放置的內(nèi)容
3.2 提供信息的注釋
// format matched kk:mm:ss EEE, MMM dd, yyyy
Pattern timeMatcher = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");
注釋說明降铸,該正則表達(dá)式意在匹配一個(gè)經(jīng)由SimpleDateFormat.format函數(shù)利用特定格式字符串格式化的時(shí)間和日期。
這類注釋有時(shí)管用摇零,但更好的方式是盡量利用函數(shù)名稱表達(dá)信息推掸。
3.3 對意圖的解釋
有時(shí),注釋不僅提供了有關(guān)實(shí)現(xiàn)的有用信息驻仅,而且還提供了某個(gè)決定后面的意圖谅畅。
下面的例子,你也許不同意程序員給這個(gè)問題提供的解決辦法噪服,但至少你知道他想干什么毡泻。
public void testConcurrentAddWidgets() throws Exception {
WidgetBuilder widgetBuilder =
new WidgetBuilder(new Class[] {BoldWidget.class});
String text = "'''bold text'";
ParentWidget parent =
new BoldWidget(new MockWidgetRoot(), text);
AtomicBoolean failFlag = new AtomicBoolean();
failFlag.set(false);
// This is our best attempt to get a race condition
// by creating large number of threads
for (itn i = 0; i < 25000; i++) {
WidgetBuilderThread widgetBuilderThread =
new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
new Thread(widgetBuilderThread).start();
}
assertEquals(false, failFlag.get());
}
3.4 闡釋
有時(shí)候參數(shù)或者返回值晦澀難懂的時(shí)候,用注釋來幫助闡釋其含義就會(huì)有用粘优。但前提是仇味,你考慮一下是否有更好的辦法,在小心的加上注釋雹顺。
3.5 警示
有時(shí)丹墨,用于警示其他程序員會(huì)出現(xiàn)某種后果的注釋也是有用的。
public static SimpleDateFormat makeStandardHttpDateFormat() {
// SimpleDateFormat is not thread safe
// so we need to create each instance independently.
SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df;
}
上面例子也許有更好的解決辦法无拗,不過這個(gè)注釋卻是非常有必要的带到。它能阻止某個(gè)急切的程序員以效率之名使用靜態(tài)初始器。
3.6 TODO注釋
TODO是一種程序員認(rèn)為應(yīng)該做英染,但由于某些原因目前還沒做的工作揽惹。它可能是要提醒刪除某個(gè)不必要的特性,或者要求他人注意某個(gè)問題四康,它可能是懇請別人取個(gè)好名字等等搪搏。
如今大多數(shù)IDE都提供了定位TODO注釋,定期查看這些注釋闪金,刪除不再需要的疯溺,讓代碼整潔论颅。
3.7 放大
注釋可以用來放大某種看起來不合理之物的重要性。
String listItemContent = match.group(3).trim();
// the trim is real important. It removes the starting
// spaces that could cause the item to be recognized
// as another list
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));
3.8 公共API中的Javadoc
如果你在編寫公共API囱嫩,就該為它編寫良好的Javadoc恃疯。
4. 壞注釋
大多數(shù)注釋都屬此類。通常墨闲,壞注釋都是糟糕代碼的支撐或借口今妄,或者對錯(cuò)誤決策的修正,基本上等于程序員自說自話鸳碧。
4.1 喃喃自語
寫一些只有作者自己知道含義的注釋盾鳞,就是喃喃自語。
4.2 多余的注釋
相比代碼本身無法提供更過的信息就是多余的注釋瞻离。比如沒有證明代碼的意義腾仅,也沒有給出代碼的意圖或邏輯。讀這種注釋還不如讀代碼套利。
4.3 誤導(dǎo)性注釋
注釋不夠精確推励,和代碼本身表達(dá)的含義不一致,容易誤導(dǎo)程序員肉迫。
4.4 循規(guī)式注釋
所謂每個(gè)函數(shù)都要有Javadoc或每個(gè)變量都要有注釋的規(guī)矩全然是愚蠢可笑的吹艇。這類注釋突然讓代碼變得散亂,滿口胡言昂拂,令人迷惑不解。
/**
* @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 = durationInMinutes;
cdList.add(cd);
}
4.5 日志式注釋
有人會(huì)在每次編輯代碼時(shí)抛猖,在模塊開始處添加一條注釋格侯。這類注釋就像是一種記錄每次修改的日志。
很久以前财著,在模塊開始處創(chuàng)建并維護(hù)這些記錄還算有道理联四。那時(shí),我們還沒有源代碼控制系統(tǒng)可用撑教。如今朝墩,這種冗長的記錄只會(huì)讓模塊變得凌亂不堪,應(yīng)當(dāng)全部刪除伟姐。
4.6 廢話注釋
對顯然之事喋喋不休收苏,毫無新意。
/**
* Default constructor
*/
protected AnnualDateRule() {
}
/** The day of the month. */
private int dayOfMonth;
/**
* Returns the day of the month
* @return the day of the month
*/
public int getDayOfMonth() {
return dayOfMonth;
}
4.7 可怕的廢話
Javadoc也可能是廢話愤兵。下列Javadoc(摘自某知名開源庫)的目的是什么鹿霸? 答案:無。
/** The name. */
private String name;
/** The version */
private String version;
/** The licenseName */
private String licenseName;
/** The version. */
private String info;
再仔細(xì)讀讀這些注釋秆乳。你是否發(fā)現(xiàn)了剪切-粘貼錯(cuò)誤懦鼠? 如果作者在寫(粘貼)注釋的時(shí)候都沒花心思钻哩,怎么能指望讀者從中受益呢?
4.8 能用函數(shù)或變量時(shí)就別用注釋
看看以下代碼概要:
// does the module from the global list <mod> depend on the
// subsystem we are part of ?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
可以改成以下沒有注釋的版本:
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))
4.9 位置標(biāo)記
有時(shí)肛冶,程序員喜歡在源代碼中標(biāo)記某個(gè)特別位置街氢。例如:
// Actions //////////////////////////////////////////
把特定函數(shù)放在這種標(biāo)記下面,多數(shù)時(shí)候?qū)崒贌o理睦袖。雞零狗碎珊肃,理當(dāng)刪除——特別是尾部那一長串無用的斜杠。
這么說吧扣泊,如果標(biāo)記欄不多近范,就會(huì)顯而易見。盡量少用這中標(biāo)記欄延蟹。如果濫用评矩,代碼就會(huì)沉沒在背景噪音中,被忽略掉阱飘。
4.10 括號后的注釋
有時(shí)斥杜,程序員會(huì)在括號后面放置特殊的注釋,盡管這對于含有深度嵌套結(jié)構(gòu)的長函數(shù)可能有意義沥匈,但只會(huì)給我們更愿意編寫的短小蔗喂、封裝的函數(shù)帶來混亂。如果你發(fā)現(xiàn)自己想標(biāo)記右括號高帖,其實(shí)應(yīng)該做的是縮短函數(shù)缰儿。
4.11 歸屬于署名
/* Added by Rick */
版本控制系統(tǒng)非常善于記住是誰在何時(shí)添加了什么。沒必要用小小的簽名搞臟代碼散址。
4.12 注釋掉的代碼
直接把代碼注釋掉是討厭的做法乖阵。別這么干!
InputStreamResponse response = new InputStreamResponse();
resonpse.setBody(formatter.getResultStream(), formatter.getByteCount());
// InputStream resultsStream = formatter.getResultStream();
// StreamReader reader = new StreamReader(resultStream);
// response.setContent(reader.read(formatter.getByteCount()));
其他人不敢刪除注釋掉的代碼预麸。他們會(huì)想瞪浸,代碼依然放在那兒,一定有其原因吏祸,而且這段代碼很重要对蒲,不能刪除。注釋掉的代碼堆積在一起贡翘,就像破舊瓶底的渣滓一般蹈矮。
曾經(jīng)有一段時(shí)間,注釋掉代碼可能有用床估。但我們已經(jīng)擁有優(yōu)秀的源代碼控制系統(tǒng)含滴,這些系統(tǒng)可以為我們記住不要的代碼。我們無需再用注釋來標(biāo)記丐巫,刪掉即可谈况,它們丟不了勺美。我擔(dān)保。
4.13 HTML注釋
源碼中包含HTML注釋讓人討厭碑韵,難以閱讀赡茸。
4.14 非本地信息
假如你一定要寫注釋,請確保它描述了離它最近的代碼祝闻。別在本地注釋的上下文環(huán)境中包含出系統(tǒng)級的信息占卧。
下面的例子,除了可怕的冗余之外联喘,還包含了系統(tǒng)級的默認(rèn)端口信息华蜒。但是這個(gè)函數(shù)完全沒控制那個(gè)所謂的默認(rèn)值。假如那個(gè)值更改了豁遭,無法擔(dān)保這個(gè)注釋也會(huì)跟著修改叭喜。
/**
* port on which fitnesse would run. Default to <b>8082</b>
* @param fitnessePort
*/
public void setFitnessePort(int fitnessPort) {
this.fitnessePort = fitnessePort;
}
4.15 信息過多
別在注釋中添加有趣的歷史性話題或者無關(guān)的細(xì)節(jié)描述。
4.16 不明顯的聯(lián)系
注釋及其描述的代碼之間的聯(lián)系應(yīng)該是顯而易見蓖谢。如果你不嫌麻煩要寫注釋捂蕴,至少讓讀者能看著注釋和代碼,并且理解注釋所談何物闪幽。
以來自Apache公共庫的這段注釋為例
/**
* start with an array that is big enough to hold all the pixels
* (plus filter bytes), and an extra 200 bytes for header info
*/
this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
過濾字節(jié)是什么鬼啥辨? 與那個(gè)+1有關(guān)系嗎?或與*3有關(guān)系盯腌? 還是與二者皆有關(guān)系溉知? 注釋的作用是解釋未能自行解釋的代碼。如果注釋本身還需要解釋腕够,就太遺憾了着倾。
4.17 函數(shù)頭
短函數(shù)不需要太多描述。為只做一件事的短函數(shù)取個(gè)好名字燕少,通常要比寫函數(shù)頭注釋要好。
4.18 非公共代碼中的Javadoc
Javadoc對于公共API非常有用蒿囤,但對于不打算做公共用途的代碼就令人厭惡了客们。為系統(tǒng)中的類和函數(shù)生成Javadoc頁并非總有用,而Javadoc注釋額外的形式要求幾乎等同于八股文章材诽。