7. 命名約定
最重要的一致性規則是命名管理. 命名的風格能讓我們在不需要去查找類型聲明的條件下快速地了解某個名字代表的含義: 類型, 變量, 函數, 常量, 宏, 等等, 甚至. 我們大腦中的模式匹配引擎非常依賴這些命名規則.
命名規則具有一定隨意性, 但相比按個人喜好命名, 一致性更重要, 所以無論你認為它們是否重要, 規則總歸是規則.
7.1. 通用命名規則
總述
函數命名, 變量命名, 文件命名要有描述性; 少用縮寫.
說明
盡可能使用描述性的命名, 別心疼空間, 畢竟相比之下讓代碼易于新讀者理解更重要. 不要用只有項目開發者能理解的縮寫, 也不要通過砍掉幾個字母來縮寫單詞.
int price_count_reader; // 無縮寫int num_errors; // "num" 是一個常見的寫法int num_dns_connections; // 人人都知道 "DNS" 是什么int n; // 毫無意義.int nerr; // 含糊不清的縮寫.int n_comp_conns; // 含糊不清的縮寫.int wgc_connections; // 只有貴團隊知道是什么意思.int pc_reader; // "pc" 有太多可能的解釋了.int cstmr_id; // 刪減了若干字母.
注意, 一些特定的廣為人知的縮寫是允許的, 例如用i表示迭代變量和用T表示模板參數.
模板參數的命名應當遵循對應的分類: 類型模板參數應當遵循類型命名的規則, 而非類型模板應當遵循變量命名的規則.
7.2. 文件命名
總述
文件名要全部小寫, 可以包含下劃線 (_) 或連字符 (-), 依照項目的約定. 如果沒有約定, 那么 “_” 更好.
說明
可接受的文件命名示例:
my_useful_class.cc
my-useful-class.cc
myusefulclass.cc
myusefulclass_test.cc//_unittest和_regtest已棄用.
C++ 文件要以.cc結尾, 頭文件以.h結尾. 專門插入文本的文件則以.inc結尾, 參見頭文件自足.
不要使用已經存在于/usr/include下的文件名 (Yang.Y 注: 即編譯器搜索系統頭文件的路徑), 如db.h.
通常應盡量讓文件名更加明確.http_server_logs.h就比logs.h要好. 定義類時文件名一般成對出現, 如foo_bar.h和foo_bar.cc, 對應于類FooBar.
內聯函數必須放在.h文件中. 如果內聯函數比較短, 就直接放在.h中.
7.3. 類型命名
總述
類型名稱的每個單詞首字母均大寫, 不包含下劃線:MyExcitingClass,MyExcitingEnum.
說明
所有類型命名 —— 類, 結構體, 類型定義 (typedef), 枚舉, 類型模板參數 —— 均使用相同約定, 即以大寫字母開始, 每個單詞首字母均大寫, 不包含下劃線. 例如:
// 類和結構體class UrlTable { ...class UrlTableTester { ...struct UrlTableProperties { ...// 類型定義typedef hash_map
7.4. 變量命名
總述
變量 (包括函數參數) 和數據成員名一律小寫, 單詞之間用下劃線連接. 類的成員變量以下劃線結尾, 但結構體的就不用, 如:a_local_variable,a_struct_data_member,a_class_data_member_.
說明
普通變量命名
舉例:
string table_name; // 好 - 用下劃線.string tablename; // 好 - 全小寫.string tableName; // 差 - 混合大小寫
類數據成員
不管是靜態的還是非靜態的, 類數據成員都可以和普通變量一樣, 但要接下劃線.
class TableInfo { ... private: string table_name_; // 好 - 后加下劃線. string tablename_; // 好. static Pool
結構體變量
不管是靜態的還是非靜態的, 結構體數據成員都可以和普通變量一樣, 不用像類那樣接下劃線:
struct UrlTableProperties { string name; int num_entries; static Pool
結構體與類的使用討論, 參考結構體 vs. 類.
7.5. 常量命名
總述
聲明為constexpr或const的變量, 或在程序運行期間其值始終保持不變的, 命名時以 “k” 開頭, 大小寫混合. 例如:
const int kDaysInAWeek = 7;
說明
所有具有靜態存儲類型的變量 (例如靜態變量或全局變量, 參見存儲類型) 都應當以此方式命名. 對于其他存儲類型的變量, 如自動變量等, 這條規則是可選的. 如果不采用這條規則, 就按照一般的變量命名規則.
7.6. 函數命名
總述
常規函數使用大小寫混合, 取值和設值函數則要求與變量名匹配:MyExcitingFunction(),MyExcitingMethod(),my_exciting_member_variable(),set_my_exciting_member_variable().
說明
一般來說, 函數名的每個單詞首字母大寫 (即 “駝峰變量名” 或 “帕斯卡變量名”), 沒有下劃線. 對于首字母縮寫的單詞, 更傾向于將它們視作一個單詞進行首字母大寫 (例如, 寫作StartRpc()而非StartRPC()).
AddTableEntry()DeleteUrl()OpenFileOrDie()
(同樣的命名規則同時適用于類作用域與命名空間作用域的常量, 因為它們是作為 API 的一部分暴露對外的, 因此應當讓它們看起來像是一個函數, 因為在這時, 它們實際上是一個對象而非函數的這一事實對外不過是一個無關緊要的實現細節.)
取值和設值函數的命名與變量一致. 一般來說它們的名稱與實際的成員變量對應, 但并不強制要求. 例如intcount()與voidset_count(intcount).
7.7. 命名空間命名
總述
命名空間以小寫字母命名. 最高級命名空間的名字取決于項目名稱. 要注意避免嵌套命名空間的名字之間和常見的頂級命名空間的名字之間發生沖突.
頂級命名空間的名稱應當是項目名或者是該命名空間中的代碼所屬的團隊的名字. 命名空間中的代碼, 應當存放于和命名空間的名字匹配的文件夾或其子文件夾中.
注意不使用縮寫作為名稱的規則同樣適用于命名空間. 命名空間中的代碼極少需要涉及命名空間的名稱, 因此沒有必要在命名空間中使用縮寫.
要避免嵌套的命名空間與常見的頂級命名空間發生名稱沖突. 由于名稱查找規則的存在, 命名空間之間的沖突完全有可能導致編譯失敗. 尤其是, 不要創建嵌套的std命名空間. 建議使用更獨特的項目標識符 (websearch::index,websearch::index_util) 而非常見的極易發生沖突的名稱 (比如websearch::util).
對于internal命名空間, 要當心加入到同一internal命名空間的代碼之間發生沖突 (由于內部維護人員通常來自同一團隊, 因此常有可能導致沖突). 在這種情況下, 請使用文件名以使得內部名稱獨一無二 (例如對于frobber.h, 使用websearch::index::frobber_internal).
7.8. 枚舉命名
總述
枚舉的命名應當和常量或宏一致:kEnumName或是ENUM_NAME.
說明
單獨的枚舉值應該優先采用常量的命名方式. 但宏方式的命名也可以接受. 枚舉名UrlTableErrors(以及AlternateUrlTableErrors) 是類型, 所以要用大小寫混合的方式.
enum UrlTableErrors { kOK = 0, kErrorOutOfMemory, kErrorMalformedInput,};enum AlternateUrlTableErrors { OK = 0, OUT_OF_MEMORY = 1, MALFORMED_INPUT = 2,};
2009 年 1 月之前, 我們一直建議采用宏的方式命名枚舉值. 由于枚舉值和宏之間的命名沖突, 直接導致了很多問題. 由此, 這里改為優先選擇常量風格的命名方式. 新代碼應該盡可能優先使用常量風格. 但是老代碼沒必要切換到常量風格, 除非宏風格確實會產生編譯期問題.
7.9. 宏命名
總述
你并不打算使用宏, 對吧? 如果你一定要用, 像這樣命名:MY_MACRO_THAT_SCARES_SMALL_CHILDREN.
說明
參考預處理宏; 通常不應該使用宏. 如果不得不用, 其命名像枚舉命名一樣全部大寫, 使用下劃線:
#define ROUND(x) ...#define PI_ROUNDED 3.0
7.10. 命名規則的特例
總述
如果你命名的實體與已有 C/C++ 實體相似, 可參考現有命名策略.
bigopen(): 函數名, 參照open()的形式
uint:typedef
bigpos:struct或class, 參照pos的形式
sparse_hash_map: STL 型實體; 參照 STL 命名約定
LONGLONG_MAX: 常量, 如同INT_MAX
譯者(acgtyrant)筆記
感覺 Google 的命名約定很高明, 比如寫了簡單的類 QueryResult, 接著又可以直接定義一個變量 query_result, 區分度很好; 再次, 類內變量以下劃線結尾, 那么就可以直接傳入同名的形參, 比如TextQuery::TextQuery(std::stringword):word_(word){}, 其中word_自然是類內私有成員.
8. 注釋
注釋雖然寫起來很痛苦, 但對保證代碼可讀性至關重要. 下面的規則描述了如何注釋以及在哪兒注釋. 當然也要記住: 注釋固然很重要, 但最好的代碼應當本身就是文檔. 有意義的類型名和變量名, 要遠勝過要用注釋解釋的含糊不清的名字.
你寫的注釋是給代碼讀者看的, 也就是下一個需要理解你的代碼的人. 所以慷慨些吧, 下一個讀者可能就是你!
8.1. 注釋風格
總述
使用//或/**/, 統一就好.
說明
//或/**/都可以; 但//更常用. 要在如何注釋及注釋風格上確保統一.
8.2. 文件注釋
總述
在每一個文件開頭加入版權公告.
文件注釋描述了該文件的內容. 如果一個文件只聲明, 或實現, 或測試了一個對象, 并且這個對象已經在它的聲明處進行了詳細的注釋, 那么就沒必要再加上文件注釋. 除此之外的其他文件都需要文件注釋.
說明
法律公告和作者信息
每個文件都應該包含許可證引用. 為項目選擇合適的許可證版本.(比如, Apache 2.0, BSD, LGPL, GPL)
如果你對原始作者的文件做了重大修改, 請考慮刪除原作者信息.
文件內容
如果一個.h文件聲明了多個概念, 則文件注釋應當對文件的內容做一個大致的說明, 同時說明各概念之間的聯系. 一個一到兩行的文件注釋就足夠了, 對于每個概念的詳細文檔應當放在各個概念中, 而不是文件注釋中.
不要在.h和.cc之間復制注釋, 這樣的注釋偏離了注釋的實際意義.
8.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和.cc文件中), 此時, 描述類用法的注釋應當和接口定義放在一起, 描述類的操作和實現的注釋應當和實現放在一起.
8.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文件或其他地方的函數聲明處直接復制注釋. 簡要重述函數功能是可以的, 但注釋重點要放在如何實現上.
8.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;
8.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
注意, 這里用了兩段注釋分別描述這段代碼的作用, 和提示函數返回時錯誤已經被記入日志.
如果你需要連續進行多行注釋, 可以使之對齊獲得更好的可讀性:
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
函數參數注釋
如果函數參數的意義不明顯, 考慮用下面的方式進行彌補:
如果參數是一個字面常量, 并且這一常量在多處函數調用中被使用, 用以推斷它們一致, 你應當用一個常量名讓這一約定變得更明顯, 并且保證這一約定不會被打破.
考慮更改函數的簽名, 讓某個bool類型的參數變為enum類型, 這樣可以讓這個參數的值表達其意義.
如果某個函數有多個配置選項, 你可以考慮定義一個類或結構體以保存所有的選項, 并傳入類或結構體的實例. 這樣的方法有許多優點, 例如這樣的選項可以在調用處用變量名引用, 這樣就能清晰地表明其意義. 同時也減少了函數參數的數量, 使得函數調用更易讀也易寫. 除此之外, 以這樣的方式, 如果你使用其他的選項, 就無需對調用點進行更改.
用具名變量代替大段而復雜的嵌套表達式.
萬不得已時, 才考慮在調用點用注釋闡明參數的意義.
比如下面的示例的對比:
// What are these arguments?const DecimalNumber product = CalculateProduct(values, 7, false, nullptr);
和
ProductOptions options;options.set_precision_decimals(7);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);}
8.8. 標點, 拼寫和語法
總述
注意標點, 拼寫和語法; 寫的好的注釋比差的要易讀的多.
說明
注釋的通常寫法是包含正確大小寫和結尾句號的完整敘述性語句. 大多數情況下, 完整的句子比句子片段可讀性更高. 短一點的注釋, 比如代碼行尾注釋, 可以隨意點, 但依然要注意風格的一致性.
雖然被別人指出該用分號時卻用了逗號多少有些尷尬, 但清晰易讀的代碼還是很重要的. 正確的標點, 拼寫和語法對此會有很大幫助.
8.8. TODO 注釋
總述
對那些臨時的, 短期的解決方案, 或已經夠好但仍不完美的代碼使用TODO注釋.
TODO注釋要使用全大寫的字符串TODO, 在隨后的圓括號里寫上你的名字, 郵件地址, bug ID, 或其它身份標識和與這一TODO相關的 issue. 主要目的是讓添加注釋的人 (也是可以請求提供更多細節的人) 可根據規范的TODO格式進行查找. 添加TODO注釋并不意味著你要自己來修正, 因此當你加上帶有姓名的TODO時, 一般都是寫上自己的名字.
// TODO(kl@gmail.com): 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.”).
8.9. 棄用注釋
總述
通過棄用注釋(DEPRECATEDcomments)以標記某接口點已棄用.
您可以寫上包含全大寫的DEPRECATED的注釋, 以標記某接口為棄用狀態. 注釋可以放在接口聲明前, 或者同一行.
在DEPRECATED一詞后, 在括號中留下您的名字, 郵箱地址以及其他身份標識.
棄用注釋應當包涵簡短而清晰的指引, 以幫助其他人修復其調用點. 在 C++ 中, 你可以將一個棄用函數改造成一個內聯函數, 這一函數將調用新的接口.
僅僅標記接口為DEPRECATED并不會讓大家不約而同地棄用, 您還得親自主動修正調用點(callsites), 或是找個幫手.
修正好的代碼應該不會再涉及棄用接口點了, 著實改用新接口點. 如果您不知從何下手, 可以找標記棄用注釋的當事人一起商量.
譯者 (YuleFox) 筆記
關于注釋風格, 很多 C++ 的 coders 更喜歡行注釋, C coders 或許對塊注釋依然情有獨鐘, 或者在文件頭大段大段的注釋時使用塊注釋;
文件注釋可以炫耀你的成就, 也是為了捅了簍子別人可以找你;
注釋要言簡意賅, 不要拖沓冗余, 復雜的東西簡單化和簡單的東西復雜化都是要被鄙視的;
對于 Chinese coders 來說, 用英文注釋還是用中文注釋, it is a problem, 但不管怎樣, 注釋是為了讓別人看懂, 難道是為了炫耀編程語言之外的你的母語或外語水平嗎;
注釋不要太亂, 適當的縮進才會讓人樂意看. 但也沒有必要規定注釋從第幾列開始 (我自己寫代碼的時候總喜歡這樣), UNIX/LINUX 下還可以約定是使用 tab 還是 space, 個人傾向于 space;
TODO 很不錯, 有時候, 注釋確實是為了標記一些未完成的或完成的不盡如人意的地方, 這樣一搜索, 就知道還有哪些活要干, 日志都省了.
-
Google
+關注
關注
5文章
1772瀏覽量
57722 -
編程
+關注
關注
88文章
3637瀏覽量
93914 -
C++
+關注
關注
22文章
2114瀏覽量
73797
原文標題:Google C++ 編程規范 - 5
文章出處:【微信號:C_Expert,微信公眾號:C語言專家集中營】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論