代碼風格(3)——註釋

一、總述

註釋雖然寫起來很痛苦,但對保證代碼可讀性至關重要。
當然也要記住:註釋固然很重要, 但最好的代碼應當本身就是文檔。有意義的類型名和變量名,要遠勝過要用註釋解釋的含糊不清的名字。

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 日

• 參考:Google 開源項目風格指南——8. 註釋

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章