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