什么樣的代碼是高質量的代碼?
?在作者的工作經歷中,每當同事評論項目代碼質量的時候,作者聽到最多的評論是“代碼 寫得很爛”或“代碼寫得很好”。作者認為,用“好”“爛”這樣的字眼來描述代碼質量是非常 籠統的。當作者詢問代碼到底“爛”在何處或“好”在哪里時,盡管大部分同事都能簡單地羅列幾個“爛”的方面或好的方面,但他們的回答往往都不夠全面,知識點零碎, 也無法切中要害。
當然,也有一些軟件工程師對如何評價代碼質量有所認識,如認為好代碼是易擴展、易讀、 簡單、易維護的,等等,但他們對于這些評價的理解往往只停留在表面上,對于諸多更加深入 的問題, 如“怎么才算可讀性好?什么樣的代碼才算易擴展、易維護?可讀、可擴展與可維護 之間有什么關系?可維護中的‘維護’兩字該如何理解?”,等等,他們并沒有太清晰的認識。
實際上,對于代碼質量的描述,除“好”“爛”這樣比較簡單、籠統的描述方式以外,還 有很多語義豐富、專業和細化的描述方式,如下所示:
靈活性(?exibility)、可擴展性(extensibility)、可維護性(maintainability)、可讀性(readability)、 可理解性(understandability)、易修改性(changeability)、可復用性(reusability)、可測試性(testability)、模塊化(modularity)、高內聚低耦合(high cohesion loose coupling)、高效(high e?ciency)、高性能(high performance)、安全性(security)、兼容性(compatibility)、易用 性(usability)、簡潔(clean)、清晰(clarity)、簡單(simple)、直接(straightforward)、少 即是多(less code is more)、文檔詳盡(well-documented)、分層清晰(well-layered)、正確 性(correctness 、bug free)、健壯性(robustness)、魯棒性(robustness)、可用性(reliability)、 可伸縮性(scalability)、穩定性(stability)和優雅(elegant)等。
面到如此多的詞匯,我們到底應該使用哪些詞匯來描述一段代碼的質量呢?
實際上,我們很難通過其中的某個或某幾個詞匯來全面地評價代碼質量,因為這些詞匯是 從不同角度描述代碼質量的。例如,在評價一個人的時候,我們往往通過多個方面進行綜合評 價,如性格、能力等,否則,對一個人的評價可能是片面的。同樣,對于代碼質量,我們也需 要綜合多種因素進行評價,不應該從單一的角度去評價。例如,一段代碼的可擴展性很好,但 可讀性很差,那么,我們不能片面地認為這段代碼的質量高。
注意,不同的評價角度并不是完全獨立的,有些之間存在包含關系、重疊關系等,或者可 以互相影響。例如,代碼的可讀性和可擴展性好,可能意味著代碼的可維護性好。而且,各種 評價角度不是“非黑即白”。例如,我們不能簡單地將代碼評價為可讀或不可讀。如果用數字 來量化代碼的可讀性,那么應該是一個連續的區間值,而非 0 、 1 這樣的離散值。
不過,我們真的可以客觀地量化一段代碼的質量嗎?答案是否定的。對一段代碼質量的評 價,常常帶有很強的主觀性。例如,對于什么樣的代碼才算是可讀性好,每個人的評判標準都 不一樣。
正是因為代碼質量評價的主觀性,使得這種主觀評價的準確度與軟件工程師自身的經驗有 極大的關系。軟件工程師的經驗越豐富,給出的評價往往越準確。形成對比的是,資歷較淺的 軟件工程師常常覺得沒有一個可量化的評價標準作為參考,很難準確判斷一段代碼的質量。如 果無法辨別代碼寫得好或壞,那么,即使寫再多的代碼,編碼能力也可能沒有太大提高。
在仔細閱讀前面羅列的代碼質量評價標準之后,讀者會發現,有些詞匯過于籠統、抽象, 而且偏向于對整體的描述,如優雅、好、壞、整潔和清晰等;有些過于注重細節、偏重方法 論,如模塊化、高內聚低耦合、文檔詳盡和分層清晰等;有些可能并不僅僅局限于編碼,與架 構設計等也有關系,如可伸縮性、可復用性和穩定性等。
為了讀者有重點地進行學習,作者挑選了7 個常用且重要的評價標準來詳細講解,包括可 維護性、可讀性、可擴展性、靈活性、簡潔性、可復用性和可測試性。
1.可維護性(maintainability)
對于代碼開發,“維護”無外乎修改 bug、修改舊的代碼和添加新的代碼等。“代碼易維護” 是指, 在不破壞原有代碼設計、不引入新的 bug 的情況下, 能夠快速修改或添加代碼。“代碼 不易維護”是指,修改或添加代碼需要冒極大的引入新 bug 的風險,并且需要很長的時間才能 完成。
對于一個項目,維護代碼的時間可能遠遠大于編寫代碼的時間。軟件工程師可能將大部分 時間花在修復 bug、修改舊的功能邏輯和添加新的功能邏輯之類的工作上。因此,代碼的可維 護性就顯得格外重要。
對于維護、易維護和不易維護這 3 個概念,我們不難理解。不過,對于實際的軟件開發,更重要的是需要清楚如何判斷代碼可維護性的高低。
實際上,可維護性是一個難以量化、偏向對代碼整體進行評價的標準,它類似之前提到的 “好”“壞”“優雅”之類的籠統評價。代碼的可維護性高低是由很多因素共同作用的結果。代 碼簡潔、可讀性好、可擴展性好,往往就會使得代碼易維護。更深入地講,如果代碼分層清 晰、模塊化程度高、高內聚低耦合、遵守基于接口而非實現編程的設計原則等,就可能意味著 代碼易維護。除此之外,代碼的易維護性還與項目的代碼量、業務的復雜程度、技術的復雜程 度、文檔的全面性和團隊成員的開發水平等諸多因素有關。
2.可讀性(readability)
軟件設計專家 Martin Fowler 曾經說過:“Any fool can write code that a computer can understand. Good programmers write code that humans can understand. ”(任何人都可以編寫計算機能理解的 代碼, 而好的程序員能夠編寫人能理解的代碼。)在 Google 內部, 有一個稱為“Readability” 的認證。只有拿到這個認證的軟件工程師,才有資格在 Code Review 的時候批準別人提交的代 碼。可見,代碼的可讀性有多么重要,畢竟,代碼被閱讀的次數有時候遠遠超過被編寫和執行 的次數。
代碼的可讀性如此重要,在編寫代碼的時候,我們要時刻考慮代碼是否易讀、易理解。代 碼的可讀性在很大程度上會影響代碼的可維護性,因為無論是修復 bug 還是添加 / 修改功能代 碼,我們首先要讀懂代碼。如果我們對代碼一知半解,就有可能因為考慮不周而引入新 bug 。
既然代碼的可讀性如此重要,那么我們如何評判一段代碼的可讀性呢?
我們需要查看代碼是否符合代碼規范,如命名是否達意、注釋是否詳盡、函數長度是否合 適、模塊劃分是否清晰, 以及代碼是否“高內聚、低耦合”等。除此之外,Code Review 也是 一個很好的測試代碼可讀性的手段。如果我們的同事可以輕松地讀懂我們寫的代碼,往往能夠 說明我們的代碼的可讀性不差;如果同事在讀我們寫的代碼時,有很多疑問,那么可能在提示 我們,代碼的可讀性存在問題,需要重點關注。
3.可擴展性(extensibility)
代碼的可擴展性是指在不修改或少量修改原有代碼的情況下,能夠通過擴展方式添加新功 能代碼。換句話說,代碼的可擴展性是指在編寫代碼時預留了一些功能擴展點,我們可以把新 功能代碼直接插入擴展點,而不會因為添加新的功能代碼而改動大量的原始代碼。可擴展性也 是評價代碼質量的重要標準。代碼的可擴展性表示代碼應對未來需求變化的能力。與代碼的可 讀性一樣,代碼是否易擴展也在很大程度上決定了代碼是否易維護。
4.靈活性(flexibility)
靈活性也可以用來描述代碼質量。例如, 我們經常會聽到這樣的描述:“代碼寫得很靈 活”。那么,我們如何理解這里提到的“靈活”呢?
盡管很多人用“靈活”描述代碼質量,但實際上,“靈活”是一個抽象的評價標準,給“靈活”下定義是很難的。不過,我們可以想一下,我們在什么情況下才會說代碼寫得很靈活呢?
作者羅列了 3 種場景,幫助讀者理解什么是代碼的靈活性。
1)當我們添加新功能代碼時,由于原有代碼中已經預留了擴展點,因此,我們不需要修 改原有代碼,只需要在擴展點上添加新代碼。這個時候,我們除可以說代碼易擴展以外,還可 以說代碼寫得很靈活。
2)當我們要實現一個功能時,如果原有代碼中已經抽象出了很多位于底層且可復用的模 塊、類等,那么我們可以直接使用。這個時候,我們除可以說代碼易復用以外,還可以說代碼 寫得很靈活。
3)當我們使用某個類時,如果這個類可以應對多種使用場景,滿足多種不同需求,那么, 我們除可以說這個類易用以外,還可以說這個類設計得很靈活或代碼寫得很靈活。
從上述場景來看,如果一段代碼易擴展、易復用,或者易用,我們一般可以認為這段代碼 寫得很靈活。因此,“靈活”的含義寬泛,很多場景都可以使用。
5.簡潔性(simplicity)
有一條非常著名的設計原則,大部分讀者應該都聽過,那就是 KISS 原則:“Keep It Simple ,Stupid”。該原則的意思是“盡量保持代碼簡單”。代碼簡單、邏輯清晰往往意味著代 碼易讀、易維護。在編寫代碼的時候,我們往往會把“簡單、清晰”原則放到首位。
不過,很多編程經驗不足的程序員會覺得,簡單的代碼沒有技術含量,喜歡在項目中引入 一些復雜的設計模式,覺得這樣才能體現自己的技術水平。實際上,思從深而行從簡,真正的 編程高手往往能用簡單的方法解決復雜的問題。
除此之外,雖然我們都能認識到,代碼要盡量寫得簡潔,要符合 KISS 原則,但怎樣的代 碼才算足夠簡潔?怎樣的代碼才算符合 KISS 原則呢?實際上,不是每個人都能準確地做出判 斷,因此,在第 3 章介紹 KISS 原則的時候,我們會通過具體的代碼示例詳細說明。
6.可復用性(reusability)
我們可以將代碼的可復用性簡單地理解為“盡量減少重復代碼的編寫,復用已有代碼”。 在后續章節中,我們會經常提到“可復用性”這一代碼評價標準。例如,當介紹面向對象特性 的時候,我們會提到繼承、多態存在的目的之一就是提高代碼的可復用性;當介紹設計原則的 時候,我們會提到單一職責原則與代碼的可復用性相關;當介紹重構技巧的時候,我們會提到 解耦、高內聚和模塊化等能夠提高代碼的可復用性。可見,可復用性是一個重要的代碼評價標 準,也是很多設計原則、設計思想和設計模式等所要實現的最終效果。
實際上,代碼的可復用性與 DRY(Don’t Repeat Yourself)原則的關系緊密,因此,在第 3 章介紹 DRY 原則的時候,我們還會介紹代碼復用相關的更多知識,如提高代碼的可復用性的 編程方法等。
7.可測試性(testability)
相比上述 6 個代碼質量評價標準,代碼的可測試性較少被提及,但它同樣重要。代碼的可 測試性的高低可以從側面準確地反映代碼質量的高低。代碼的可測試性低,難以編寫單元測試,那么,基本能夠說明代碼的設計有問題。?