7. 注釋

2018-02-24 15:11 更新

注釋雖然寫(xiě)起來(lái)很痛苦, 但對(duì)保證代碼可讀性至關(guān)重要. 下面的規(guī)則描述了如何注釋以及在哪兒注釋. 當(dāng)然也要記住: 注釋固然很重要, 但最好的代碼本身應(yīng)該是自文檔化. 有意義的類(lèi)型名和變量名, 要遠(yuǎn)勝過(guò)要用注釋解釋的含糊不清的名字.

你寫(xiě)的注釋是給代碼讀者看的: 下一個(gè)需要理解你的代碼的人. 慷慨些吧, 下一個(gè)人可能就是你!

7.1. 注釋風(fēng)格

Tip

使用 ///* */, 統(tǒng)一就好.

///* */ 都可以; 但 // 常用. 要在如何注釋及注釋風(fēng)格上確保統(tǒng)一.

7.2. 文件注釋

Tip

在每一個(gè)文件開(kāi)頭加入版權(quán)公告, 然后是文件內(nèi)容描述.

法律公告和作者信息:
每個(gè)文件都應(yīng)該包含以下項(xiàng), 依次是:

  • 版權(quán)聲明 (比如, Copyright 2008 Google Inc.)
  • 許可證. 為項(xiàng)目選擇合適的許可證版本 (比如, Apache 2.0, BSD, LGPL, GPL)
  • 作者: 標(biāo)識(shí)文件的原始作者.

如果你對(duì)原始作者的文件做了重大修改, 將你的信息添加到作者信息里. 這樣當(dāng)其他人對(duì)該文件有疑問(wèn)時(shí)可以知道該聯(lián)系誰(shuí).

文件內(nèi)容:
緊接著版權(quán)許可和作者信息之后, 每個(gè)文件都要用注釋描述文件內(nèi)容.

通常, .h 文件要對(duì)所聲明的類(lèi)的功能和用法作簡(jiǎn)單說(shuō)明. .cc 文件通常包含了更多的實(shí)現(xiàn)細(xì)節(jié)或算法技巧討論, 如果你感覺(jué)這些實(shí)現(xiàn)細(xì)節(jié)或算法技巧討論對(duì)于理解 .h 文件有幫助, 可以該注釋挪到 .h, 并在 .cc 中指出文檔在 .h.

不要簡(jiǎn)單的在 .h.cc 間復(fù)制注釋. 這種偏離了注釋的實(shí)際意義.

7.3. 類(lèi)注釋

Tip

每個(gè)類(lèi)的定義都要附帶一份注釋, 描述類(lèi)的功能和用法.

// Iterates over the contents of a GargantuanTable.  Sample usage:
//    GargantuanTable_Iterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;
class GargantuanTable_Iterator {
    ...
};

如果你覺(jué)得已經(jīng)在文件頂部詳細(xì)描述了該類(lèi), 想直接簡(jiǎn)單的來(lái)上一句 “完整描述見(jiàn)文件頂部” 也不打緊, 但務(wù)必確保有這類(lèi)注釋.

如果類(lèi)有任何同步前提, 文檔說(shuō)明之. 如果該類(lèi)的實(shí)例可被多線(xiàn)程訪問(wèn), 要特別注意文檔說(shuō)明多線(xiàn)程環(huán)境下相關(guān)的規(guī)則和常量使用.

7.4. 函數(shù)注釋

Tip

函數(shù)聲明處注釋描述函數(shù)功能; 定義處描述函數(shù)實(shí)現(xiàn).

函數(shù)聲明:
注釋位于聲明之前, 對(duì)函數(shù)功能及用法進(jìn)行描述. 注釋使用敘述式 (“Opens the file”) 而非指令式 (“Open the file”); 注釋只是為了描述函數(shù), 而不是命令函數(shù)做什么. 通常, 注釋不會(huì)描述函數(shù)如何工作. 那是函數(shù)定義部分的事情.

函數(shù)聲明處注釋的內(nèi)容:

  • 函數(shù)的輸入輸出.
  • 對(duì)類(lèi)成員函數(shù)而言: 函數(shù)調(diào)用期間對(duì)象是否需要保持引用參數(shù), 是否會(huì)釋放這些參數(shù).
  • 如果函數(shù)分配了空間, 需要由調(diào)用者釋放.
  • 參數(shù)是否可以為 NULL.
  • 是否存在函數(shù)使用上的性能隱患.
  • 如果函數(shù)是可重入的, 其同步前提是什么?

