C++代碼整潔之道:C++17 可持續軟體開發模式實踐
Clean C++: Sustainable Software Development Patterns and Best Practices with C++ 17
[德]斯蒂芬·羅斯(Stephan Roth)著
連少華 郭發陽 陳濤 譯
第1章
簡介
如何去做和做到同樣重要。
—Eduardo Namur
目前大部分軟體項目的開發形勢依然很嚴峻,甚至有些項目處于嚴重的危機之中。其原因是多種多樣的,有的項目是由項目管理的糟糕導緻的,有的項目則是由于開發過程中需求的不斷變化,而開發方式不能适應導緻的。
在某些項目中,導緻這一結果的隻是單純的技術原因,即代碼品質不高。但這并不意味着代碼的功能沒有實作,相反代碼的外部品質看似很高,能夠通過品質保證部門的黑盒測試、使用者測試以及驗收測試。代碼能夠完美地通過QA,并且在測試報告上找不到任何錯誤。軟體使用者對軟體也很滿意,甚至軟體能夠按時傳遞并且不超出預算(當然,這種情況極少)。一切看起來很美好,但事實真的如此嗎?
真相是這份能夠正常工作的代碼的内部品質實際上很低。通常代碼可讀性不高,維護和擴充困難。軟體的組成單元,如類、函數非常臃腫,有的甚至會達到上千行的代碼量。太複雜的依賴關系導緻的結果就是,改變其中某一小部分内容所造成的影響将是難以預估的。軟體的架構不具有前瞻性,其結構可能是由開發人員的臨時“拍腦袋”決定的,也就是一些開發人員常說的“曆史衍生軟體”或者“随意的架構”。類、函數、變量、常量命名不規範,含義不明确,并且代碼被大量無用的注釋包圍,有些注釋已經過時了,有些注釋隻描述了顯而易見的東西,甚至是完全錯誤的。不少開發人員害怕修改或擴充軟體,因為他們知道自己的軟體很脆弱,單元測試覆寫率很低甚至沒有單元測試。在這樣的項目中,“不要碰已經能夠運作的系統”的聲音不絕于耳。一個新的特性從開發到部署上線,通常不是幾天就能完成的,這需要幾周甚至幾個月的時間才能完成。
這種糟糕的軟體通常被稱作“a Big Ball Of Mud”。這個術語由Brian Foote和Joseph W. Yoder在第四次模式程式設計語言會議上的一篇論文中第一次提到。Foote和Yoder将其解釋為“……結構随意的、笨拙的、草率的、盤根錯節的代碼雜糅在一起”。這種軟體系統維護起來是一個噩夢,不僅代價高昂還會花費大量時間,它通常會拖垮整個開發團隊。
上述現象在整個程式設計領域中都客觀存在着,與使用哪種程式設計語言沒有關系,不管使用的是 Java、PHP、C、C#、C++,還是其他任何語言,都有可能産生“a Big Ball Of Mud”。那麼,産生這一問題的根源是什麼呢?
1.1 軟體熵
首先,有些東西就好像是一種自然規律,就像其他一些封閉和複雜的系統,軟體會随着時間的推移而變得混亂,這種現象稱為軟體熵。這個詞借鑒了熱力學第二定律,意思是指,一個封閉系統的總混亂度不會減小,隻能保持不變或增加。軟體的表現看起來就是這樣的,每次添加一個新功能或者改變一些原有功能,代碼都會變得更加混亂,有很多影響因素能夠提高軟體熵,舉例如下:
□不切實際的項目進度安排會給程式員增加壓力,進而迫使開發人員以一種糟糕和非專業的方式處理開發工作。
□當今,軟體系統大都龐大而複雜。
□開發人員擁有不同的技能水準和開發經驗。
□全球分布的、跨文化差異的團隊,執行和交流方面存在的問題。
□開發人員主要關注軟體的功能性方面(功能性的需求和系統的用例),以緻品質要求(例如非功能性要求),如性能、可維護性、可用性、可移植性、安全性等被忽略甚至被完全忘記了。
□不當的開發環境和糟糕的開發工具。
□管理層專注于眼前利益,而不了解可持續軟體開發的價值所在。
□快速而糟糕的程式開發以及軟體設計與實作的不一緻(例如破窗理論)。
破窗理論
破窗理論是在美式犯罪研究中發展起來的。該理論指出,一幢被遺棄的建築物中的一個被破壞的窗戶,可能是整個周邊地區開始破敗的一個觸發器。破碎的窗戶給環境發出了緻命的信号:“看,沒人在乎這幢大樓!”,這引起了進一步的腐化、破壞和其他反社會行為。破窗理論一直是刑事政策學的很多改革的基礎,特别是發展出零容忍政策。
在軟體開發中,該理論被采用并應用于代碼品質。對程式不适當的開發和糟糕的實作稱為“破窗”。如果這些不好的實作沒有被修複,那麼會有更多不适當的代碼出現在它們周圍,是以,代碼的混亂就開始了。
不要容忍“破窗”出現在你的代碼中—及時地改正它們。
然而,似乎C和C++項目特别容易出現混亂,而且比其他程式設計語言更容易陷入一種糟糕的狀态。即使是在網際網路上,同樣也充斥着大量的、糟糕的C++代碼的例子,它們看起來快速且高度優化,但實際上是使用了高技巧的文法,并且完全忽略了設計良好和易維護的代碼的基本準則。
導緻本節問題的原因之一可能在于C++是一個中等層次的多範型程式設計語言,即它包含了進階和低級語言的特點。使用C++程式設計語言,你可以編寫龐大且複雜的使用者界面的分布式業務軟體系統,也可以編寫小型的嵌入式實時響應系統,這要求與底層硬體密切關聯。多範型程式設計語言意味着你能夠編寫程式化、功能化或面向對象的程式,甚至三種範型的混合體。此外,C++支援模闆元程式設計(Template Metaprogramming,TMP),這種技術用到了一種被稱作模闆的東西,模闆被編譯器用于生成臨時使用的源代碼,而這些臨時的源代碼會和其餘的源代碼合并在一起,并進行編譯。自從ISO釋出了支援C++11标準以來,更多的方法被加入到C++中,例如,具有匿名函數的函數式程式設計現在能通過lambda表達式以一種非常優雅的方式完成。由于這些多樣性能力,C++同時也具有非常晦澀難懂、複雜和煩瑣的名聲。
開發出糟糕軟體的另一個原因可能是很多程式開發者并沒有IT背景。如今,任何人都可以開始開發軟體,不必管他是否獲得了大學學位或者是否是任何其他計算機科學方面的人才。絕大多數C++開發者都是(或曾是)非專業人員,特别是在汽車、鐵路運輸、航空航天、電氣/電子或機械工程等技術領域。許多開發工程師在投入程式設計之前的幾十年裡并沒有受到過計算機科學方面的教育,随着複雜度的增加以及技術系統包含了越來越多的軟體,世界對程式員的需求很迫切,這種需求被現存的其他勞動力所補充了,電氣工程師、數學家、實體學家,還有很多人嚴格來講并非是經專業訓練過而開始開發軟體的,他們主要通過自學和實操而簡單地進行開發,并且他們已經盡了最大的努力。
基本上來看,這絕對是無可厚非的,但有時隻知道開發工具和程式設計語言是不夠的!軟體開發與程式設計不一樣,世界上很多的軟體是由沒有經過教育訓練的軟體開發人員在一起開發和維護的,開發人員要在抽象層次考慮很多的事情,以便建立一個可持續的系統,例如架構和設計。如何建構高品質達到某些目标的系統?面向對象的東西有什麼好處?我如何有效地使用它呢?某個架構或庫的優點和缺點是什麼?各種算法之間的差異是什麼?為什麼同一個算法不适合所有類似的問題?到底什麼是有限狀态機,為什麼它有助于處理複雜性問題?
不要灰心!一個軟體的持續健康需要有人去關注它,而整潔的代碼就是處理的關鍵所在!
1.2 整潔的代碼
人們常常混淆整潔的代碼和“漂亮的代碼”。其實,整潔的代碼中并不包含漂亮的因素。專業的程式員通常不會寫漂亮的代碼,因為,他們是以專家的身份而工作,并為客戶創造價值的。
整潔的代碼是容易被任何團隊的成員了解和維護的。
整潔的代碼是高效工作的基礎。如果你的代碼是整潔的并且測試覆寫率也比較高,增加一個函數或修改一部分代碼,隻會花費幾個小時或幾天的時間,否則可能需要幾周或數月的時間才能完成開發、測試和部署。
整潔的代碼是軟體持續發展的基礎,能夠在沒有欠下大量技術債務的情況下保證軟體運作很長的一段時間。開發人員必須積極主動地維護代碼并保證代碼保持一定的風格,因為代碼是軟體開發公司生存的根本。
整潔的代碼也是一個讓你成為一個快樂的開發者的主要因素。它能夠讓你毫無壓力地生活。如果你的代碼是整潔的并且自我感覺良好,即使今天是項目的最後期限,你也可以處變不驚。
上面提到的幾點都是實際存在的,最關鍵的一點是:整潔的代碼能夠節省金錢!本質上來講,這和經濟效率有關,每年,軟體開發公司都會因為糟糕的代碼品質而損失大量的金錢。
1.3 為什麼使用C++
C可以讓你很容易地搬起石頭砸自己的腳,C++則困難得多,但當砸到的時候,你就會失去整條腿!
—Bjarne Stroustrup, Bjarne Stroustrup's FAQ:你是認真的嗎?
每種程式設計語言都是一種工具,并且,每種程式設計語言都有自己的優點和缺點。軟體架構師的一個重要工作就是選擇一種程式設計語言(或是多種程式設計語言),讓合适的語言在項目中做合适的事情。這是一項不能受個人喜好左右的重要決策。類似的,“在公司我們根據《replace this with the language of your choice》做任何事”不是一個好的指導原則。
作為一種多範型程式設計語言,C++把不同的思想和概念融合到了一起。在作業系統、裝置驅動程式、嵌入式系統、資料庫管理系統、計算機遊戲、3D動畫和計算機輔助設計、實時音頻和視訊處理、大資料管理系統和很多其他高性能的應用中,C++語言一直都是一個很好的選擇。在某些領域中,C++可以與其他語言混合使用。數十億行的大量的C++代碼目前仍在使用中。
幾年前,人們廣泛地認為C++很難學以緻用。對于那些經常肩負編寫大型複雜程式任務的程式員來說,這種語言可能是複雜而令人畏懼的。鑒于此觀點,解釋型程式設計語言和托管型程式設計語言,如Java或C#變得流行起來。由于這些語言廠家的過度營銷,解釋型語言和托管型程式設計語言在某些領域占據了主導地位,但是在其他領域,編譯型語言仍然占據主導地位。程式設計語言并不是宗教。如果你不需要C++的高性能特性,比如Java可以讓你更輕松的工作,那麼你就可以使用Java而不是C++。
1.4 C++11—新時代的開始
有人說C++目前正處于偉大的複興時期,有些人甚至說這是一場革命。現在的C++已經不是20世紀90年代早期的C++了,這種趨勢的催化劑主要是在2011年9月出現的C++标準ISO/IEC 1488∶2011 [ ISO11],稱為C++ 11标準。
毫無疑問,C++11帶來了一些偉大的創新。在本書的編寫過程中,C++标準委員會完成了C++17标準的起草工作,這一标準已送出ISO國際标準化組織處理。與此同時,C++20也已經完成了開始部分的編寫。
目前,在傳統行業發生了很多事情,尤其是在公司和制造業上,因為作為技術系統的軟體已成為最重要的增值因素。C++開發工具變得更加強大了,并且也出現了很多可用的第三方庫和架構。但我并不認為這是一場革命,我認為這是一種演進。同時,程式設計語言必須不斷改進以滿足新的需求。C++98和C++03是兩個标準,C++03主要是在C++98的基礎上修複了一些bug。
1.5 适合本書的讀者
作為一名教育訓練講師和顧問,我有機會去很多正在開發軟體的公司,同時,我也非常仔細地觀察了在開發過程中正在發生的一些事情,并且我也已經意識到了C++陣營與其他開發語言陣營的差距。
給我的印象是,C++程式員已經被那些促進軟體工藝和整潔代碼開發的人員忽視了。相對來說,在Java環境中,以及在Web或遊戲開發世界中,許多熟知的原則和實踐在C++開發領域似乎都不被人所知道。一些開創性的書籍,如Andrew Hunt和David Thomas的《Pragmatic Programmer》[ hunt99 ],或是Robert C. Martin的《Clean Code》[ martin09]也同樣如此。
而本書試圖縮小這種差距,因為即使是C++,代碼一樣可以寫得很整潔!如果你想讓自己寫的代碼更加整潔,那麼本書适合你閱讀。
本書不是C++的入門書!你應該已經熟悉了C++語言的基本概念,才能有效掌握本書的内容。如果你隻是想從C++開發開始,并且沒有C++語言的基礎知識,你應該首先通過其他書籍(如《C++ Primer》)學習,或選擇一個好的C++入門的練習項目。
此外,本書也不包含任何深奧的技巧和雜亂的知識點。我知道C++有很多令人興奮的技巧,但這些通常不是整潔代碼的精神,也不是現代C++的代碼風格。如果你真的沉迷于C++的裸指針,那麼本書不适合你閱讀。
本書中的一些代碼示例,用到了C++11标準(ISO/IEC 14882:2011)和C++14标準(ISO/IEC 14882:2014)的多個特性,也用到了一些C++17标準的特性。如果你不熟悉這些特性,也不用擔心。我将通過擴充閱讀的形式,提供一些簡要的介紹。需要注意的是,實際上目前并不是所有的C++編譯器都支援C++語言的所有新特性。
除此之外,本書為了幫助C++程式員提高技能水準,舉例說明了如何編寫易于了解的、靈活的、可維護的和高效的C++代碼。即使你是一個經驗豐富的C++開發人員,本書中也有一些值得你學習的地方,我認為這些值得學習的地方能夠促進你的工作。書中所提出的原則和實踐可以應用于新的軟體系統,有時被稱為“綠地項目”;以及具有悠久曆史的遺留系統,通常被稱為“棕地項目”。
1.6 本書使用的約定
本書使用的排版約定如下所示:
楷體字:新的術語、名字,或擴充内容。
黑體字:段落中強調的術語或重要的語句。
等寬字:段落中表示程式元素,如類、變量或函數名,語句或C++關鍵字。這種字型還表示指令行的輸入參數,一個按鍵序列或者程式的輸出結果。
1.6.1 擴充閱讀
有時候我想告訴你一些與上下文毫不相關的資訊,它們是相對獨立的内容。這些部分就叫作擴充閱讀,用于對其附近的主題做進一步讨論或對比論證。例子:
此處為擴充閱讀的标題
此處為擴充閱讀的内容。
1.6.2 說明、提示和警告
還有另一種形式的擴充閱讀,提供說明、提示和警告的相關資訊。它們的目的是為你提供一些特殊的資訊,一條建議或者警告你一些比較危險需要注意避免的事情。例子:
說明 此處為說明的内容
1.6.3 示例代碼
示例代碼和代碼片段與正文互相獨立,使用等寬字型且文法高亮(C++關鍵字為黑體)。較長的代碼片段通常含有标題。為了快速地指明代碼所在位置,代碼示例中通常會給出行号。
為了更好地關注代碼中特定的部分,不相關部分的代碼将被隐藏并用一個注釋替換,其形式為“//…”,參考下方的示例代碼:
1.6.4 編碼風格
簡單說一下本書使用的代碼風格。
你可能會發現我的編碼風格和典型的Java風格非常相似,混有Kernighan and Ritchie (K&R)風格。在我近20年的開發生涯中,我學習的程式設計語言遠不止C++,還包含ANSI-C、Java、Delphi、Scala以及其他腳本語言,今後亦是如此。是以,我在學習過程中不斷吸取各方之長,已經形成了一套自己的編碼風格。
也許你不喜歡我的編碼風格,并且更傾向于Linus Torvald 的核心編碼風格,Allman風格或其他流行的C++編碼風格。我想說的是,每個人都有自己的編碼風格,不必要求個人的編碼風格完全一樣。
1.7 相關網站和代碼庫
本書附有一個網站:www.clean-cpp.com。
該網站包含了以下内容:
□一個論壇,讀者可以與其他讀者讨論特定主題,當然也可以與作者讨論。
□一些本書可能尚未涉及的附加主題的讨論。
□本書中所有圖解的高分辨率版本。
本書中的大多數源代碼示例以及其他有用的附屬物都可以在GitHub上獲得,網址為:
https://github.com/clean-cpp。
你可以使用Git的以下指令導出代碼:
你還可以去
https://github.com/clean-cpp/book-samples網站上點選“Download ZIP”按鈕下載下傳一個“.zip”格式的檔案。
1.8 UML圖
本書中的一些插圖是UML圖。統一模組化語言(UML)是一種标準化的圖形語言,用于建立軟體和其他系統的模型。在目前的2.5版本中,UML提供了14種圖表類型來全面描述系統。
如果你不熟悉所有的圖表類型,請不要擔心,我在本書中隻使用其中的一部分。有時我提出UML圖是為了提供某些問題的概述,而這些問題可能無法通過閱讀代碼快速檢測到。在附錄A中,簡單介紹了UML使用的符号。