寫在前面
借著公司內(nèi)和其他小組的一個(gè)分享科侈,把自己幾年來C++開發(fā)的一點(diǎn)思考總結(jié)一下。全篇沒有高屋建瓴的觀點(diǎn)孝凌,基本都是些細(xì)節(jié)方面的注意事項(xiàng)离钝。希望能給大家提供一點(diǎn)幫助票编。
構(gòu)建工具
C/C++世界里有不少的構(gòu)建工具:make、autotools卵渴、scons慧域、CMake、Bazel浪读。但近幾年比較流行的昔榴,也就是CMake和Bazel。所以這一部分碘橘,也就大概對(duì)比下這兩個(gè)工具吧互订。
究竟該選擇哪個(gè)工具,我覺得可以從如下幾個(gè)方面來對(duì)比一下:
1. 上手難度
因?yàn)锽azel采用了類似Python的語法痘拆,所以其學(xué)習(xí)曲線相比CMake要平緩一些仰禽。但當(dāng)我們考慮上手難度時(shí),除了學(xué)習(xí)曲線之外纺蛆,還要考慮文檔的完備性吐葵、該工具的通用性等各個(gè)角度。當(dāng)綜合考慮時(shí)桥氏,我覺得CMake是一個(gè)盡管保守但仍舊不錯(cuò)的選擇温峭。主要原因就在于,CMake幾乎已經(jīng)成為現(xiàn)在C++的事實(shí)標(biāo)準(zhǔn)字支。使用CMake凤藏,就意味著:
- 你可以把你熟悉CMake的技能用在折騰別的C++項(xiàng)目上奸忽。而這點(diǎn)之所以重要,是因?yàn)槟阍诶媚硞€(gè)第三方庫的時(shí)候清笨,往往需要大概研究下它的編譯過程月杉。
- CMake的官方文檔和stackoverflow上的問答也比較完善刃跛。一旦遇到一個(gè)問題抠艾,往往通過搜索引擎能快速的得到答案。
另外桨昙,從設(shè)計(jì)理念上來看检号,CMake提供的解決方案是改革式的:它并沒有提供一個(gè)全新的解決方案,而是和Make蛙酪、Visual Studio或者其他現(xiàn)有的構(gòu)建工具來結(jié)合使用的齐苛。而這就使得你無需丟棄在其他工具上所積累起來的開發(fā)經(jīng)驗(yàn)——例如你熟悉make工具,哪怕是一個(gè)CMake維護(hù)的項(xiàng)目桂塞,你也可以毫不費(fèi)力就知道如何來查看編譯參數(shù)凹蜂,以及控制編譯并發(fā)度等等。
而對(duì)于Bazel則不是如此阁危。Bazel完全以革命者的姿態(tài)完整提供了一整套解決方案玛痊,所有的使用細(xì)節(jié)你都要從頭開始。加上文檔的匱乏狂打,這就使得你也得花上一段時(shí)間擂煞,才能熟悉Bazel。
2. thirdparty的管理
Bazel內(nèi)置了對(duì)thirdparty源碼級(jí)別依賴的支持:
- thirdparty可以是用Bazel構(gòu)建的趴乡,也可以不是对省。對(duì)于非Bazel項(xiàng)目,你需要額外為其添加一個(gè)Bazel的描述文件晾捏。
- thirdparty可以是一個(gè)本地項(xiàng)目蒿涎,也可以是一個(gè)git倉庫或者h(yuǎn)ttp鏈接
所以總的來看,Bazel對(duì)thirdparty支持還是非常友好的惦辛。
就這點(diǎn)對(duì)比來看劳秋,CMake其實(shí)做的是不太好的。CMake盡管也有ExternalProject的feature裙品,但根據(jù)實(shí)際經(jīng)驗(yàn)來看俗批,使用和維護(hù)都比較的復(fù)雜。所以我還是更傾向于寫幾個(gè)腳本來下載和編譯這些thirdparty依賴市怎。
這里可以拿我參與維護(hù)的Pegasus項(xiàng)目為例岁忘。在該項(xiàng)目中,我們依賴了幾個(gè)不同類型的項(xiàng)目:
- 從構(gòu)建工具上來看区匠,這些依賴有使用CMake的干像,有使用make的帅腌,有使用autotools的
- 從來源上來看,有的依賴來自git倉庫麻汰,有的來自http鏈接速客,有的則是從一個(gè)大的項(xiàng)目里面挑選了一個(gè)更小的模塊使用
- 從代碼的使用方式上來看,有的是直接拿來用五鲫,有的還需要稍微修改下源代碼溺职。
而通過shell腳本,這些各種各樣的場(chǎng)景我們都能非常方便位喂、直接浪耘、易維護(hù)的得以支持。
3. 其它
Bazel和CMake當(dāng)然還有些其它方面值得對(duì)比塑崖,但并非一些通用的點(diǎn)七冲,這里就簡單列舉下,不再詳細(xì)展開了:
- IDE集成
- 緩存編譯結(jié)果规婆,從而加速編譯過程
- 多語言混合變成的支持
- 分布式編譯
- 跨平臺(tái)的支持
再補(bǔ)充一個(gè)別人的討論鏈接, 有需求的可以參考一下澜躺。
編程規(guī)范
強(qiáng)烈推薦Google C++ Style。盡管它禁止了很多C++ feature而被很多人黑的很慘抒蚜,但從工程的角度而言掘鄙,它的確提供了非常多極其中肯的建議。說到底削锰,編程規(guī)范的存在通铲,主要就是可以讓水平參差不齊的工程師們,可以在一起協(xié)作出風(fēng)格較為一致的項(xiàng)目來器贩。
也存在一些工具可以對(duì)google規(guī)范進(jìn)行檢查:
因?yàn)間oogle的規(guī)范文檔對(duì)C++ feature的取舍原因講的非常好颅夺,這里就不再贅述了。唯一想補(bǔ)充的是異常:
- C++在語法層面對(duì)異常支持不太友好:你無法通過函數(shù)簽名來得知一個(gè)函數(shù)到底會(huì)拋出哪些異常蛹稍。例如:
如果這個(gè)接口沒有良好文檔或注釋吧黄,并且也沒有代碼可翻時(shí),你在調(diào)用這個(gè)接口時(shí)很有可能會(huì)漏掉一些錯(cuò)誤情況——因?yàn)樗赡軖伋霎惓K艚恪8氖寝挚粋€(gè)疏于捕獲的異常一旦觸發(fā),線上的程序就會(huì)crash奉芦。void GetSomeResource(const char* resource_name);
其實(shí)解釋這么多赵抢,大家只要和Java中的異常機(jī)制對(duì)比一下,就高下立判了声功。對(duì)于這個(gè)話題烦却,王垠的這篇博客值得一看的。 - 在運(yùn)維Pegasus項(xiàng)目時(shí)先巴,遇到過一個(gè)老版本glibc的bug:如果多個(gè)線程同時(shí)拋出異常其爵,程序會(huì)陷入死循環(huán)冒冬。這個(gè)bug的發(fā)現(xiàn)也是個(gè)有趣的過程,后面我專門寫篇文章展開吧摩渺。
- 在禁用異常后简烤,程序就只能用錯(cuò)誤碼來進(jìn)行錯(cuò)誤處理。對(duì)于很多項(xiàng)目摇幻,大家都采用一套類似的范式横侦,可以參考tensorflow的做法
C++的新特性
如果能使用C++的新特性,當(dāng)然是盡量使用的好囚企。我自己在開發(fā)中丈咐,覺得非常方便必須使用的新特性有:
- 智能指針
- 右值瑞眼,以及C++14中右值得capture
- lambda, bind
- initialize list
想補(bǔ)充說一下的是auto龙宏,我自己不是特別喜歡這個(gè)feature,也非常贊同google規(guī)范中的對(duì)auto的限制:僅當(dāng)可以提高代碼可讀性時(shí)伤疙,使用auto
這里不由得就想扯起java 10中的var银酗。雖然能方便開發(fā),但覺得更多的是會(huì)被濫用徒像。而一個(gè)可能被濫用的feature黍特,還不如沒有的好。
第三方utility
在做項(xiàng)目開發(fā)的時(shí)候锯蛀,一般會(huì)有很多瑣碎的需求灭衷,從而也需要很多utility工具包。這里把我遇到的一些需求整理一下:
- 算法和數(shù)據(jù)結(jié)構(gòu):stl, boost
- 錯(cuò)誤碼管理:參見tensorflow
- C語言的字符串封裝:string_view
- 字符串的各種操作旁涤、轉(zhuǎn)換翔曲、打印:可以多翻翻abseil, 以及folly劈愚,另外也推薦fmtlib
- 線程安全的瞳遍、無鎖的數(shù)據(jù)結(jié)構(gòu)、線程池: folly
- google全家桶:gtest菌羽,gflags, glog, protobuf, grpc
最后掠械,也推薦下kudu這個(gè)項(xiàng)目,里面有自己實(shí)現(xiàn)的一些工具包注祖,以及對(duì)google開源項(xiàng)目中utility的整理猾蒂。
單元測(cè)試
每個(gè)程序員都討厭寫測(cè)試。就我自己而言是晨,我覺的單元測(cè)試的目的有以下幾個(gè):
- 確保功能的實(shí)現(xiàn)和預(yù)期一致
- 防止程序在重構(gòu)的時(shí)候出問題
- 給模塊的使用者肚菠,提供使用示例
值得一提的是,對(duì)于C++項(xiàng)目署鸡,除了功能性測(cè)試之外案糙,你最好還能讓你的單元測(cè)試通過一些自動(dòng)化工具的檢測(cè)限嫌,如:
- valgrind:檢查內(nèi)存泄露,以及非法訪存
- Address Sanitizer:檢測(cè)非法訪存
- Thread Sanitizer:檢測(cè)線程競(jìng)爭
寫在最后
自己的整理這些內(nèi)容時(shí)时捌,腦子里反復(fù)縈繞的一個(gè)問題是:我們?cè)陂_發(fā)一個(gè)項(xiàng)目時(shí)怒医,所要遵守的各種流程和規(guī)范到底是不是真的有必要的?說的更直白一點(diǎn)就是奢讨,“代碼潔癖”這東西到底有沒有意義稚叹?
我的看法是:代碼潔癖不是一個(gè)原則,而是在投入和產(chǎn)出上的一種權(quán)衡拿诸。如果僅僅快速試錯(cuò)扒袖,那么就不需要維持代碼潔癖,因?yàn)槟阃耆恢滥憬裉鞂懙拇a究竟能存活多久亩码。而如果是一個(gè)馬拉松式的項(xiàng)目季率,代碼潔癖就值得維持,因?yàn)樗鼘?duì)于項(xiàng)目的維護(hù)的確很有意義描沟。
最后飒泻,貼一個(gè)自己比較喜歡的C++博客。