最近開始接手項(xiàng)目組的開發(fā)管理工作检激,項(xiàng)目組開發(fā)的產(chǎn)品一期功能基本開發(fā)完成,進(jìn)入內(nèi)部測(cè)試及小渠道發(fā)布階段腹侣,然而產(chǎn)品的穩(wěn)定性還存在很大問(wèn)題叔收。
先做一下背景介紹,項(xiàng)目組開發(fā)的是一款面向C端的互聯(lián)剛產(chǎn)品傲隶,運(yùn)行操作系統(tǒng)為windows饺律。整個(gè)項(xiàng)目使用c++開發(fā),總體代碼量大概在數(shù)十萬(wàn)行跺株。
c++是一門很復(fù)雜的語(yǔ)言复濒,有很多強(qiáng)大的特性脖卖,然而當(dāng)用其開發(fā)一款商業(yè)產(chǎn)品時(shí),這些特性可能會(huì)帶來(lái)麻煩巧颈。所以當(dāng)設(shè)計(jì)c++的使用規(guī)范時(shí)胚嘲,更多的是對(duì)其做減法。
本文的規(guī)范針對(duì)VC++開發(fā)環(huán)境洛二,開發(fā)工具為Visual Studio。
文件系統(tǒng)目錄規(guī)范
一款完整的商業(yè)產(chǎn)品開發(fā)通常會(huì)涉及到很多模塊攻锰,這其中包括可執(zhí)行程序(.exe)晾嘶,項(xiàng)目組開發(fā)的庫(kù)(靜態(tài)庫(kù)或動(dòng)態(tài)庫(kù)),第三方的庫(kù)(靜態(tài)庫(kù)或動(dòng)態(tài)庫(kù))娶吞,測(cè)試程序垒迂,這么多的模塊和代碼,需要一個(gè)良好組織的目錄結(jié)夠妒蛇。
這里假設(shè)項(xiàng)目名稱為XXProject
- XXProject
- XXProject.sln
- Bin
- Debug
- Release
- TestBin
- Debug
- Release
- Src
- Document
其中Bin存放需要發(fā)布的可執(zhí)行程序机断,TestBin存放測(cè)試程序的可執(zhí)行文件,Src存放項(xiàng)目的工程文件和源代碼绣夺,Document存放項(xiàng)目相關(guān)的開發(fā)文檔(如項(xiàng)目說(shuō)明吏奸,代碼規(guī)范等)。
接下來(lái)假設(shè)項(xiàng)目包括如下工程:XXMain(發(fā)布的主程序)陶耍,XXUpdate(升級(jí)程序)奋蔚,XXSdk(自己開發(fā)的基礎(chǔ)庫(kù)),XXThird(第三方庫(kù)), XXTest(測(cè)試程序)
- Src
- XXMain
- XXMain.vcxproy
- code文件
- XXUpdate
- XXUpdate.vcxproy
- code文件
- XXTest
- XXTest.vcxproy
- code文件
- XXSdk
- XXSdk.vcxproy
- code文件
- ThirdLib
- XXThird
- Lib
- Debug
- Release
- Include
Lib庫(kù)用來(lái)存放靜態(tài)庫(kù)烈钞,動(dòng)態(tài)庫(kù)的.a文件泊碑,Include用來(lái)存放公共頭文件,ThirdLib用來(lái)存放第三方庫(kù)毯欣。
解決方案目錄規(guī)范
解決方案目錄是VS開發(fā)工具提供的邏輯上組織項(xiàng)目的方式馒过,與物理文件系統(tǒng)并不存在對(duì)應(yīng)關(guān)系。
仍然假設(shè)項(xiàng)目包含上述項(xiàng)目和模塊酗钞。
- Solution (解決方案)
- Application (解決方案文件夾)
- XXMain (工程)
- XXUpdate (工程)
- Test (解決方案文件夾)
- XXTest (工程)
- Library (解決方案文件夾)
- XXSdk
- ThirdLibrary(解決方案文件夾)
- XXThird
- Public (解決方案文件夾)
- 公共頭文件
代碼編寫規(guī)范
1:禁用全局變量
全局變量會(huì)帶來(lái)晦澀的依賴問(wèn)題
2:禁用goto指令
goto指令的代碼難以閱讀和維護(hù)
3:禁用異常機(jī)制
c++的異常機(jī)制有很多缺陷且復(fù)雜
4:用struct封裝數(shù)據(jù)腹忽,使用class定義對(duì)象
C++中class和struct幾乎沒(méi)有區(qū)別,在規(guī)范中進(jìn)行語(yǔ)義的區(qū)分
5:struct和class必須顯示包含構(gòu)造函數(shù)
6:除非特殊情況算吩,總是將析構(gòu)函數(shù)定義為虛函數(shù)
方便繼承時(shí)的資源釋放
7:不要在構(gòu)造函數(shù)中執(zhí)行復(fù)雜操作留凭,推薦加入init函數(shù)用于初始化操作
構(gòu)造函數(shù)沒(méi)有返回值,難以反饋錯(cuò)誤
8:不要在構(gòu)造函數(shù)中調(diào)用虛函數(shù)
構(gòu)造函數(shù)中的虛函數(shù)不會(huì)重定向到子類偎巢。
9:慎用繼承
相比對(duì)象組合蔼夜,繼承帶來(lái)更強(qiáng)的依賴,推薦使用接口繼承而不是對(duì)象繼承
10:禁用多重繼承(接口繼承除外)
多重繼承通常代表不好的設(shè)計(jì)
11:慎用運(yùn)算符重載
運(yùn)算符重載會(huì)混淆代碼的語(yǔ)義压昼,應(yīng)只在不會(huì)造成混淆時(shí)使用
12:將成員設(shè)置為私有并提供訪問(wèn)函數(shù)
封裝是降低代碼耦合的有力武器
13:將同一訪問(wèn)權(quán)限的成員定義在一起
可以按照public,protect,private順序進(jìn)行組織
14:避免出現(xiàn)大而全的類
當(dāng)一個(gè)類的代碼超過(guò)1000行求冷,應(yīng)有所警惕瘤运,超過(guò)2000行,則應(yīng)考慮拆分(行數(shù)不包括注釋)
15:頭文件應(yīng)包含它所需要的頭文件
這樣可以保證cpp文件引入該頭文件后不需要包含其它頭文件
16:合理的組織引入的頭文件匠题,不要重復(fù)引入拯坟,不要引入不必要的頭文件
可以以系統(tǒng)頭文件,第三方庫(kù)頭文件韭山,項(xiàng)目組庫(kù)頭文件郁季,本程序頭文件來(lái)組合,不同類型頭文件之間用空格隔開
17:頭文件使用#define宏來(lái)避免多重包含
pragma once 指令只有VC編譯器能識(shí)別
18:允許合理的使用友元特性
19:使用引用傳遞對(duì)象類型參數(shù), 對(duì)于不需要改變的參數(shù)加入const修飾符
引用傳遞可以避免對(duì)象拷貝
20:函數(shù)應(yīng)該進(jìn)參在前钱磅,出參在后
21:使用明確的返回值指示函數(shù)的運(yùn)行結(jié)果梦裂,而不是用返回的內(nèi)容來(lái)指示結(jié)果
推薦: int GetDeviceName(string& deviceName);
不推薦: string GetDeviceName()
22:聲明基本類型變量后立即賦值
正確 int nCount = 0; bool bSuc = false; 錯(cuò)誤 int nCount; bool bsuc;
23:使用內(nèi)聯(lián),枚舉,常量來(lái)代替宏
宏的使用有很多弊端,應(yīng)盡量避免
24:使用singleton模式代替靜態(tài)類
相比靜態(tài)類后专,singleton模式可以更好地控制初始化時(shí)機(jī)。
25:使用share_ptr來(lái)管理指針
指針和管理在復(fù)雜項(xiàng)目中十分困難冗恨,使用智能指針是不二選擇
26:使用weak_ptr來(lái)處理循環(huán)引用
27:明確對(duì)象或資源的生存周期
明確對(duì)象的生存周期通常代表著良好的設(shè)計(jì)
28:合理的使用縮進(jìn),空格
最重要的是保持風(fēng)格的統(tǒng)一味赃,自動(dòng)生成的代碼可能會(huì)打破這種統(tǒng)一掀抹,應(yīng)該靈活設(shè)計(jì)規(guī)則
29:合理使用typedef縮減類型的長(zhǎng)度,合理使用auto
使用stl時(shí)經(jīng)常會(huì)導(dǎo)致過(guò)長(zhǎng)的類型洁桌,合理使用auto可以有效減少代碼長(zhǎng)度
命名規(guī)則
由于是在VC環(huán)境下開發(fā)渴丸,沿用微軟的命名駝峰命名法
選擇哪種命名方式實(shí)際上不是很重要,最重要的是保持統(tǒng)一
- 代碼文件: DeviceMgr.h, DeviceMgr.cpp
- 解決方案目錄: NetLibrary
- 工程篩選器: DataModel
- 類:CDeviceMgr
- 結(jié)構(gòu)體: DeviceInfo
- 變量: listDevice
- 類成員: m_deviceName
- 函數(shù): GetName; GetDeviceName;
- 代表bool含義的變量: 都以b開頭另凌,如:bOk, bSafe
- 代表整數(shù)含義的變量: i表示符號(hào)整數(shù)谱轨,n表示無(wú)符號(hào)整數(shù)
- 避免無(wú)意義的變量名和縮寫: 如 x,dn(deviceName)等
注釋規(guī)則
- 文件注釋:注明文件作者吠谢,聯(lián)系方式土童,文件代碼作用,重大修改記錄等
- 類注釋:說(shuō)明類的作用工坊,使用限制等等
- 函數(shù)注釋: 盡量依靠意義明確的函數(shù)命名而不是依靠注釋献汗,說(shuō)明函數(shù)的使用限制,對(duì)于意義不明確的參數(shù)加以說(shuō)明
- 變量注釋:盡量依靠意義明確的變量命名而不是依靠注釋,特殊情況王污。
- 實(shí)現(xiàn)注釋: 對(duì)于使用了非常規(guī)技巧罢吃,或復(fù)雜算法,或很復(fù)雜的業(yè)務(wù)邏輯部分要加入注釋說(shuō)明
原則: 注釋應(yīng)風(fēng)格統(tǒng)一昭齐,簡(jiǎn)短而意義明確尿招,最終目的是有效幫助其它人閱讀和理解代碼的目的
日志
日志的打印十分重要,是產(chǎn)品發(fā)生問(wèn)題時(shí)重要的參考依據(jù)
- 日志的打印要盡量詳盡,合理劃分日志等級(jí)就谜,一般為Fatal, ERROR, WARNING, DEBUG, INFO
- 統(tǒng)一使用unicode(utf-16)編碼來(lái)輸出日志
- 提供異步打印日志的接口
- 提供定期清理日志的機(jī)制
- 在發(fā)布版中將日志等級(jí)設(shè)置為ERROR或更高怪蔑,提供配置文件供調(diào)整日志等級(jí)
一些其它規(guī)則
- 避免大而全的類(代碼控制在1000行以內(nèi))
- 避免過(guò)長(zhǎng)的函數(shù)(代碼控制在200行以內(nèi))
- 避免深層的嵌套(不要超過(guò)3層)
- 使用do-while-break技巧來(lái)避免重復(fù)寫釋放資源的代碼
- 盡量使用RALL技巧來(lái)釋放資源
- 當(dāng)函數(shù)或代碼廢棄時(shí),應(yīng)與標(biāo)注丧荐,最好將其注釋掉并定期清理
線程和鎖
復(fù)雜的項(xiàng)目肯定會(huì)涉及到多線程開發(fā)缆瓣,而開發(fā)多線程程序是十分困難的。據(jù)我們統(tǒng)計(jì)虹统,項(xiàng)目組產(chǎn)品有70%左右的崩潰和bug和線程有關(guān)弓坞。
將線程模塊獨(dú)立出來(lái),交給項(xiàng)目最有經(jīng)驗(yàn)的開發(fā)人員管理和維護(hù)车荔,對(duì)外暴露抽象接口昼丑,屏蔽線程的概念。
強(qiáng)制使用RALL技術(shù)來(lái)使用鎖
未盡
本文并沒(méi)有涉及到C++規(guī)范的所有方面夸赫,歡迎討論和補(bǔ)充