天天看點

C++風格_注釋

注釋雖然寫起來很痛苦, 但對保證代碼可讀性至關重要.

注釋固然很重要, 但最好的代碼應當本身就是文檔.

有意義的類型名和變量名, 要遠勝過要用注釋解釋的含糊不清的名字.

1、注釋風格

使用 // 或 , 統一就好.

說明

// 或 都可以; 但 // 更 常用. 要在如何注釋及注釋風格上確定統一.

2、檔案注釋

在每一個檔案開頭加入版權公告.

檔案注釋描述了該檔案的内容. 如果一個檔案隻聲明, 或實作, 或測試了一個對象, 并且這個對象已經在它的聲明處進行了詳細的注釋, 那麼就沒必要再加上檔案注釋. 除此之外的其他檔案都需要檔案注釋.

說明

法律公告和作者資訊

每個檔案都應該包含許可證引用. 為項目選擇合适的許可證版本.(比如, Apache 2.0, BSD, LGPL, GPL)

如果你對原始作者的檔案做了重大修改, 請考慮删除原作者資訊.(這是Google的指南中的)

檔案内容

如果一個 .h 檔案聲明了多個概念, 則檔案注釋應當對檔案的内容做一個大緻的說明, 同時說明各概念之間的聯系. 一個一到兩行的檔案注釋就足夠了, 對于每個概念的詳細文檔應當放在各個概念中, 而不是檔案注釋中.

不要在 .h 和 .cpp 之間複制注釋, 這樣的注釋偏離了注釋的實際意義.

3、類注釋

每個類的定義都要附帶一份注釋, 描述類的功能和用法, 除非它的功能相當明顯.

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

說明

類注釋應當為讀者了解如何使用與何時使用類提供足夠的資訊, 同時應當提醒讀者在正确使用此類時應當考慮的因素. 如果類有任何同步前提, 請用文檔說明. 如果該類的執行個體可被多線程通路, 要特别注意文檔說明多線程環境下相關的規則和常量使用.

如果你想用一小段代碼示範這個類的基本用法或通常用法, 放在類注釋裡也非常合适.

如果類的聲明和定義分開了(例如分别放在了 .h 和 .cpp 檔案中), 此時, 描述類用法的注釋應當和接口定義放在一起, 描述類的操作和實作的注釋應當和實作放在一起.

4、函數注釋

函數聲明處的注釋描述函數功能; 定義處的注釋描述函數實作.

說明

函數聲明

基本上每個函數聲明處前都應當加上注釋, 描述函數的功能和用途. 隻有在函數的功能簡單而明顯時才能省略這些注釋(例如, 簡單的取值和設值函數). 注釋使用叙述式 (“Opens the file”) 而非指令式 (“Open the file”); 注釋隻是為了描述函數, 而不是指令函數做什麼. 通常, 注釋不會描述函數如何工作. 那是函數定義部分的事情.

函數聲明處注釋的内容:

函數的輸入輸出.

對類成員函數而言: 函數調用期間對象是否需要保持引用參數, 是否會釋放這些參數.

函數是否配置設定了必須由調用者釋放的空間.

參數是否可以為空指針.

是否存在函數使用上的性能隐患.

如果函數是可重入的, 其同步前提是什麼?

舉例如下:

// 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;
           

但也要避免羅羅嗦嗦, 或者對顯而易見的内容進行說明. 下面的注釋就沒有必要加上 “否則傳回 false”, 因為已經暗含其中了:

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

注釋函數重載時, 注釋的重點應該是函數中被重載的部分, 而不是簡單的重複被重載的函數的注釋. 多數情況下, 函數重載不需要額外的文檔, 是以也沒有必要加上注釋.

注釋構造/析構函數時, 切記讀代碼的人知道構造/析構函數的功能, 是以 “銷毀這一對象” 這樣的注釋是沒有意義的. 你應當注明的是注明構造函數對參數做了什麼 (例如, 是否取得指針所有權) 以及析構函數清理了什麼. 如果都是些無關緊要的内容, 直接省掉注釋. 析構函數前沒有注釋是很正常的.

函數定義

如果函數的實作過程中用到了很巧妙的方式, 那麼在函數定義處應當加上解釋性的注釋. 例如, 你所使用的程式設計技巧, 實作的大緻步驟, 或解釋如此實作的理由. 舉個例子, 你可以說明為什麼函數的前半部分要加鎖而後半部分不需要.

不要 從 .h 檔案或其他地方的函數聲明處直接複制注釋. 簡要重述函數功能是可以的, 但注釋重點要放在如何實作上.

5、變量注釋

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

說明

類資料成員

每個類資料成員 (也叫執行個體變量或成員變量) 都應該用注釋說明用途. 如果有非變量的參數(例如特殊值, 資料成員之間的關系, 生命周期等)不能夠用類型與變量名明确表達, 則應當加上注釋. 然而, 如果變量類型與變量名已經足以描述一個變量, 那麼就不再需要加上注釋.

特别地, 如果變量可以接受 NULL 或 -1 等警戒值, 須加以說明. 比如:

private:
 // Used to bounds-check table accesses. -1 means
 // that we don't yet know how many entries the table has.
 int num_total_entries_;
           

全局變量

和資料成員一樣, 所有全局變量也要注釋說明含義及用途, 以及作為全局變量的原因. 比如:

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

