C++代码整洁之道
整潔的代碼在團隊中無疑是很受歡迎的,可以高效的被其它成員理解和維護,本文參考《C++代碼整潔之道》和《Google C++編碼規范》,結合自己的一些想法整理如下:
C++本身作為面向對象語言,首先介紹下面向對象一般涉及到的開發原則。
面向對象開發原則
依賴倒置原則:針對接口編程,依賴于抽象而不依賴于具體,抽象(穩定)不應依賴于實現細節(變化),實現細節應該依賴于抽象,因為穩定態如果依賴于變化態則會變成不穩定態。
開放封閉原則:對擴展開放,對修改關閉,業務需求是不斷變化的,當程序需要擴展的時候,不要去修改原來的代碼,而要靈活使用抽象和繼承,增加程序的擴展性,使易于維護和升級,類、模塊、函數等都是可以擴展的,但是不可修改。
單一職責原則:一個類只做一件事,一個類應該僅有一個引起它變化的原因,并且變化的方向隱含著類的責任。
里氏替換原則:子類必須能夠替換父類,任何引用基類的地方必須能透明的使用其子類的對象,開放關閉原則的具體實現手段之一。
接口隔離原則:接口最小化且完備,盡量少public來減少對外交互,只把外部需要的方法暴露出來。
最少知道原則:一個實體應該盡可能少的與其他實體發生相互作用。
將變化的點進行封裝,做好分界,保持一側變化,一側穩定,調用側永遠穩定,被調用側內部可以變化。
優先使用組合而非繼承,繼承為白箱操作,而組合為黑箱,繼承某種程度上破壞了封裝性,而且父類與子類之間耦合度比較高。
針對接口編程,而非針對實現編程,強調接口標準化。
C++開發原則
通過上述面向對象開發原則的理解可以細化到具體C++開發原則。
保持簡單和直接原則(KISS, Keep it simple and stupid):保持代碼盡可能簡單,如果需求需要的話,才在代碼中引入靈活的可變點,只添加那些可使整體變得更簡單的局部復雜的東西。
不需要原則(YAGNI, You’re not gonna need it):總是在你真正需要的時候再實現他們,而不是在你只是預見到你將來會需要他們而去實現,在真正需要的時候再寫代碼,那時再重構也來得及。
避免復制原則(DRY, Do not repeat yourself):不要復制,不要重復,這是相當危險的操作,你修改一處代碼的時候總能記得去修改另外一處或另外多處你曾經復制的代碼嗎?
信息隱藏原則:一段代碼調用了另外一段代碼,調用者不應該知道被調用者代碼的實現,否則調用者就有可能修改被調用者的實現來實現某些功能,而這有可能引發其它調用者的bug。
高內聚低耦合原則:類似單一職責原則,明確每個模塊的具體責任,盡量少的依賴于其它模塊。
最少驚訝原則:函數功能要與函數名字功能一致,難道你要在一個getter()函數去更改成員變量的值嗎?
更干凈原則(自命名):離開露營地的時候,應讓露營地比你來之前還要干凈,當發現代碼中有需要改進或者風格不好的地方,應該立刻改掉,不要care這段代碼的原作者是誰,也不要care這是誰的模塊,代碼所有權是集體的,每個團隊成員在任何時候都應該可以對任何代碼進行更改和擴展。
關于面向對象設計原則可以參考一文讓你搞懂設計模式
注重單元測試
重要性就不多說了,防患于未然,構建大型系統尤其需要進行單元測試,保證代碼質量,可以防患于未然。一般都講究測試驅動開發,開發一個功能首先要想好怎么測試,先把測試代碼寫好,再去開發對應的需求。通過單元測試也有利于開發者更好的進行接口的設計,主要說下良好的單元測試的原則。
單元測試的原則
保證單元測試的代碼的質量,單元測試的代碼也是代碼,不應該和產品代碼區別對待,而且單元測試的代碼再寫出bug更影響測試效率。
單元測試的命名, 每個測試單元需要根據具體測試內容進行相應的命名,方便定位分析問題,好的命名如果出現問題時通過測試單元的名字基本就可以定位問題。
保證單元測試的獨立性,每個測試單元都是獨立的,不依賴于其它測試單元,不要構建測試單元的上下文,上面的測試單元出問題影響到下面的單元測試的設計是很不友好的。
盡量保證一個測試單元使用一個斷言,保證測試單元內部的一個相對獨立性,上面的斷言阻礙了下面的斷言測試也是不好的設計。
保證單元測試環境的獨立,保證每個測試單元都有獨立的環境,不依賴于其它環境,每個測試單元都要是個獨立的可運行的實例,每個單元測試結束后記得清理環境。
沒必要對第三方庫和外部系統做單元測試,只對自己寫的代碼進行測試。
單元測試盡量不要涉及數據庫,數據庫的狀態是全局的,測試不能保證獨立性,而且數據庫的訪問也是緩慢的,影響單元測試的速度,如果真的需要可以模擬數據庫在內容中進行測試,其實通常是在系統集成和系統測試級別時去測試數據庫。
不要混淆測試代碼和產品代碼,產品代碼中不應依賴測試代碼。
測試必須要快速執行,確保秒級別,大型系統的單元測試也就幾分鐘而已,單元測試不要訪問數據庫、磁盤、網絡等外設。
找一些測試替身,例如有些數據需要通過網絡獲取,那可以利用依賴注入做一個網絡替身的類模擬這些數據的產生,可以研究研究Google mock。
良好的命名
無論是什么語言,函數和變量的良好命名都是很有必要的,通過函數的名字我們就可以知道這個函數里代碼的作用,而不是通過寫注釋,個人一直傾向于用代碼自解釋。
文件命名
文件名字要全部小寫,中間用_相連,后綴名為.cc和.h
類型命名
類型名稱的每個單詞首字母均大寫, 不包含下劃線: MyExcitingClass, MyExcitingEnum.
變量命名
不要將變量的類型在名字中體現,這樣以后變量類型改變的話還需要去改動變量名,充分利用IDE的功能,變量 (包括函數參數) 和數據成員名一律小寫, 單詞之間用下劃線連接. 類的成員變量以下劃線結尾, 但結構體的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.
常量命名
聲明為 constexpr 或 const 的變量, 或在程序運行期間其值始終保持不變的, 命名時以 “k” 開頭, 大小寫混合
const int kDaysInAWeek = 7;
函數命名
常規函數使用大小寫混合, 取值和設值函數則要求與變量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().
枚舉命名
和常量一致
Tip:
除非像swap函數里tmp那種一目了然,否則不要搞無意義的命名,函數名變量名字寧可特別長也要寫清楚究竟是什么意思,不要用縮寫,一個變量盡量在臨近使用前才定義,可讀性強也可更好利用cpu cache。
編輯器
團隊可以統一使用相同的編輯器,個人目前使用的是VS Code編輯器,同時每個項目使用統一的.clang_format文件,統一規范代碼格式,所有的換行符都要用LF格式,不要用CRLF格式,在右下角可以設置。
個人的.clang-format文件如下,是在google風格的基礎上做了些修改:
BasedOnStyle: Google IndentWidth: 4 ColumnLimit: 120 SortIncludes: true MaxEmptyLinesToKeep: 2C++編碼規范要點小總結
每個頭文件都要使用#define避免被重復引用
命名格式 <PROJECT>_<PATH>_<FILE>_H_#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_或使用#pragma once,而#define方式更通用
鼓勵在 .cc 文件內使用匿名命名空間或 static 聲明. 使用具名的命名空間時, 其名稱可基于項目名或相對路徑. 禁止使用 using 指示, 禁止使用內聯命名空間(inline namespace)
一行盡量不要超過120個字符,一個函數盡量不要超過40行,同時一個文件盡量控制在500行內.
所有的引用形參如不做改動一律加const,在任何可能的情況下都要使用 const或constexpr
new內存的地方盡量使用智能指針,c++11 就盡量用std::unique_ptr替代std::auto_ptr
合理使用移動語義,減少內存拷貝,參考左值引用、右值引用、移動語義、完美轉發,你知道的不知道的都在這里
禁止使用 RTTI,盡量在編譯期間就確定參數類型,不要搞運行時識別typeid這種代碼
使用 C++ 的類型轉換, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等轉換方式
明確使用前置++還是后置++的具體含義,如不考慮返回值,盡量使用效率高的前置++ (++i)
不要使用uint類型,如果需要使用大整型可以考慮int64,否則類型的隱式類型轉換會帶來很多麻煩
如無特殊必要不要使用宏,可以考慮使用const或constexpr替代宏,宏的全局作用域很麻煩,如果非要用在馬上要使用時才進行 #define, 使用后要立即 #undef
google文檔說一定不要用宏來控制條件編譯(但是我自己還沒有查到不用宏如何控制條件編譯,或許就不要搞條件編譯)
盡可能用 sizeof(varname) 代替 sizeof(type).使用 sizeof(varname) 是因為當代碼中變量類型改變時會自動更新. 您或許會用 sizeof(type) 處理不涉及任何變量的代碼,比如處理來自外部或內部的數據格式,這時用變量就不合適了
類型名如果過長的話可以考慮使用auto關鍵字
注釋統一使用 // ,不要通過注釋禁用代碼,擅用git,不要為易懂的代碼寫注釋
寫完代碼后記得format,VS Code(windows快捷鍵) shift + alt + F ,每個項目最好都有統一的.clang_format文件
使用C++的string和stream替代C語言風格的char*,使用std::ostream和std::cout替代printf()、sprintf()等
盡量使用STL標準庫的容器而不是C語言風格的數組,數組的越界訪問之類當時是不會報錯的,反而可能弄臟堆棧信息,導致奇奇怪怪難以排查的bug
可以更多的使用模板元編程,盡量多的使用constexpr等編譯器計算,編譯器是我們的好搭檔,個人認為模板元編程以后會是C++的主流技術
可以考慮更多的使用異常處理方式,而不是C語言風格的errno錯誤碼等,這里可以參考你的c++團隊還在禁用異常處理嗎?
附:本文不是技術文章,介紹較為主觀,可能和很多人想法有所沖突,各位可以結合自己的經歷經驗酌情參考。
需要學習C++,建議進這個群一起聊,跟前輩一起探討,也會得到很多幫助。也可以交流學習心得,技術問題,可以獲取PDF書籍源碼、教程等給大家免費使用 。
總結
- 上一篇: Mac系统中MongoChef链接Mon
- 下一篇: 谈谈对python这门课的认识_【Pyt