舉例如下:

// Returns an iterator for this table. It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
// Iterator iter = table->NewIterator();
// iter->Seek("");
// return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator
GetIterator() const;

但也要避免羅羅嗦嗦, 或做些顯而易見(jiàn)的說(shuō)明. 下面的注釋就沒(méi)有必要加上 “returns false otherwise”, 因?yàn)橐呀?jīng)暗含其中了:

// Returns true if the table cannot hold any more entries.
bool IsTableFull();

注釋構(gòu)造/析構(gòu)函數(shù)時(shí), 切記讀代碼的人知道構(gòu)造/析構(gòu)函數(shù)是干啥的, 所以 “destroys this object” 這樣的注釋是沒(méi)有意義的. 注明構(gòu)造函數(shù)對(duì)參數(shù)做了什么 (例如, 是否取得指針?biāo)袡?quán)) 以及析構(gòu)函數(shù)清理了什么. 如果都是些無(wú)關(guān)緊要的內(nèi)容, 直接省掉注釋. 析構(gòu)函數(shù)前沒(méi)有注釋是很正常的.

函數(shù)定義:
每個(gè)函數(shù)定義時(shí)要用注釋說(shuō)明函數(shù)功能和實(shí)現(xiàn)要點(diǎn). 比如說(shuō)說(shuō)你用的編程技巧, 實(shí)現(xiàn)的大致步驟, 或解釋如此實(shí)現(xiàn)的理由, 為什么前半部分要加鎖而后半部分不需要.

不要.h 文件或其他地方的函數(shù)聲明處直接復(fù)制注釋. 簡(jiǎn)要重述函數(shù)功能是可以的, 但注釋重點(diǎn)要放在如何實(shí)現(xiàn)上.

7.5. 變量注釋

Tip

通常變量名本身足以很好說(shuō)明變量用途. 某些情況下, 也需要額外的注釋說(shuō)明.

類(lèi)數(shù)據(jù)成員:
每個(gè)類(lèi)數(shù)據(jù)成員 (也叫實(shí)例變量或成員變量) 都應(yīng)該用注釋說(shuō)明用途. 如果變量可以接受 NULL-1 等警戒值, 須加以說(shuō)明. 比如:

private:
// Keeps track of the total number of entries in the table.
// Used to ensure we do not go over the limit. -1 means
// that we don't yet know how many entries the table has.
int num_totalentries;

全局變量:
和數(shù)據(jù)成員一樣, 所有全局變量也要注釋說(shuō)明含義及用途. 比如:

// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;

7.6. 實(shí)現(xiàn)注釋

Tip

對(duì)于代碼中巧妙的, 晦澀的, 有趣的, 重要的地方加以注釋.

代碼前注釋:
巧妙或復(fù)雜的代碼段前要加注釋. 比如:

// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
x = (x << 8) + (result)[i];
(
result)[i] = x >> 1;
x &= 1;
}

行注釋:
比較隱晦的地方要在行尾加入注釋. 在行尾空兩格進(jìn)行注釋. 比如:

// If we have enough memory, mmap the data portion too.
mmap_budget = max(0, mmapbudget - index->length());
if (mmap_budget >= datasize && !MmapData(mmap_chunk_bytes, mlock))
return; // Error already logged.

注意, 這里用了兩段注釋分別描述這段代碼的作用, 和提示函數(shù)返回時(shí)錯(cuò)誤已經(jīng)被記入日志.

如果你需要連續(xù)進(jìn)行多行注釋, 可以使之對(duì)齊獲得更好的可讀性:

DoSomething(); // Comment here so the comments line up.
DoSomethingElseThatIsLonger(); // Comment here so there are two spaces between
// the code and the comment.
{ // One space before comment when opening a new scope is allowed,
// thus the comment lines up with the following comments and code.
DoSomethingElse(); // Two spaces before line comments normally.
}

NULL, true/false, 1, 2, 3...:
向函數(shù)傳入 NULL, 布爾值或整數(shù)時(shí), 要注釋說(shuō)明含義, 或使用常量讓代碼望文知意. 例如, 對(duì)比:

