范實(shí)戰(zhàn):從代碼布局到錯(cuò)誤處理的團(tuán)隊(duì)落地指南)
簡(jiǎn)介《中興通訊軟件編程規(guī)范——C/C》是中興通訊股份有限公司2003年發(fā)布的企業(yè)設(shè)計(jì)技術(shù)標(biāo)準(zhǔn)面向所有使用C/C語(yǔ)言進(jìn)行軟件開(kāi)發(fā)的工程師用于統(tǒng)一公司內(nèi)部軟件編程風(fēng)格提升源程序的可讀性、可靠性和可維護(hù)性。這份規(guī)范按規(guī)則性和建議性?xún)深?lèi)要求組織內(nèi)容涵蓋基本原則、布局、注釋、命名規(guī)則、變量常量與類(lèi)型、表達(dá)式與語(yǔ)句、函數(shù)與過(guò)程、可靠性、可測(cè)試性、斷言與錯(cuò)誤處理等12個(gè)章節(jié)附錄A和附錄B還提供編程模板與規(guī)范檢查表便于開(kāi)發(fā)者在實(shí)際項(xiàng)目中逐項(xiàng)對(duì)照?qǐng)?zhí)行。壓縮包為1個(gè)doc格式文檔容量?jī)H486KB輕量聚焦適合隨時(shí)查閱。該文檔已有301人學(xué)習(xí)瀏覽既能幫助企業(yè)團(tuán)隊(duì)快速建立統(tǒng)一的編碼標(biāo)準(zhǔn)也適合個(gè)人開(kāi)發(fā)者自查代碼風(fēng)格、減少缺陷是一份頗具參考價(jià)值的經(jīng)典企業(yè)級(jí)編程規(guī)范資料。1. 一份C/C編碼規(guī)范憑什么值得你逐行讀完接手一個(gè)維護(hù)了三年多的嵌入式通信項(xiàng)目我第一次打開(kāi)核心模塊源碼的時(shí)候心態(tài)是崩潰的一個(gè)文件里三種縮進(jìn)風(fēng)格變量名從a1、tmp到m_pStrCount混著來(lái)函數(shù)長(zhǎng)度三百行起步最要命的是沒(méi)有任何注釋說(shuō)明這段邏輯到底在干什么。后來(lái)項(xiàng)目組引進(jìn)了一份內(nèi)部的C/C編碼規(guī)范要求所有新代碼和改動(dòng)代碼必須按它執(zhí)行。三個(gè)月后代碼審查效率明顯提升新人上手時(shí)間縮短線(xiàn)上故障排查也輕松了不少。這份規(guī)范就是某通信設(shè)備公司于2003年發(fā)布的企業(yè)標(biāo)準(zhǔn)《軟件編程規(guī)范——C/C》編號(hào) Q/ZX 04.302.1–2003。它覆蓋了布局、注釋、命名、表達(dá)式、函數(shù)、可靠性、可測(cè)試性和錯(cuò)誤處理適合C/C開(kāi)發(fā)工程師、嵌入式軟件團(tuán)隊(duì)以及想建立代碼規(guī)范的技術(shù)負(fù)責(zé)人閱讀。今天我把這份規(guī)范拆開(kāi)結(jié)合實(shí)戰(zhàn)場(chǎng)景講講它到底怎么用。2. 設(shè)計(jì)原則與文件布局先統(tǒng)一思想再統(tǒng)一代碼規(guī)范的價(jià)值從來(lái)不是約束而是統(tǒng)一認(rèn)識(shí)。這一章是整套規(guī)范的思想底座之后所有章節(jié)的規(guī)則幾乎都是從這幾個(gè)原則推演出來(lái)的值得逐條細(xì)品。2.1 基本原則這八條守則是整份規(guī)范的思想根基規(guī)范開(kāi)篇提出八條基本原則寫(xiě)得很樸素但每一條都對(duì)應(yīng)著實(shí)際開(kāi)發(fā)中的典型問(wèn)題?!驹瓌t1-1】“首先是為人編寫(xiě)程序其次才是計(jì)算機(jī)”——初看像句口號(hào)但仔細(xì)想代碼要經(jīng)歷開(kāi)發(fā)、測(cè)試、生產(chǎn)、用戶(hù)使用、版本升級(jí)和后期維護(hù)只有人能順利讀懂的代碼才談得上維護(hù)。很多團(tuán)隊(duì)翻車(chē)不是因?yàn)楣δ軟](méi)實(shí)現(xiàn)而是因?yàn)槿齻€(gè)月后沒(méi)人敢改那段“高效但晦澀”的代碼?!驹瓌t1-2】“保持代碼的簡(jiǎn)明清晰避免過(guò)分追求技巧”——過(guò)度使用宏、表達(dá)式里堆疊運(yùn)算符、為了少寫(xiě)幾行把邏輯壓縮成一行這些都是常見(jiàn)的反面教材。簡(jiǎn)明清晰就是把意圖直接表達(dá)出來(lái)而不是讓讀者做閱讀理解。【原則1-3】“所有代碼盡量遵循ANSI C標(biāo)準(zhǔn)”——這一條在2003年的背景下尤其重要當(dāng)時(shí)的編譯器各有各的擴(kuò)展語(yǔ)法。盡量不用未定義行為或編譯器擴(kuò)展是為了可移植性?!驹瓌t1-4】“首先達(dá)到正確性其次考慮效率”——這條順序太關(guān)鍵了。我見(jiàn)過(guò)太多人一上來(lái)就優(yōu)化循環(huán)、改用寄存器變量結(jié)果功能都沒(méi)跑對(duì)。正確性、健壯性、可維護(hù)性先過(guò)關(guān)性能最后調(diào)這個(gè)次序不能倒。【原則1-5】“避免或少用全局變量”——全局變量帶來(lái)的問(wèn)題是模塊間緊耦合一處改動(dòng)處處受牽連。規(guī)范不搞一刀切而是說(shuō)“少用”這符合工程實(shí)際。【原則1-6】“盡量避免使用GOTO語(yǔ)句”——GOTO會(huì)讓程序的控制流變成意大利面條不到萬(wàn)不得已不用。這條原則在后續(xù)的“表達(dá)式與語(yǔ)句”章節(jié)會(huì)用規(guī)則進(jìn)一步約束。【原則1-7】“盡可能重用、修正老的代碼”——不是重新發(fā)明輪子而是對(duì)已驗(yàn)證的代碼做修改優(yōu)化來(lái)滿(mǎn)足新需求減少測(cè)試成本和質(zhì)量風(fēng)險(xiǎn)。【原則1-8】“盡量減少同樣的錯(cuò)誤出現(xiàn)的次數(shù)”——復(fù)盤(pán)歷史缺陷把常見(jiàn)錯(cuò)誤沉淀成規(guī)范條目這就是為什么規(guī)范越積越厚的原因。2.2 文件布局頭文件和實(shí)現(xiàn)文件的書(shū)寫(xiě)順序規(guī)范對(duì)頭文件和實(shí)現(xiàn)文件的布局給出了明確的章節(jié)順序這一點(diǎn)對(duì)讀老代碼特別有幫助——你打開(kāi)一個(gè)文件就能知道哪個(gè)區(qū)塊該找什么。頭文件布局順序是文件頭注釋、#ifndef防止重復(fù)包含、#include依次放標(biāo)準(zhǔn)庫(kù)頭文件、非標(biāo)準(zhǔn)庫(kù)頭文件、常量定義、全局宏、全局?jǐn)?shù)據(jù)類(lèi)型、類(lèi)定義模板、全局函數(shù)原型、#endif。實(shí)現(xiàn)文件布局順序是文件頭注釋、#include、常量定義、文件內(nèi)部使用的宏、文件內(nèi)部使用的數(shù)據(jù)類(lèi)型、全局變量、本地變量靜態(tài)全局變量、局部函數(shù)原型、類(lèi)的實(shí)現(xiàn)、全局函數(shù)、局部函數(shù)。這里我實(shí)際用下來(lái)最受益的是兩條硬性規(guī)則。第一條是【規(guī)則2-1-4】頭文件必須避免重復(fù)包含用宏定義保護(hù)#ifndef MODULE_H #define MODULE_H /* 文件體 */ #endif這個(gè)寫(xiě)法現(xiàn)在已經(jīng)是常識(shí)了但在老代碼里依然能看到不寫(xiě)保護(hù)的頭文件一旦被兩個(gè)源文件包含就會(huì)報(bào)重復(fù)定義錯(cuò)誤。第二條是【規(guī)則2-1-5】標(biāo)準(zhǔn)庫(kù)頭文件用尖括號(hào)非標(biāo)準(zhǔn)庫(kù)用雙引號(hào)這個(gè)約定讓閱讀者一眼分辨頭文件的來(lái)源歸屬。2.3 文件布局的價(jià)值給代碼建立“心理地圖”把布局規(guī)則落到工程實(shí)踐上本質(zhì)上是給代碼建立一套心理地圖。比如我后來(lái)接手別人的模塊先是翻文件頭注釋了解用途和修改記錄然后掃常量定義和數(shù)據(jù)類(lèi)型再看全局函數(shù)原型基本不用通讀全文就能知道這個(gè)文件的“骨架”。這樣做的好處是代碼審查時(shí)能快速定位到關(guān)鍵節(jié)點(diǎn)缺陷排查時(shí)能判斷哪個(gè)區(qū)域最可能出問(wèn)題。3. 從布局到注釋可讀性不是靠感覺(jué)是靠規(guī)則布局是代碼的外殼注釋則是代碼的說(shuō)明書(shū)。這一章把規(guī)范里最詳盡的布局規(guī)則和注釋規(guī)則拆開(kāi)講每一條都有正反例可以直接當(dāng)團(tuán)隊(duì)checklist用。3.1 基本格式與縮進(jìn)四個(gè)空格的執(zhí)念規(guī)范對(duì)格式的要求極其具體不搞“建議盡量”這種軟話(huà)直接就是規(guī)則?!疽?guī)則2-2-1】一行代碼和注釋不能超過(guò)80列包括空格在內(nèi)。這條約束寫(xiě)代碼的人要時(shí)刻注意斷行否則在分屏對(duì)比代碼時(shí)會(huì)被折行搞瘋。【規(guī)則2-2-2】if、else、else if、for、while、do等語(yǔ)句自占一行執(zhí)行語(yǔ)句不得緊跟其后不論執(zhí)行語(yǔ)句有多少都要加{}。/* 正例 */ if (varible1 varible2) { varible1 varible2; } /* 反例執(zhí)行語(yǔ)句緊跟if條件且不加花括號(hào) */ if (varible1 varible2) varible1 varible2;這條規(guī)則我舉雙手贊成。不加花括號(hào)的寫(xiě)法在后續(xù)維護(hù)中極易埋雷——有人想往分支里加一行代碼結(jié)果忘了加花括號(hào)新代碼變成了無(wú)條件執(zhí)行。這是典型的“省一行毀一夜”翻車(chē)現(xiàn)場(chǎng)。【規(guī)則2-3-1】禁止使用TAB鍵必須使用空格進(jìn)行縮進(jìn)縮進(jìn)為4個(gè)空格。原因很簡(jiǎn)單不同的編輯器對(duì)TAB的解析寬度不一樣在A(yíng)機(jī)器上對(duì)齊的代碼到B機(jī)器上就錯(cuò)位了?,F(xiàn)在IDE基本都能配置“TAB轉(zhuǎn)空格”但在老舊的嵌入式開(kāi)發(fā)環(huán)境里這條規(guī)則還是能避免大量對(duì)齊問(wèn)題。實(shí)戰(zhàn)中我建議在項(xiàng)目根目錄放一份.editorconfig文件把indent_style space和indent_size 4固定下來(lái)?!疽?guī)則2-3-2】程序的分界符{和}應(yīng)獨(dú)占一行并且位于同一列同時(shí)與引用它們的語(yǔ)句左對(duì)齊。這樣寫(xiě)的好處是成對(duì)的花括號(hào)垂直對(duì)齊代碼塊邊界一目了然。3.2 對(duì)齊、空行與空格讓代碼呈現(xiàn)“結(jié)構(gòu)感”布局規(guī)則有很多細(xì)節(jié)但它們都在為同一個(gè)目標(biāo)服務(wù)讓代碼呈現(xiàn)出結(jié)構(gòu)感讓人眼能像看報(bào)紙一樣掃讀?!疽?guī)則2-3-5】相關(guān)的賦值語(yǔ)句等號(hào)對(duì)齊。舉規(guī)范里的例子tPDBRes.wHead 0; tPDBRes.wTail wMaxNumOfPDB - 1; tPDBRes.wFree wMaxNumOfPDB; tPDBRes.wAddress wPDBAddr; tPDBRes.wSize wPDBSize;一組同類(lèi)的賦值語(yǔ)句對(duì)齊后右側(cè)的變量值和左側(cè)的邏輯關(guān)系一目了然。維護(hù)時(shí)增刪字段也能立刻發(fā)現(xiàn)不對(duì)稱(chēng)的地方?!疽?guī)則2-4-1】不同邏輯程序塊之間要用空行分隔。函數(shù)與函數(shù)之間、循環(huán)體與后續(xù)代碼之間這些位置都應(yīng)該有空行讓代碼“喘口氣”。void Foo::Hey(void) { /* Hey實(shí)現(xiàn)代碼 */ } /* 空一行 */ void Foo::Ack(void) { /* Ack實(shí)現(xiàn)代碼 */ }【規(guī)則2-4-2】【規(guī)則2-4-3】在運(yùn)算符空格上做了標(biāo)準(zhǔn)化一元操作符如!、~、、--、*、地址運(yùn)算符前后不加空格多元運(yùn)算符和操作數(shù)之間至少一個(gè)空格。/* 一元運(yùn)算符 */ !bValue ~iValue iCount *strSource fSum /* 多元運(yùn)算符 */ fValue fOldValue; fTotal fValue; iNumber 2;【規(guī)則2-4-4】關(guān)鍵字之后要留空格。if、for、while等關(guān)鍵字后應(yīng)留一個(gè)空格再跟左括號(hào)以突出關(guān)鍵字與函數(shù)調(diào)用的區(qū)別。函數(shù)名后則不要留空格【規(guī)則2-4-5】這樣一眼就能區(qū)分if (和DoSomething(。3.3 斷行規(guī)則長(zhǎng)表達(dá)式怎么拆才不破壞邏輯超過(guò)80列時(shí)必須斷行但怎么斷有講究?!疽?guī)則2-5-1】長(zhǎng)表達(dá)式要在低優(yōu)先級(jí)操作符處拆分成新行操作符放在新行之首以便突出操作符。拆分出的新行要做適當(dāng)縮進(jìn)。/* 條件表達(dá)式續(xù)行在第一個(gè)條件處對(duì)齊 */ if ((iFormat CH_A_Format_M) (iOfficeType CH_BSC_M)) { DoSomething(); } /* 賦值語(yǔ)句的續(xù)行在賦值號(hào)處對(duì)齊 */ fTotalBill fTotalBill faCustomerPurchases[iID] fSalesTax(faCustomerPurchases[iID]);注意看操作符和都放在了新行的行首這樣讀代碼時(shí)先看到操作符再看到操作數(shù)邏輯重心在前可讀性確實(shí)更好。3.4 注釋規(guī)則注釋是解釋意圖不是復(fù)述代碼規(guī)范的注釋章節(jié)開(kāi)篇就說(shuō)了一段很有價(jià)值的話(huà)有效的注釋是指在代碼的功能、意圖層次上進(jìn)行注釋提供有用、額外的信息而不是代碼表面意義的簡(jiǎn)單重復(fù)。【規(guī)則3-1】C語(yǔ)言的注釋符為/* ... */C語(yǔ)言中多行注釋采用/* ... */單行注釋采用// ...?!疽?guī)則2-4-7】注釋符與注釋內(nèi)容之間要用一個(gè)空格進(jìn)行分隔。/* 注釋內(nèi)容 */ // 注釋內(nèi)容我自己的習(xí)慣是文件頭寫(xiě)版權(quán)聲明和修改歷史函數(shù)定義上方寫(xiě)功能、參數(shù)、返回值和注意事項(xiàng)代碼行內(nèi)注釋只加在有隱含邏輯和魔法數(shù)字的地方。一眼能看懂的代碼不注釋看不懂的代碼才需要注釋。4. 命名、變量與函數(shù)給機(jī)器寫(xiě)的代碼給人命名命名是編程里最難的事之一。規(guī)范用了一整章的篇幅定義命名規(guī)則并且給出了一套實(shí)用的前綴體系。這一章把命名規(guī)則和變量、函數(shù)的規(guī)范串起來(lái)講落到實(shí)際怎么寫(xiě)。4.1 命名規(guī)則與變量前綴從名字就能讀出“元信息”規(guī)范的命名規(guī)則章節(jié)定義了完整的匈牙利命名法變體。變量名要帶類(lèi)型前綴指針用p函數(shù)指針用fp全局變量用g靜態(tài)變量用s類(lèi)成員變量用m_。拿規(guī)范里的例子看float *pfBuffer; /* p表示指針f表示floatBuffer是用途 */ int iCount; /* i表示int */ char chKey; /* ch表示char */ BYTE ucCallNo; /* uc表示unsigned char */這套命名法的精髓在于從變量名就能讀出它的類(lèi)型、作用域不用回頭翻定義?!疽?guī)則2-2-3】定義指針類(lèi)型的變量*應(yīng)放在變量前寫(xiě)成float *pfBuffer而不是float* pfBuffer。這個(gè)細(xì)節(jié)起初看起來(lái)有點(diǎn)別扭但讀代碼的時(shí)候“指針屬性”緊貼變量名效果更直觀(guān)。為了避免變量名過(guò)長(zhǎng)或過(guò)短命名規(guī)則章節(jié)還要求不使用含義模糊的縮寫(xiě)。比如用iLength而不是iLen用strUserName而不是strUN。我見(jiàn)過(guò)太多用tmp1、tmp2糊弄的代碼三個(gè)月后自己都分不清哪個(gè)是哪個(gè)。類(lèi)定義內(nèi)部【規(guī)則2-3-3】public、protected、private關(guān)鍵字要與{}對(duì)齊這些部分的內(nèi)容要縮進(jìn)。類(lèi)成員變量統(tǒng)一加m_前綴避免與函數(shù)局部變量混淆class CCount { public: CCount(void); ~CCount(void); int GetCount(void); void SetCount(int iCount); private: int m_iCount; };4.2 變量、常量與類(lèi)型全局變量嚴(yán)格控制規(guī)范對(duì)變量的約束集中在“少用全局變量”這個(gè)原則上具體落地包括模塊內(nèi)全局變量用static修飾限制可見(jiàn)范圍定義常量用const而不是#defineC環(huán)境下枚舉類(lèi)型用于有窮集合的取值。類(lèi)型定義上有兩條硬規(guī)則結(jié)構(gòu)體類(lèi)型定義要放在頭文件或文件前部的“數(shù)據(jù)類(lèi)型定義”區(qū)塊中復(fù)雜的類(lèi)型定義必須寫(xiě)注釋說(shuō)明其用途和約束。這些都是小細(xì)節(jié)但加在一起能顯著減少誤用和誤讀。4.3 表達(dá)式、語(yǔ)句與函數(shù)用規(guī)則把“坑”填平【規(guī)則2-2-2】已經(jīng)要求if語(yǔ)句強(qiáng)制執(zhí)行體加大括號(hào)函數(shù)章節(jié)則對(duì)函數(shù)本身的寫(xiě)法提出了約束。函數(shù)實(shí)現(xiàn)遵循單一職責(zé)的原則一個(gè)函數(shù)只做一件事函數(shù)的行數(shù)控制在50行以?xún)?nèi)建議性規(guī)范。函數(shù)名用動(dòng)詞開(kāi)頭如GetCount()、SetCount()、DoSomething()。函數(shù)參數(shù)的書(shū)寫(xiě)有明確規(guī)則——【規(guī)則2-5-2】函數(shù)聲明時(shí)類(lèi)型與名稱(chēng)不允許分行書(shū)寫(xiě)/* 正例 */ extern double FAR CalcArea(double dWidth, double dHeight); /* 反例類(lèi)型與名稱(chēng)被拆到兩行 */ extern double FAR CalcArea(double dWidth, double dHeight);函數(shù)聲明時(shí)類(lèi)型與名稱(chēng)同行參數(shù)過(guò)多時(shí)在第一個(gè)參數(shù)處對(duì)齊續(xù)行。這樣寫(xiě)讓函數(shù)簽名在代碼搜索和IDE解析時(shí)更穩(wěn)定尤其是在老式的源碼瀏覽工具里折行會(huì)破壞函數(shù)原型的解析。4.4 實(shí)際應(yīng)用代碼審查時(shí)怎么用這套規(guī)則我在團(tuán)隊(duì)里推這套命名規(guī)范時(shí)有個(gè)體會(huì)與其靠人自覺(jué)不如把規(guī)范變成代碼審查的檢查項(xiàng)。審查時(shí)先看文件名和頭文件布局再看變量命名是否帶合法前綴最后看函數(shù)長(zhǎng)度是否超標(biāo)。自動(dòng)化工具如cppcheck和clang-tidy能檢查一部分格式問(wèn)題但命名語(yǔ)義和前綴規(guī)則還是得靠人眼。5. 可靠性、可測(cè)試性與錯(cuò)誤處理避坑與排查實(shí)戰(zhàn)前面幾章講的都是代碼“長(zhǎng)什么樣”這一章要講的是代碼“要不出事”。規(guī)范和錯(cuò)誤處理章節(jié)直接關(guān)系線(xiàn)上質(zhì)量是這次拆解的重點(diǎn)也是踩坑記錄最豐富的部分。5.1 可靠性從衛(wèi)生習(xí)慣開(kāi)始可靠性章節(jié)的核心思路是“防御性編程”假設(shè)輸入是惡意的、假設(shè)資源是有限的、假設(shè)外部環(huán)境是不可靠的。具體規(guī)則包括函數(shù)入口處檢查參數(shù)合法性指針使用前判空除法運(yùn)算前檢查分母不為零數(shù)組訪(fǎng)問(wèn)前檢查索引邊界申請(qǐng)的內(nèi)存一定要釋放且成對(duì)出現(xiàn)。這些都是教科書(shū)寫(xiě)爛了的內(nèi)容但真正能在每一行代碼里堅(jiān)持的人不多。另一個(gè)重要條目是防止整數(shù)溢出。在計(jì)算緩沖區(qū)長(zhǎng)度或分配大小時(shí)先判斷加法和乘法是否溢出再執(zhí)行操作這是很多安全漏洞的根源。規(guī)范在2003年就寫(xiě)了不要使用無(wú)符號(hào)整數(shù)的循環(huán)條件因?yàn)閒or (unsigned int i n; i 0; i--)是一個(gè)死循環(huán)——i減到0后再減會(huì)變成最大值這個(gè)坑我親眼見(jiàn)過(guò)有人踩排查了大半天。5.2 可測(cè)試性代碼從設(shè)計(jì)上就要容易被驗(yàn)證可測(cè)試性章節(jié)建議函數(shù)盡量不依賴(lài)全局狀態(tài)輸入輸出盡量通過(guò)參數(shù)和返回值傳遞模塊通過(guò)接口解耦便于打樁和模擬。這在當(dāng)時(shí)是相當(dāng)先進(jìn)的思想——很多團(tuán)隊(duì)還以為“測(cè)試”就是編譯運(yùn)行看結(jié)果。規(guī)范附錄A的編程模板提供了標(biāo)準(zhǔn)的代碼組織格式從文件頭到函數(shù)實(shí)現(xiàn)都有空行和注釋位附錄B的規(guī)范檢查表則是一個(gè)逐項(xiàng)打勾的清單。這兩份附錄是直接把規(guī)范落地的實(shí)操工具而不是停留在概念層面。5.3 斷言與錯(cuò)誤處理程序要“死得明白”斷言與錯(cuò)誤處理章節(jié)強(qiáng)調(diào)斷言用于捕獲“不應(yīng)該發(fā)生”的程序邏輯錯(cuò)誤錯(cuò)誤處理用于處理“可能發(fā)生”的運(yùn)行時(shí)異常。兩者分工明確不能混淆。/* 斷言示例 */ assert(pBuffer ! NULL); /* 錯(cuò)誤處理示例 */ if (pBuffer NULL) { return ERROR_NULL_POINTER; }使用斷言的一個(gè)重要準(zhǔn)則是斷言只應(yīng)在調(diào)試版本中生效發(fā)布版本要把斷言編譯掉。如果斷言保留在發(fā)布版本中可能會(huì)因?yàn)橛|發(fā)斷言而暴露內(nèi)部信息甚至被利用來(lái)發(fā)起拒絕服務(wù)攻擊。錯(cuò)誤處理的三條原則是錯(cuò)誤信息要包含足夠的上下文哪個(gè)模塊、哪個(gè)操作錯(cuò)誤碼要有統(tǒng)一的定義和管理資源清理要做“對(duì)等釋放”——在函數(shù)的所有退出路徑上都要釋放資源包括正常返回和錯(cuò)誤返回。最常見(jiàn)的資源泄漏是提前return時(shí)忘了釋放這類(lèi)問(wèn)題用cppcheck能自動(dòng)查出來(lái)一部分但最好從代碼審查源頭解決。5.4 常見(jiàn)踩坑記錄以下是我在實(shí)踐中整理的高頻問(wèn)題每一條都是真實(shí)發(fā)生過(guò)的按“現(xiàn)象→原因→解決”的套路寫(xiě)出來(lái)。踩坑一TAB和空格混用導(dǎo)致代碼錯(cuò)位現(xiàn)象代碼在開(kāi)發(fā)者的編輯器里對(duì)齊整齊提交到代碼庫(kù)后其他人打開(kāi)看全是錯(cuò)位的。原因有人用TAB縮進(jìn)有人用4個(gè)空格縮進(jìn)而不同編輯器對(duì)TAB的渲染寬度不同。解決項(xiàng)目根目錄統(tǒng)一放.editorconfig強(qiáng)制indent_style space、indent_size 4代碼提交前跑一遍格式檢查腳本不符合的直接拒絕提交。踩坑二匈牙利前綴誤導(dǎo)類(lèi)型判斷現(xiàn)象代碼里有個(gè)變量叫iYear結(jié)果實(shí)際是個(gè)unsigned int還有人底層是char的數(shù)組存年份。原因命名時(shí)沒(méi)嚴(yán)格綁定類(lèi)型前綴后面類(lèi)型改了但名字沒(méi)同步。解決把命名列入代碼審查硬性檢查項(xiàng)類(lèi)型變更時(shí)變量名同步修改。雖然改起來(lái)麻煩但不改的后患更大。踩坑三#include頭文件順序混亂導(dǎo)致編譯失敗現(xiàn)象自己本地編譯過(guò)了別人一拉代碼就報(bào)錯(cuò)換個(gè)編譯器又報(bào)錯(cuò)。原因頭文件包含順序依賴(lài)了編譯環(huán)境的隱式包含非標(biāo)準(zhǔn)頭文件寫(xiě)在了標(biāo)準(zhǔn)庫(kù)前面導(dǎo)致宏定義或類(lèi)型定義缺失。解決嚴(yán)格執(zhí)行【規(guī)則2-1-5】的標(biāo)準(zhǔn)庫(kù)在前、非標(biāo)準(zhǔn)庫(kù)在后的順序同時(shí)頭文件加保護(hù)宏。這樣依賴(lài)關(guān)系穩(wěn)定編譯錯(cuò)誤也容易定位。踩坑四斷言在發(fā)布版本中對(duì)用戶(hù)彈錯(cuò)誤框現(xiàn)象程序發(fā)布后用戶(hù)操作時(shí)突然彈出一個(gè)斷言失敗對(duì)話(huà)框。原因斷言沒(méi)有被編譯掉錯(cuò)誤處理被當(dāng)成了斷言來(lái)寫(xiě)。解決區(qū)分?jǐn)嘌院湾e(cuò)誤處理斷言只檢查“不可能發(fā)生”的條件編譯時(shí)排除在發(fā)布版本外用戶(hù)可見(jiàn)的錯(cuò)誤路徑一律用錯(cuò)誤處理代碼。踩坑五長(zhǎng)表達(dá)式斷行位置不對(duì)邏輯被讀錯(cuò)現(xiàn)象后來(lái)的人修改條件表達(dá)式時(shí)誤以為換行后的兩行是并列的兩個(gè)條件。原因斷行時(shí)沒(méi)有把操作符放在行首或者縮進(jìn)不對(duì)讓閱讀者產(chǎn)生了歧義。解決嚴(yán)格按【規(guī)則2-5-1】操作符放行首續(xù)行縮進(jìn)。這屬于格式問(wèn)題但格式問(wèn)題直接影響人腦對(duì)邏輯的判斷。6. 把規(guī)范落地到自己的項(xiàng)目從檢查表到漸進(jìn)式改造這可能是讀者最關(guān)心的一章我怎么把這套2003年的規(guī)范用在自己的項(xiàng)目上而不是把它鎖進(jìn)抽屜里。我的經(jīng)驗(yàn)是別指望一步到位用漸進(jìn)式改造和工具化手段讓規(guī)范真正在一個(gè)存量代碼庫(kù)里生根。第一步是引入規(guī)范檢查表。規(guī)范附錄B本來(lái)就是逐項(xiàng)打勾的檢查表我把它翻譯成團(tuán)隊(duì)代碼審查的模板文件頭是否完整、頭文件保護(hù)宏是否存在、縮進(jìn)是否4空格、變量名前綴是否合法、函數(shù)行數(shù)是否超標(biāo)、空行是否分隔邏輯塊、斷行是否合規(guī)。審查者在代碼評(píng)審時(shí)逐項(xiàng)過(guò)一遍幾分鐘就能完成。這三五分鐘的成本換來(lái)的是代碼庫(kù)的長(zhǎng)期一致性性?xún)r(jià)比很高。第二步是格式化工具先行。用clang-format把布局規(guī)則自動(dòng)化配置項(xiàng)就按規(guī)范來(lái)ColumnLimit: 80、IndentWidth: 4、UseTab: Never、BreakBeforeBinaryOperators: All。人工審查只管邏輯和命名格式問(wèn)題交給工具。這一步能消除團(tuán)隊(duì)里95%的風(fēng)格爭(zhēng)論。但clang-format管不了注釋寫(xiě)法規(guī)范的注釋規(guī)則還是要靠代碼審查時(shí)提示。第三步是控制增量。對(duì)存量代碼不做大范圍重寫(xiě)只要求新代碼和改動(dòng)代碼符合規(guī)范。改到哪個(gè)文件就順手把這個(gè)文件的格式規(guī)范一遍。這樣漸進(jìn)式推進(jìn)既不影響業(yè)務(wù)節(jié)奏又能讓代碼庫(kù)的“規(guī)范覆蓋率”穩(wěn)步提升。最后一步是在團(tuán)隊(duì)內(nèi)部建立共識(shí)編碼規(guī)范不是某些人拍腦袋定的教條而是從歷史代碼的血淚教訓(xùn)里提煉出來(lái)的。規(guī)范里的每一條規(guī)則背后至少對(duì)應(yīng)過(guò)一次線(xiàn)上故障或者一次低效排查。這個(gè)共識(shí)建立起來(lái)之后團(tuán)隊(duì)成員的執(zhí)行力會(huì)明顯提升因?yàn)樗麄兪钦嫘恼J(rèn)可這些規(guī)則的價(jià)值。從那以后我每次給項(xiàng)目設(shè)計(jì)代碼結(jié)構(gòu)的時(shí)候都會(huì)強(qiáng)制走一遍規(guī)范的核心條目文件布局按照頭文件和實(shí)現(xiàn)文件的固定順序來(lái)排變量命名帶類(lèi)型前綴長(zhǎng)表達(dá)式斷行時(shí)把操作符放行首錯(cuò)誤處理先于性能優(yōu)化考慮。一圈走下來(lái)代碼自己會(huì)說(shuō)話(huà)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取