6、實作注釋

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

說明

代碼前注釋

巧妙或複雜的代碼段前要加注釋. 比如:

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

行注釋

比較隐晦的地方要在行尾加入注釋. 在行尾空兩格進行注釋. 比如:

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

注意, 這裡用了兩段注釋分别描述這段代碼的作用, 和提示函數傳回時錯誤已經被記入日志.

如果你需要連續進行多行注釋, 可以使之對齊獲得更好的可讀性:

DoSomething();                  // Comment here so the comments line up.
DoSomethingElseThatIsLonger();  // 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.
}
std::vector<string> list{
                    // Comments in braced lists describe the next element...
                    "First item",
                    // .. and should be aligned appropriately.
"Second item"};
DoSomething(); /* For trailing block comments, one space is fine. */
           

函數參數注釋

如果函數參數的意義不明顯, 考慮用下面的方式進行彌補:

如果參數是一個字面常量, 并且這一常量在多處函數調用中被使用, 用以推斷它們一緻, 你應當用一個常量名讓這一約定變得更明顯, 并且保證這一約定不會被打破.

考慮更改函數的簽名, 讓某個 bool 類型的參數變為 enum 類型, 這樣可以讓這個參數的值表達其意義.

如果某個函數有多個配置選項, 你可以考慮定義一個類或結構體以儲存所有的選項, 并傳入類或結構體的執行個體. 這樣的方法有許多優點, 例如這樣的選項可以在調用處用變量名引用, 這樣就能清晰地表明其意義. 同時也減少了函數參數的數量, 使得函數調用更易讀也易寫. 除此之外, 以這樣的方式, 如果你使用其他的選項, 就無需對調用點進行更改.

用具名變量代替大段而複雜的嵌套表達式.

萬不得已時, 才考慮在調用點用注釋闡明參數的意義.

比如下面的示例的對比:

// What are these arguments?
const DecimalNumber product = CalculateProduct(values, , false, nullptr);
和

ProductOptions options;
options.set_precision_decimals();
options.set_use_cache(ProductOptions::kDontUseCache);
const DecimalNumber product =
    CalculateProduct(values, options, /*completion_callback=*/nullptr);
           

哪個更清晰一目了然.

不允許的行為

不要描述顯而易見的現象, 永遠不要 用自然語言翻譯代碼作為注釋, 除非即使對深入了解 C++ 的讀者來說代碼的行為都是不明顯的. 要假設讀代碼的人 C++ 水準比你高, 即便他/她可能不知道你的用意:

你所提供的注釋應當解釋代碼 為什麼 要這麼做和代碼的目的, 或者最好是讓代碼自文檔化.

比較這樣的注釋:

// Find the element in the vector.  <-- 差: 這太明顯了!
auto iter = std::find(v.begin(), v.end(), element);
if (iter != v.end()) {
  Process(element);
}
           

和這樣的注釋:

// Process "element" unless it was already processed.
auto iter = std::find(v.begin(), v.end(), element);
if (iter != v.end()) {
  Process(element);
}
           

自文檔化的代碼根本就不需要注釋. 上面例子中的注釋對下面的代碼來說就是毫無必要的:

if (!IsAlreadyProcessed(element)) {
  Process(element);
}
           

7、标點, 拼寫和文法

注意标點, 拼寫和文法; 寫的好的注釋比差的要易讀的多.

說明

注釋的通常寫法是包含正确大小寫和結尾句号的完整叙述性語句. 大多數情況下, 完整的句子比句子片段可讀性更高. 短一點的注釋, 比如代碼行尾注釋, 可以随意點, 但依然要注意風格的一緻性.

雖然被别人指出該用分号時卻用了逗号多少有些尴尬, 但清晰易讀的代碼還是很重要的. 正确的标點, 拼寫和文法對此會有很大幫助.

8、TODO 注釋

對那些臨時的, 短期的解決方案, 或已經夠好但仍不完美的代碼使用 TODO 注釋.

TODO 注釋要使用全大寫的字元串 TODO, 在随後的圓括号裡寫上你的名字, 郵件位址, bug ID, 或其它身份辨別和與這一 TODO 相關的 issue. 主要目的是讓添加注釋的人 (也是可以請求提供更多細節的人) 可根據規範的 TODO 格式進行查找. 添加 TODO 注釋并不意味着你要自己來修正, 是以當你加上帶有姓名的 TODO 時, 一般都是寫上自己的名字.

// TODO([email protected]): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug ): remove the "Last visitors" feature
           

如果加 TODO 是為了在 “将來某一天做某事”, 可以附上一個非常明确的時間 “Fix by November 2005”), 或者一個明确的事項 (“Remove this code when all clients can handle XML responses.”).

9、棄用注釋

通過棄用注釋(DEPRECATED comments)以标記某接口點已棄用.

您可以寫上包含全大寫的 DEPRECATED 的注釋, 以标記某接口為棄用狀态. 注釋可以放在接口聲明前, 或者同一行.

棄用注釋應當包涵簡短而清晰的指引, 以幫助其他人修複其調用點. 在 C++ 中, 你可以将一個棄用函數改造成一個内聯函數, 這一函數将調用新的接口.

僅僅标記接口為 DEPRECATED 并不會讓大家不約而同地棄用, 您還得親自主動修正調用點(callsites), 或是找個幫手.

繼續閱讀