Warning

bool success = CalculateSomething(interesting_value,

10,
false,
NULL); // What are these arguments??

和:

bool success = CalculateSomething(interesting_value,
10, // Default base value.
false, // Not the first time we're calling this.
NULL); // No callback.

或使用常量或描述性變量:

const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
kDefaultBaseValue,
kFirstTimeCalling,
null_callback);

不允許:
注意 永遠(yuǎn)不要 用自然語(yǔ)言翻譯代碼作為注釋. 要假設(shè)讀代碼的人 C++ 水平比你高, 即便他/她可能不知道你的用意:

Warning

// 現(xiàn)在, 檢查 b 數(shù)組并確保 i 是否存在,
// 下一個(gè)元素是 i+1.
...        // 天哪. 令人崩潰的注釋.

7.7. 標(biāo)點(diǎn), 拼寫(xiě)和語(yǔ)法

Tip

注意標(biāo)點(diǎn), 拼寫(xiě)和語(yǔ)法; 寫(xiě)的好的注釋比差的要易讀的多.

注釋的通常寫(xiě)法是包含正確大小寫(xiě)和結(jié)尾句號(hào)的完整語(yǔ)句. 短一點(diǎn)的注釋 (如代碼行尾注釋) 可以隨意點(diǎn), 依然要注意風(fēng)格的一致性. 完整的語(yǔ)句可讀性更好, 也可以說(shuō)明該注釋是完整的, 而不是一些不成熟的想法.

雖然被別人指出該用分號(hào)時(shí)卻用了逗號(hào)多少有些尷尬, 但清晰易讀的代碼還是很重要的. 正確的標(biāo)點(diǎn), 拼寫(xiě)和語(yǔ)法對(duì)此會(huì)有所幫助.

7.8. TODO 注釋

Tip

對(duì)那些臨時(shí)的, 短期的解決方案, 或已經(jīng)夠好但仍不完美的代碼使用 TODO 注釋.

TODO 注釋要使用全大寫(xiě)的字符串 TODO, 在隨后的圓括號(hào)里寫(xiě)上你的大名, 郵件地址, 或其它身份標(biāo)識(shí). 冒號(hào)是可選的. 主要目的是讓添加注釋的人 (也是可以請(qǐng)求提供更多細(xì)節(jié)的人) 可根據(jù)規(guī)范的 TODO 格式進(jìn)行查找. 添加 TODO 注釋并不意味著你要自己來(lái)修正.

// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.

如果加 TODO 是為了在 “將來(lái)某一天做某事”, 可以附上一個(gè)非常明確的時(shí)間 “Fix by November 2005”), 或者一個(gè)明確的事項(xiàng) (“Remove this code when all clients can handle XML responses.”).

譯者 (YuleFox) 筆記

  1. 關(guān)于注釋風(fēng)格,很多 C++ 的 coders 更喜歡行注釋, C coders 或許對(duì)塊注釋依然情有獨(dú)鐘, 或者在文件頭大段大段的注釋時(shí)使用塊注釋;
  2. 文件注釋可以炫耀你的成就, 也是為了捅了簍子別人可以找你;
  3. 注釋要言簡(jiǎn)意賅, 不要拖沓冗余, 復(fù)雜的東西簡(jiǎn)單化和簡(jiǎn)單的東西復(fù)雜化都是要被鄙視的;
  4. 對(duì)于 Chinese coders 來(lái)說(shuō), 用英文注釋還是用中文注釋, it is a problem, 但不管怎樣, 注釋是為了讓別人看懂, 難道是為了炫耀編程語(yǔ)言之外的你的母語(yǔ)或外語(yǔ)水平嗎;
  5. 注釋不要太亂, 適當(dāng)?shù)目s進(jìn)才會(huì)讓人樂(lè)意看. 但也沒(méi)有必要規(guī)定注釋從第幾列開(kāi)始 (我自己寫(xiě)代碼的時(shí)候總喜歡這樣), UNIX/LINUX 下還可以約定是使用 tab 還是 space, 個(gè)人傾向于 space;
  6. TODO 很不錯(cuò), 有時(shí)候, 注釋確實(shí)是為了標(biāo)記一些未完成的或完成的不盡如人意的地方, 這樣一搜索, 就知道還有哪些活要干, 日志都省了.
以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)