整潔的代碼在團隊中無疑是很受歡迎的,能夠高效的被其它成員理解和維護,本文參考《C++代碼整潔之道》和《Google C++編碼規範》,結合本身的一些想法整理以下:nginx
C++自己做爲面嚮對象語言,首先介紹下面向對象通常涉及到的開發原則。c++
面向對象開發原則
依賴倒置原則:針對接口編程,依賴於抽象而不依賴於具體,抽象(穩定)不該依賴於實現細節(變化),實現細節應該依賴於抽象,由於穩定態若是依賴於變化態則會變成不穩定態。git
開放封閉原則:對擴展開放,對修改關閉,業務需求是不斷變化的,當程序須要擴展的時候,不要去修改原來的代碼,而要靈活使用抽象和繼承,增長程序的擴展性,使易於維護和升級,類、模塊、函數等都是能夠擴展的,可是不可修改。數據庫
單一職責原則:一個類只作一件事,一個類應該僅有一個引發它變化的緣由,而且變化的方向隱含着類的責任。編程
里氏替換原則:子類必須可以替換父類,任何引用基類的地方必須能透明的使用其子類的對象,開放關閉原則的具體實現手段之一。windows
接口隔離原則:接口最小化且完備,儘可能少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_.
class TableInfo {...private:string table_name_; // 好 - 後加下劃線.string tablename_; // 好.static Pool<TableInfo>* pool_; // 好.int i_table; // 很差,不要將變量的類型在名字中體現};
常量命名
聲明爲 constexpr 或 const 的變量, 或在程序運行期間其值始終保持不變的, 命名時以 「k」 開頭, 大小寫混合
const int kDaysInAWeek = 7;
函數命名
常規函數使用大小寫混合, 取值和設值函數則要求與變量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().
枚舉命名
和常量一致
enum UrlTableErrors { kOK = 0, kErrorOutOfMemory, kErrorMalformedInput,};
Tip:
除非像swap函數裏tmp那種一目瞭然,不然不要搞無心義的命名,函數名變量名字寧肯特別長也要寫清楚到底是什麼意思,不要用縮寫,一個變量儘可能在臨近使用前才定義,可讀性強也可更好利用cpu cache。
編輯器
團隊能夠統一使用相同的編輯器,我的目前使用的是VS Code編輯器,同時每一個項目使用統一的.clang_format文件,統一規範代碼格式,全部的換行符都要用LF格式,不要用CRLF格式,在右下角能夠設置。
我的的.clang-format文件以下,是在google風格的基礎上作了些修改:
BasedOnStyle: GoogleIndentWidth: 4ColumnLimit: 120SortIncludes: trueMaxEmptyLinesToKeep: 2
C++編碼規範要點小總結
每一個頭文件都要使用#define避免被重複引用
命名格式 <PROJECT>_<PATH>_<FILE>_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++代碼整潔之道》
https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/contents/
REVIEW
本文分享自微信公衆號 - 程序喵大人(dmjjzl1)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。