//自己翻譯的,本想整理一下,但一直沒(méi)時(shí)間,現(xiàn)在就這樣放上來(lái)吧,有錯(cuò)就批,別客氣,呵呵。
C++編碼規(guī)范
一、組織與策略問(wèn)題
If builders built buildings the way programmers wrote programs, then the
first woodpecker that came along would destroy civilization.
----Gerald Weinberg
在C和C++的主要傳統(tǒng)中,我們認(rèn)為是一種零基礎(chǔ)的習(xí)慣.第0條是一個(gè)根本的指示,它涵蓋了編碼規(guī)范中我們認(rèn)為是最基本的建議.
在這介紹性的一節(jié)的其余部分中,我們精心選取了其中的少量問(wèn)題加以闡述,這些大多與編碼本身無(wú)直接關(guān)系,但卻是寫(xiě)出可靠代碼的基本工具和技術(shù).
在這一節(jié)中我們認(rèn)為最有價(jià)值的是第0條不要因小失大.(或:知道什么不需要規(guī)范化.)
0. 不要因小失大. (或:知道什么不需要規(guī)范化.)
摘要
僅說(shuō)必要的話(huà):不要堅(jiān)持個(gè)人品味或陳舊的習(xí)慣.
討論
真只是個(gè)人品味且不影響正確性或可讀性的結(jié)論不屬于編碼標(biāo)準(zhǔn).任何專(zhuān)業(yè)程序員可以很容易地讀寫(xiě)稍微不同于他所習(xí)慣的格式的代碼.
在每個(gè)源文件甚至每個(gè)工程中都要用一致的格式,因?yàn)樵趲追N不同編碼風(fēng)格的代碼片段中跳轉(zhuǎn)是很不協(xié)調(diào)的.但是不要企圖在不同項(xiàng)目或公司中堅(jiān)持一致地格式.
下面是幾條通用的結(jié)論,這里重要的不是去制定規(guī)則,而只是要保持與你維護(hù)的文件的風(fēng)格的一致:
不必明確指定要縮進(jìn)多少,但要縮進(jìn)以突出結(jié)構(gòu):用你喜歡的任意數(shù)量的空格縮進(jìn),但至少要在同一文件中保持一致.
不必強(qiáng)行保持特定的行長(zhǎng)度,但要保持行具有可讀性:用你喜歡的任意長(zhǎng)度的行長(zhǎng),但不要太長(zhǎng)了.研究表明十個(gè)單詞以?xún)?nèi)的寬度對(duì)眼睛跟蹤是最理想的.
不要過(guò)度制定命名規(guī)則,但要用一致的命名約定:僅有兩點(diǎn)必須要做的:a)決不用"隱秘的"名稱(chēng),也即以一個(gè)下劃線(xiàn)開(kāi)頭或包括雙下劃線(xiàn)的名稱(chēng);以及b)總是用字母全部大寫(xiě)的單詞命名宏并且決不要考慮定義一個(gè)常用或縮寫(xiě)單詞的宏(包括常用模板參數(shù),比如T和U;以#define T anything定義任何東西是極其不好的做法).此外,要用一致的有意義的名稱(chēng),并按照文件或模塊的約定.(若你不能決定你自己的命名約定,試試這種方式:以各單詞首字母大寫(xiě)方式命名類(lèi)、函數(shù)和枚舉(LikeThis);命名變量時(shí)在前者基礎(chǔ)上小寫(xiě)第一個(gè)單詞的首字母(likeThis);命名私有成員變量時(shí)在前者方式之后再加一個(gè)下劃線(xiàn)(linkThis_);以全大寫(xiě)并用一個(gè)下劃線(xiàn)連接各單詞的方式命名宏(LINK_THIS).)
不要規(guī)定注釋的風(fēng)格(除非有工具解析特定格式的注釋生成文檔),但要寫(xiě)有用的注釋:如果可以的話(huà)以代碼來(lái)代替注釋(見(jiàn)
第16條).不要在注釋中重復(fù)代碼;它們不能被同步維護(hù).要寫(xiě)解釋方法和基本原理的啟發(fā)性的注釋.
最后,不要試圖堅(jiān)持陳舊的規(guī)則(見(jiàn)
例3和
例4),即使它們?cè)谂f編碼規(guī)范出現(xiàn)過(guò).
例子
例1:括號(hào)的放置.下面的幾種沒(méi)有任何可讀性上的差異.
void using_k_and_r_style() {
// K&R風(fēng)格
}
void putting_each_brace_on_its_own_line()
{
// 括號(hào)獨(dú)占一行
}
void or_putting_each_brace_on_its_own_line_indented()
{
// 括號(hào)獨(dú)占一行并縮進(jìn)
}
任何專(zhuān)業(yè)程序員都可以不費(fèi)力地讀寫(xiě)上面所列的任何一種風(fēng)格的代碼.但要保持一致性:不要隨意的或以晦澀的嵌套方式放置括號(hào),試著去遵循各文件中已有的風(fēng)格.在本書(shū)中,我們的括號(hào)有意識(shí)的在排版的約束中以最好的可讀性的來(lái)放置.
例2: 空格與制表符.由于編輯器對(duì)制表符解釋的不同,它可能被誤用,或被理解成凸出,或無(wú)縮進(jìn),這種情況下一些團(tuán)隊(duì)合理地選擇禁用制表符.其他同樣有名望的團(tuán)隊(duì)則合理地允許制表符,而采用紀(jì)律來(lái)避免其潛在缺點(diǎn).只要保持一致性:當(dāng)團(tuán)隊(duì)成員維護(hù)其他人的代碼時(shí),如果你允許使用制表符,確保不要以代碼清晰度和可讀性為代價(jià)(見(jiàn)第6條).如果你禁用制表符,允許編輯器在讀入源文件時(shí)將空格轉(zhuǎn)換成制表符,以便用戶(hù)可以在編輯器中使用制表符;但要確保寫(xiě)回文件的時(shí)候?qū)⑺鼈冞€原為空格.
例3: 匈牙利命名法. 將類(lèi)型信息合并到變量名稱(chēng)中的命名方式為類(lèi)型不安全的語(yǔ)言(尤其是C)帶來(lái)了一些混合的作用.但這種命名法在面向?qū)ο笳Z(yǔ)言中沒(méi)有好處(只有壞處),尤其在泛型編程中是根本不可能的.因此,不會(huì)有哪個(gè)C++編碼規(guī)范會(huì)要求用匈牙利命名法,C++編碼規(guī)范可能合理地將其排除在外.
例4: 單入口,單出口("SESE"). 歷史上,一些編碼規(guī)范要求每個(gè)函數(shù)有且僅有一個(gè)出口,也即一個(gè)返回語(yǔ)句.這樣的要求在支持異常與析構(gòu)函數(shù)的語(yǔ)言中是過(guò)時(shí)的,在這些語(yǔ)言中,函數(shù)通常都會(huì)有許多隱式的出口.取而代之的是,按照像第5條那樣直接提倡更簡(jiǎn)短的函數(shù)的標(biāo)準(zhǔn),這樣會(huì)很自然地具有更容易理解代碼和把握錯(cuò)誤的特性.
參考
[BoostLRG] · [Brooks95] $12 · [Constantine95] $29 · [Keffer95] p. 1 ·
[Kernighan99] $1.1, $1.3, $1.6-7 · [Lakos96] $1.4.1, $2.7 · [McConnell93]
$9, $19 · [Stroustrup94] $4.2-3 · [Stroustrup00] $4.9.3, $6.4, $7.8, $C.1
· [Sutter00] $6, $20 · [SuttHysl01]
1. 以高警告級(jí)別干凈地編譯
摘要
將警告銘記于心:使用你的編譯器的最高警告級(jí)別.要求干凈(無(wú)警告)的構(gòu)建.理解全部的警告,并通過(guò)修改代碼消除警告,而不是通過(guò)降低警告級(jí)別.
討論
編譯器是你的好朋友.若它由于一個(gè)特定的結(jié)構(gòu)而發(fā)出一個(gè)警告,通常你的代碼含有潛在的問(wèn)題.
成功構(gòu)建應(yīng)該是干凈的(無(wú)警告的).如若不是這樣,你將會(huì)很快養(yǎng)成快速瀏覽輸出結(jié)果的習(xí)慣,進(jìn)而你將錯(cuò)過(guò)真正的問(wèn)題.(見(jiàn)第2條)
消除警告: a)理解它;然后b)更改你的代碼去消除警告并讓你想讓它所做的事情對(duì)人和編譯器都更清楚.
一定要做這一步,即使一開(kāi)始程序看起來(lái)正確運(yùn)行了,或者即使你肯定警告是良性的.即使是良性警告也可以使后面的指出真正危險(xiǎn)的警告變得隱晦.
例子
例1: 第三方頭文件.你不可能去修改一個(gè)引起(或許是良性的)警告的庫(kù)頭文件.因此你要在你自己的頭文件中包含原始頭文件并僅在這個(gè)頭文件的作用域內(nèi)選擇性的屏蔽掉這些煩人的警告,然后在你其它的項(xiàng)目文件中包含你的這個(gè)包裝過(guò)的頭文件.例如(注意這里的警告控制語(yǔ)法在編譯器間是不同的):
// 文件: myproj/my_lambda.h -- 包裝Boost的lambda.hpp
// 總是使用這個(gè)文件,而不直接使用lambda.hpp.
// 注意: 我們的構(gòu)建現(xiàn)在自動(dòng)檢查: "grep lambda.hpp ".
// Boost.Lambda產(chǎn)生我們所知道的無(wú)害的編譯警告.
// 當(dāng)作者修正它時(shí)我們將移除下面的#pragma語(yǔ)句,但是這個(gè)頭文件仍將存在.
//
#pragma warning(push) // 僅屏蔽這個(gè)頭文件
#pragma warning(disable:4512)
#pragma warning(disable:4180)
#include
#pragma warning(pop) // 恢復(fù)原來(lái)的警告級(jí)別
例2: "未使用過(guò)的函數(shù)參數(shù)."檢查以確保你真的不打算用這個(gè)函數(shù)參數(shù)(例如:它可能是占位符以便將來(lái)擴(kuò)展,或是你代碼中從未用到但卻是必需的標(biāo)準(zhǔn)化函數(shù)簽名的一部分).如若它不是必需的,只要?jiǎng)h除這個(gè)函數(shù)參數(shù)就行了:
// … 不使用提示的用戶(hù)自定義分配器內(nèi)部 …
// 警告: "unused parameter 'localityHint'"
pointer allocate( size_type numObjects, const void *localityHint = 0 ) {
return static_cast( mallocShared( numObjects * sizeof(T) ) );
}
// 新版本: 消除警告
pointer allocate( size_type numObjects, const void * /* localityHint */ = 0 ) {
return static_cast( mallocShared( numObjects * sizeof(T) ));
}
例3: "變量定義了但卻從未使用."檢查確保你真的不想引用這個(gè)變量.(一個(gè)基于棧的RAII對(duì)象經(jīng)常引起這樣的不合理的警告,請(qǐng)見(jiàn)第13條.)如若它不是必需的,通常你可以插入一個(gè)變量自求值的表達(dá)式使編譯器安靜(這一求值不會(huì)對(duì)運(yùn)行時(shí)速度有影響):
// 警告: "變量'lock'定義了但卻從未使用."
void Fun() {
Lock lock;
// …
}
// 新版本: 消除了警告
void Fun() {
Lock lock;
lock;
// …
}
例4: "可能使用了未初始化的變量."那就初始化這個(gè)變量(見(jiàn)第19條).
例5: "丟失返回語(yǔ)句."即使你的控制流永遠(yuǎn)也不可能到達(dá)函數(shù)的末尾,編譯器有時(shí)也會(huì)要求有一個(gè)返回語(yǔ)句(例如無(wú)限循環(huán)、異常拋出語(yǔ)句以及其它類(lèi)型的返回語(yǔ)句).這也許是一件好事,因?yàn)橛袝r(shí)你只是認(rèn)為控制流不會(huì)運(yùn)行到末尾.例如無(wú)default的switch語(yǔ)句沒(méi)有修改的彈性,因些要有一個(gè)default語(yǔ)句執(zhí)行assert( false ) (另見(jiàn)第68和90條):
// 警告: 丟失"return"
int Fun( Color c ) {
switch( c ) {
case Red: return 2;
case Green: return 0;
case Blue:
case Black: return 1;
}
}
// 新版本: 消除警告
int Fun( Color c ) {
switch( c ) {
case Red: return 2;
case Green: return 0;
case Blue:
case Black: return 1;
default: assert( !"should never get here!" ); // !"string"的值為false
return -1;
}
}
例6: "有符號(hào)/無(wú)符號(hào)不匹配."有符號(hào)數(shù)與無(wú)符號(hào)數(shù)之間的比較與賦值通常不是必需的.改變參與比較的變量的類(lèi)型以使?jié)M足類(lèi)型匹配要求.最壞情況下,插入一個(gè)顯式轉(zhuǎn)換.(由于編譯器為你插入這樣的轉(zhuǎn)換,也會(huì)警告你它所做的,所以你最好不要讓它出現(xiàn).)
例外
有時(shí)編譯器可能發(fā)出一個(gè)厭煩的甚至欺騙性的警告(比如純粹的擾亂信息),但沒(méi)有可提供的方法去消除它,而且去修改代碼去消除它可能是不可實(shí)現(xiàn)的或是徒勞的工作.在這些罕見(jiàn)的情況下,作為一個(gè)團(tuán)隊(duì)決策,除去這個(gè)只是無(wú)聊的警告的煩人的工作是:僅使特定警告無(wú)效,并盡可能是局部性的,并寫(xiě)一個(gè)清晰的注釋文檔說(shuō)明為什么這樣做是必要的.
參考
[Meyers97] $48 · [Stroustrup94] $2.6.2
2. 使用自動(dòng)構(gòu)建系統(tǒng)
摘要
按(單個(gè))按鈕:使用一個(gè)無(wú)需用戶(hù)參與的全自動(dòng)("一鍵觸發(fā)")構(gòu)建系統(tǒng).
討論
一個(gè)一鍵觸發(fā)式構(gòu)建方法是基本的.它必須進(jìn)行可靠的和可重復(fù)的轉(zhuǎn)換,將你的源文件轉(zhuǎn)換為可交付的程序包.有很多自動(dòng)構(gòu)建工具可以使用,沒(méi)有理由不去用它.挑選一個(gè),使用它.
我們已見(jiàn)過(guò)一些忽視了"一鍵觸發(fā)"式要求的組織.一些人認(rèn)為隨處點(diǎn)幾下鼠標(biāo),就可以運(yùn)行一些工具來(lái)注冊(cè)COM/CORBA服務(wù),或通過(guò)手工定制的一個(gè)合理構(gòu)建過(guò)程拷貝一些文件.但是你沒(méi)有時(shí)間和精力可以浪費(fèi)在一些機(jī)器能做得更好更快的事情上.你需要一鍵觸發(fā)式的自動(dòng)化的和可靠的構(gòu)建.
成功的構(gòu)建應(yīng)該是沒(méi)有任何警告的(見(jiàn)第1條).理想化的構(gòu)建不產(chǎn)生擾亂信息,而僅是一個(gè)日志消息:"構(gòu)建成功完成."
有兩個(gè)構(gòu)建模式:增量構(gòu)建和完全構(gòu)建.增量構(gòu)建僅重建自上自增量構(gòu)建或完全構(gòu)建以來(lái)被修改過(guò)的文件.推論:兩個(gè)連續(xù)的增量構(gòu)建中的后者應(yīng)該沒(méi)有任何輸出文件;如果有的話(huà),你可能有一個(gè)依賴(lài)環(huán)(見(jiàn)第22條),或者你的構(gòu)建系統(tǒng)執(zhí)行了不必要的操作(例如生成不合理的臨時(shí)文件而只是丟棄它們).
一個(gè)工程可以有不同形式的完全構(gòu)建.考慮用一系列本質(zhì)的特征確定你的構(gòu)建的參數(shù);很可能候選者就是目標(biāo)式體系結(jié)構(gòu)、調(diào)試和發(fā)布、或更廣(基本文件和全部文件和完全安裝).一個(gè)構(gòu)建設(shè)置可以創(chuàng)建一個(gè)產(chǎn)品的基本的可執(zhí)行文件和庫(kù),另一個(gè)可能也創(chuàng)建一些輔助文件,一個(gè)完全充實(shí)的構(gòu)建也可能創(chuàng)建一個(gè)包含你所有文件、可重發(fā)布的第三方庫(kù)和安裝代碼的安裝程序.
隨著工程的進(jìn)行,沒(méi)有自動(dòng)構(gòu)建的花費(fèi)也在增長(zhǎng).如果你一開(kāi)始沒(méi)有用,你將浪費(fèi)很多時(shí)間和資源.更糟糕的是,隨著自動(dòng)構(gòu)建成為無(wú)法抵抗的需求,你將會(huì)有比項(xiàng)目一開(kāi)始更多的壓力.
大項(xiàng)目可能有一個(gè)"構(gòu)建師/主管",他的工作就是照料構(gòu)建系統(tǒng).
參考
[Brooks95] $13, $19 · [Dewhurst03] $1 · [GnuMake] · [Stroustrup00] $9.1
3. 使用一個(gè)版本控制系統(tǒng)
摘要
好記性比不上爛筆頭:使用一個(gè)版本控制系統(tǒng)(VCS).決不要讓檢出的文件保留很長(zhǎng)時(shí)間.一旦你的更新的單元通過(guò)測(cè)試就盡快檢入.確保檢入的代碼不會(huì)破壞整個(gè)構(gòu)建.
討論
幾乎所有不平凡的工程都需要一個(gè)以上的開(kāi)發(fā)者和/或超過(guò)一周的工作量.在這樣的工程中,你將需要比較同一文件的歷史版本以確定變化是什么時(shí)候(和/或被誰(shuí))引入的.你也將需要控制和管理源代碼的變化.
當(dāng)有多個(gè)開(kāi)發(fā)者時(shí),幾個(gè)開(kāi)發(fā)者很可能會(huì)在同一時(shí)間對(duì)同一文件的不同部分進(jìn)行并行地更改.你需要工具以自動(dòng)進(jìn)行文件的檢出和恢復(fù),以及在某些時(shí)候?qū)Σl(fā)編輯的合并.VCS自動(dòng)操作和控制檢出,恢復(fù)以及合并.VCS比手工做的更快更準(zhǔn)確.而且你不用花時(shí)間每天的去擺弄那些重復(fù)性的工作,你有軟件要寫(xiě).
不要破壞構(gòu)建.在VCS中的代碼必須總是可以成功構(gòu)建的.
存在很多的版本控制系統(tǒng)可供選擇,沒(méi)有理由不去用它.最便宜和流行的是cvs(見(jiàn)參考).它是一個(gè)靈活的工具,具胡TCP/IP訪(fǎng)問(wèn)特性,可選擇性的提高安全性(通過(guò)用安全外殼SSH作為后端),卓越的腳本管理,甚至有圖形接口.許多其它VCS也將cvs作為標(biāo)準(zhǔn)去效仿,或基于它構(gòu)建新的功能.
例外
從始至終只花一周左右時(shí)間的一個(gè)程序員的項(xiàng)目或許可以不需要VCS而生存吧.
參考
[BetterSCM] · [Brooks95] $11, $13 · [CVS]
4. 在代碼審閱上作投入
摘要
代碼審閱:更多雙眼睛將會(huì)帶來(lái)更好的質(zhì)量.展示你的代碼,并閱讀他人的.你們都將相互學(xué)習(xí)或受益.
討論
一個(gè)良好的代碼審閱過(guò)程在許多方面都對(duì)你的團(tuán)隊(duì)有好處.它可以:
有益的平等的壓力可以增加代碼質(zhì)量.
可以找到bugs、移植性不好的代碼以及潛在的度量問(wèn)題.
通過(guò)思想的互補(bǔ)培養(yǎng)形成的設(shè)計(jì)和實(shí)現(xiàn).
帶動(dòng)新成員和初級(jí)者迅速提升能力.
在團(tuán)隊(duì)中形成共同價(jià)值和團(tuán)隊(duì)意識(shí).
增加精英、信心、動(dòng)機(jī)和專(zhuān)業(yè)自豪心.
許多開(kāi)發(fā)商既不對(duì)代碼質(zhì)量和團(tuán)隊(duì)質(zhì)量進(jìn)行獎(jiǎng)賞,也不投入時(shí)間和金錢(qián)鼓勵(lì)他們.希望我們從現(xiàn)在起幾年都不會(huì)食言,但我們感覺(jué)到這種趨勢(shì)正在慢慢改變,部分原因是由于對(duì)安全軟件的需求的增長(zhǎng).代碼審閱正有助于這種趨勢(shì),另外也是一個(gè)極好的(和免費(fèi)的)內(nèi)部訓(xùn)練方法.
即使你的雇主還不支持代碼審閱方法,你也要增加管理知識(shí)(提示:要開(kāi)始,給他們看這本書(shū))以及無(wú)論如何要盡你最大努力去安排時(shí)間并引導(dǎo)審閱的進(jìn)行.這時(shí)間是值得花的.
將代碼審閱作為你的軟件開(kāi)發(fā)周期的一項(xiàng)常規(guī)程序.如果你和你的隊(duì)友贊同基于激勵(lì)(也可能是挫折)的獎(jiǎng)懲制度,就會(huì)好得多.
不要做得太形式化了,寫(xiě)一封簡(jiǎn)單的郵件就足夠?qū)⒋a審閱做得很好了.這會(huì)使你更容易跟蹤自己的進(jìn)程以及避免重復(fù).
當(dāng)審閱他人的代碼時(shí),你可能想在旁邊保留一個(gè)供參考的清單.竊以為一個(gè)好的清單可能正是你正在讀的這本書(shū)的目錄表.滿(mǎn)意吧!
摘要:我們知道我們?cè)诮o"唱詩(shī)班"布道,但是不得不說(shuō).你們的自負(fù)或自我主義也許討厭代碼審閱,但你們中的少量天才程序員喜歡它,因?yàn)樗鼤?huì)有成效并使代碼更好,使程序更強(qiáng)健.
參考
[Constantine95] $10, $22, $33 · [McConnell93] $24 · [MozillaCRFAQ]