一、總述
註釋雖然寫起來很痛苦,但對保證代碼可讀性至關重要。
當然也要記住:註釋固然很重要, 但最好的代碼應當本身就是文檔。有意義的類型名和變量名,要遠勝過要用註釋解釋的含糊不清的名字。
1.1 註釋風格
使用 //
或 /* */
都可以;但 //
更常用
二、文件註釋
在每一個文件開頭加入版權公告
/**===========================================================================
Copyright (C) XXXX All rights reserved.
@file user_udp.c
@brief 本文件用於UDP通訊接口
@author Leung Man-Wah
@version r0.1
@date 2019/07/04
@license XXXX
----------------------------------------------------------------------------
Remark: (備註描述)
----------------------------------------------------------------------------
History
----------------------------------------------------------------------------
<Date> | <Version> | <Author> | <Description>
-------------|-----------|----------------|---------------------------------
2019/07/04 | r0.1 | Leung Man-Wah | 創建
-------------|-----------|----------------|---------------------------------
| | |
-------------|-----------|----------------|---------------------------------
| | |
-------------|-----------|----------------|---------------------------------
| | |
============================================================================*/
2.1 法律公告和作者信息
每個文件都應該包含許可證引用。爲項目選擇合適的許可證版本。(比如, Apache 2.0, BSD, LGPL, GPL)
如果你對原始作者的文件做了重大修改,請考慮刪除原作者信息。
2.2 文件內容
如果一個 .h
文件聲明瞭多個概念,則文件註釋應當對文件的內容做一個大致的說明,同時說明各概念之間的聯繫。一個一到兩行的文件註釋就足夠了,對於每個概念的詳細文檔應當放在各個概念中,而不是文件註釋中。
三、類註釋
每個類的定義都要附帶一份註釋,描述類的功能和用法,除非它的功能相當明顯。
// 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
和 .cc
文件中),此時,描述類用法的註釋應當和接口定義放在一起,描述類的操作和實現的註釋應當和實現放在一起。
四、函數註釋
基本上每個函數定義處前都應當加上註釋,描述函數的功能和用途。只有在函數的功能簡單而明顯時才能省略這些註釋(例如,簡單的取值和設值函數)。
函數聲明處註釋的內容:
- 函數的輸入輸出。
- 對類成員函數而言:函數調用期間對象是否需要保持引用參數, 是否會釋放這些參數。
- 函數是否分配了必須由調用者釋放的空間。
- 參數是否可以爲空指針。
- 是否存在函數使用上的性能隱患。
- 如果函數是可重入的,其同步前提是什麼?
/**
@brief UDP發送繼電器狀態
@param deviceType -[in] 設備類型
@param pMacAddr -[in] MAC地址
@param relayStatus -[in] 繼電器狀態
@return 無
*/
void UdpSendRelayStatus(uint8 deviceType, uint8 *pMacAddr, uint8 relayStatus)
{
}
/**
@brief JSON格式封裝設備信息
@param pSendData -[in&out] 要封裝的發送數據
@return 無
*/
static void jsonPackageDeviceInfo(char *pSendData)
{
}
註釋函數重載時,註釋的重點應該是函數中被重載的部分,而不是簡單的重複被重載的函數的註釋。多數情況下,函數重載不需要額外的文檔,因此也沒有必要加上註釋。
註釋構造/析構函數時,切記讀代碼的人知道構造/析構函數的功能,所以 “銷燬這一對象” 這樣的註釋是沒有意義的。你應當註明的是註明構造函數對參數做了什麼 (例如,是否取得指針所有權) 以及析構函數清理了什麼。如果都是些無關緊要的內容,直接省掉註釋。析構函數前沒有註釋是很正常的。
五、變量註釋
通常變量名本身足以很好說明變量用途。某些情況下,也需要額外的註釋說明。
5.1 類數據成員
每個類數據成員 (也叫實例變量或成員變量) 都應該用註釋說明用途。如果有非變量的參數(例如特殊值,數據成員之間的關係,生命週期等)不能夠用類型與變量名明確表達,則應當加上註釋。然而,如果變量類型與變量名已經足以描述一個變量,那麼就不再需要加上註釋。
特別地,如果變量可以接受 NULL
或 -1
等警戒值,須加以說明。比如:
private:
// Used to bounds-check table accesses. -1 means
// that we don't yet know how many entries the table has.
int numTotalEntries;
5.2 全局變量
和數據成員一樣,所有全局變量也要註釋說明含義及用途,以及作爲全局變量的原因。比如:
// The total number of tests cases that we run through in this regression test.
int g_numTestCases = 6;
六、實現註釋
對於代碼中巧妙的,晦澀的,有趣的,重要的地方加以註釋。
6.1 代碼前註釋
巧妙或複雜的代碼段前要加註釋,比如:
// 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;
}
6.2 行註釋
比較隱晦的地方要在行尾加入註釋,在行尾空兩格進行註釋。比如:
// 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(); // 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.
七、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 12345): remove the "Last visitors" feature
如果加 TODO
是爲了在 “將來某一天做某事”,可以附上一個非常明確的時間 “Fix by November 2005”),或者一個明確的事項 (“Remove this code when all clients can handle XML responses.”)。
TODO
很不錯,有時候,註釋確實是爲了標記一些未完成的或完成的不盡如人意的地方,這樣一搜索,就知道還有哪些活要幹,日誌都省了。
• 由 Leung 寫於 2019 年 10 月 28 日