1. 程式人生 > >4. 我使用的C/C++編寫規範之註釋

4. 我使用的C/C++編寫規範之註釋

XML mit 避免 ans call 成熟 上一個 cati 查找

閑來無事,把自用的C/C++編程規範整理一下。內容主要來自Google與華為,並參考了一點微軟。


  註釋雖然寫起來很痛苦,但對保證代碼可讀性至為重要,下面的規則描述了應該註釋什麽、註釋在哪兒。當然也要記住,註釋的確很重要,但最好的代碼本身就是文檔(self-documenting),類型和變量命名意義明確要比通過註釋解釋模糊的命名好得多。
  註釋是為別人(下一個需要理解你的代碼的人)而寫的,認真點吧,那下一個人可能就是你!
 
1.通用規則
  1)除了文件頭使用/ /,其余地方一律使用//;
  2)將註釋縮進為被其描述的代碼的同一級;
  3)在註釋中使用首字母大寫(用英語註釋時)的完整的句子,以及適當的標點符號和拼寫;

  4)一定不要讓註釋僅僅是重復代碼;
  5)註釋應放在其代碼上方相鄰位置或右方,不可放在下面;
  6)避免在註釋中使用縮寫,除非是業界通用或子系統內標準化的縮寫;
  7)避免在一行代碼或表達式的中間插入註釋;
  8)註釋應考慮程序易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準確的英文表達。對於有外籍員工的,由產品確定註釋語言;
  9)文件頭、函數頭、全局常量變量、類型定義的註釋格式采用doxygen格式。
 
2.文件註釋
  請參考“文件頭”一文。
 
3.類註釋
  每個類的定義要附著描述類的功能和用法的註釋。

// 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 {
  ...
};

  如果你覺得已經在文件頂部詳細描述了該類,想直接簡單的來上一句“完整描述見文件頂部”的話,還是多少在類中加點註釋吧。
  如果類有任何同步前提(synchronization assumptions),文檔說明之。如果該類的實例可被多線程訪問,使用時務必註意文檔說明。
 
4.函數註釋
  函數聲明處註釋描述函數功能,定義處描述函數實現。
  函數聲明:
  註釋於聲明之前,描述函數功能及用法,註釋使用描述式("Opens the file")而非指令式("Open the file");註釋只是為了描述函數而不是告訴函數做什麽。通常,註釋不會描述函數如何實現,那是定義部分的事情。

  函數聲明處註釋的內容:
  1)inputs(輸入)及outputs(輸出);
  2)對類成員函數而言:函數調用期間對象是否需要保持引用參數,是否會釋放這些參數;
  3)如果函數分配了空間,需要由調用者釋放;
  4)參數是否可以為NULL;
  5)是否存在函數使用的性能隱憂(performance implications);
  6)如果函數是可重入的(re-entrant),其同步前提(synchronization assumptions)是什麽?
  舉例如下:

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

  但不要有無謂冗余或顯而易見的註釋,下面的註釋就沒有必要加上“returns false otherwise”,因為已經暗含其中了:

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

  註釋構造/析構函數時,記住,讀代碼的人知道構造/析構函數是什麽,所以“destroys this object”這樣的註釋是沒有意義的。說明構造函數對參數做了什麽(例如,是否是指針的所有者)以及析構函數清理了什麽,如果都是無關緊要的內容,直接省掉註釋,析構函數前沒有註釋是很正常的。
  函數定義:
  每個函數定義時要以註釋說明函數功能和實現要點,如使用的漂亮代碼、實現的簡要步驟、如此實現的理由、為什麽前半部分要加鎖而後半部分不需要。
  不要從.h 文件或其他地方的函數聲明處直接復制註釋,簡要說明函數功能是可以的,但重點要放在如何實現上。
 
5.變量註釋
  通常變量名本身足以很好說明變量用途,特定情況下,需要額外註釋說明。
  類數據成員:
  每個類數據成員(也叫實例變量或成員變量)應註釋說明用途,如果變量可以接受NULL或-1等警戒值(sentinel values),須說明之,如:

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

  全局變量(常量):
  全局變量要有較詳細的註釋,包括對其功能、取值範圍以及存取時註意事項等的說明。例如:

// The ErrorCode when SCCP translate
// Global Title failure, as follows    變量作用、含義
// 0 -SUCCESS    1 -GT Table error
// 2 -GT error Others -no use          變量取值範圍
// only function SCCPTranslate() in
// this modual can modify it, and other
// module can visit it through call
// the function GetGTTransErrorCode()  使用方法
BYTE g_GTTranErrorCode;

 
6.實現註釋
  對於實現代碼中巧妙的、晦澀的、有趣的、重要的地方加以註釋。
  代碼前註釋:
  出彩的或復雜的代碼塊前要加註釋,如:

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

  行註釋:
  比較隱晦的地方要在行尾加入註釋,可以在代碼之後空兩格加行尾註釋,如:

// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, 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();  // Comment here so there are two
                                // spaces between the code and the
                                // comment.
...

  NULL、true/false、1、2、3……:
  向函數傳入、布爾值或整數時,要註釋說明含義,或使用常量讓代碼望文知意,比較一下:

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

  不要:
  註意永遠不要用自然語言翻譯代碼作為註釋,要假設讀你代碼的人C++比你強:

// Now go through the b array and make sure that if i occurs,
// the next element is i+1.
...       // Geez. What a useless comment.

 
7.標點、拼寫和語法
  留意標點、拼寫和語法,寫的好的註釋比差的要易讀的多。
  註釋一般是包含適當大寫和句點(.)的完整的句子,短一點的註釋(如代碼行尾的註釋)可以隨意點,依然要註意風格的一致性。完整的句子可讀性更好,也可以說明該註釋是完整的而不是一點不成熟的想法。
  雖然被別人指出該用分號(semicolon)的時候用了逗號(comma)有點尷尬。清晰易讀的代碼還是很重要的,適當的標點、拼寫和語法對此會有所幫助。
 
8.TODO 註釋
  對那些臨時的、短期的解決方案,或已經夠好但並不完美的代碼使用TODO註釋。
  這樣的註釋要使用全大寫的字符串TODO,後面括號(parentheses)裏加上你的大名、郵件地址等,還可以加上冒號(colon):目的是可以根據統一的TODO格式進行查找:

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

  如果加上是為了在“將來某一天做某事”,可以加上一個特定的時間("Fix by November 2005")或事件("Remove this code when all clients can handle XML responses.")。
  一定不要在已發布的代碼示例中使用TODO待辦註釋。每一個代碼示例都必須完整,在代碼中不能有未完成的任務。

4. 我使用的C/C++編寫規範之註釋