代码质量与规范,那些年你欠下的技术债
提到“質(zhì)量”二字時(shí),我們的第一反應(yīng)往往是“有多少BUG?”“性能好不好?“這樣的問題。我們對軟件產(chǎn)品或服務(wù)的質(zhì)量定義看其能不能滿足用戶的需求,包括功能、性能和體驗(yàn)等維度的指標(biāo),我們可以通過各種類型的檢測手段來給出其質(zhì)量高低的度量。但是,如果直接拿出一段源代碼放在我們面前,問這段代碼的質(zhì)量好壞時(shí),我們又該如何作答呢?
有人說:“好的代碼就像好的笑話一樣,它不需要解釋(Good code is like a good joke: It needs no explanation)”。有編碼經(jīng)驗(yàn)的人對代碼都有一定的“鑒賞力”,能憑感覺給出代碼好壞的主觀評價(jià),看到所謂的“意大利面條式代碼”都會感到不舒服,但是這樣憑感覺的方式太個(gè)性化、太隨意了,有沒有一種公認(rèn)的標(biāo)準(zhǔn)來鑒定代碼質(zhì)量呢?
Bob大叔在其著作《代碼整潔之道》的前言中引用了這樣一幅漫畫:
圖1代碼質(zhì)量的唯一有效度量指標(biāo)
使用漫畫中的“每分鐘爆粗?jǐn)?shù)量”來衡量代碼質(zhì)量是個(gè)很有趣的玩笑,強(qiáng)調(diào)了代碼的可讀易懂等這樣的“內(nèi)在”質(zhì)量屬性。相對于滿足需求規(guī)范這樣的“外在”質(zhì)量屬性,“內(nèi)在”的代碼質(zhì)量屬性強(qiáng)調(diào)的是支持實(shí)現(xiàn)功能需求的代碼內(nèi)部結(jié)構(gòu)的質(zhì)量。《Sonar code quality testing essential》一書中從七個(gè)維度定義了代碼的這種內(nèi)在質(zhì)量,Sonar開發(fā)團(tuán)隊(duì)上綱上線的戲稱為開發(fā)人員七宗罪:
- 編碼規(guī)范:是否遵守了編碼規(guī)范,遵循了最佳實(shí)踐。
- 潛在的BUG:可能在最壞情況下出現(xiàn)問題的代碼,以及存在安全漏洞的代碼。
- 文檔和注釋:過少(缺少必要信息)、過多(沒有信息量)、過時(shí)的文檔或注釋。
- 重復(fù)代碼:違反了Don’tRepeat Yourself原則。
- 復(fù)雜度:代碼結(jié)構(gòu)太復(fù)雜(如圈復(fù)雜度高),難以理解、測試和維護(hù)。
- 測試覆蓋率:編寫單元測試,特別是針對復(fù)雜代碼的測試覆蓋是否足夠。
- 設(shè)計(jì)與架構(gòu):是否高內(nèi)聚、低耦合,依賴最少。
Martin Fowler在其著作《重構(gòu):改善即有代碼的設(shè)計(jì)》中生動形象的使用“代碼壞味道(Bad Code Smells)”來比喻低質(zhì)量的代碼設(shè)計(jì)和實(shí)現(xiàn)所顯現(xiàn)的“癥狀”。書中羅列了22種代碼壞味道以及對應(yīng)的重構(gòu)手法。
參照這些資料,現(xiàn)在我們可以用可測性,可讀性,可理解性,容變性等代碼可維護(hù)性維度的質(zhì)量屬性來衡量代碼質(zhì)量。代碼質(zhì)量指的是代碼內(nèi)在的非功能性的質(zhì)量,用戶不能直接體驗(yàn)到這種質(zhì)量的好壞,代碼質(zhì)量不好,最直接的“受害者”是開發(fā)者或組織自身,因?yàn)榇a質(zhì)量好壞直接決定了軟件的可維護(hù)性成本的高低,例如重復(fù)代碼會造成維護(hù)成本的成倍增加;不規(guī)范的代碼、不良注釋和復(fù)雜度過高的代碼會增加閱讀和理解代碼的難度,復(fù)雜度過高也會極大增加測試覆蓋的難度,耗費(fèi)過多人力,而缺少測試覆蓋的代碼會使得定位問題和修復(fù)問題的難度加大;結(jié)構(gòu)不良、低內(nèi)聚高耦合的代碼則會使得哪怕是微小的需求變更或功能擴(kuò)展都無從下手,修改的代價(jià)很可能超過了重寫的代價(jià)。
至此,我們得到了一些定性的辦法來衡量代碼的質(zhì)量,我們可以借助一些代碼掃描工具來暴露代碼的質(zhì)量問題,也有了相應(yīng)的重構(gòu)方法和技巧來應(yīng)對這些問題。但是,我們還是難以回答某段代碼有多好或多差,兩段代碼相比哪個(gè)更好這樣的問題,因?yàn)槲覀內(nèi)匀粵]有完全解決代碼質(zhì)量的量化問題:同樣都是代碼質(zhì)量問題,重復(fù)代碼和過多注釋的危害肯定是不一樣的;同樣都是方法太復(fù)雜,圈復(fù)雜度為10的方法和圈復(fù)雜度為20的方法相比,危害和修改難度也差別很大。所以我們不能直接用問題的數(shù)量來衡量質(zhì)量,需要找到更精細(xì)合理的量化度量方法。
SQALE方法的質(zhì)量模型
如何評估軟件產(chǎn)品源代碼質(zhì)量一直是業(yè)界的一大挑戰(zhàn),SQALE(Software Quality Assessment based on Lifecycle Expectations)方法的出現(xiàn)提供一套科學(xué)的度量和分析方法,有效應(yīng)對了這一挑戰(zhàn)。SQALE方法整合了ISO-25010標(biāo)準(zhǔn)與代碼規(guī)范,其目標(biāo)是:以客觀、準(zhǔn)確、可復(fù)制和自動化的方式為評估軟件應(yīng)用程序的源代碼提供支持;為管理技術(shù)債務(wù)提供一種有效的方法。SQALE是目前眾多主流代碼分析工具的參照標(biāo)準(zhǔn),包括我們熟知的SonarQube,和CoderGears, SQUORE等商用代碼掃描分析工具。
下面我們簡單介紹一下SQALE方法的原理。SQALE方法包含兩種模型:質(zhì)量模型和分析模型。下圖的樹型結(jié)構(gòu)展示了SQALE方法的質(zhì)量模型:樹根節(jié)點(diǎn)代表軟件質(zhì)量(此處即代碼質(zhì)量),從左向右展開,第一級定義了代碼質(zhì)量的特征分類,往下是每種特征的子類,最后是每個(gè)子類對應(yīng)的屬性/具體的度量項(xiàng)。
圖2SQALE方法示意圖(質(zhì)量模型)
從左向右的方向是把代碼質(zhì)量不斷細(xì)化分解為更小的單元,直到最小粒度可以直接度量的屬性;從右向左的方向是把度量值逐步匯總到根節(jié)點(diǎn),最終得到一個(gè)總的代碼質(zhì)量的度量值。表1是SQALE質(zhì)量模型分解的示例。表中第一列把代碼質(zhì)量細(xì)分為可維護(hù)性、可測性、可變更性和可靠性幾個(gè)維度,對于每個(gè)維度又有進(jìn)一步的細(xì)節(jié),如可測性又細(xì)分為單元測試可測性和集成級可測性這樣的子特征,進(jìn)一步的,子特征還能細(xì)化到可直接度量的屬性,或者稱為要求(表中第三列,即我們通常說的代碼掃描規(guī)則),例如單元測試可測性再細(xì)分為“模塊測試路徑數(shù)量<11”和“模塊調(diào)用參數(shù)數(shù)量<6”這樣的規(guī)則:
表1 SQALE質(zhì)量模型示例(Java語言,節(jié)選)
注:我們使用的SonarQube并沒有完全照般SQALE的質(zhì)量模型,在5.4及之前的版本中還存在與SQALE類似的可測性、易變更性、可理解性和可讀性等維度,整個(gè)模型只有兩級,即第一列和第二列合并了,例如可測性維度下直接對應(yīng)了“表達(dá)式不應(yīng)該太復(fù)雜”,“方法不應(yīng)該太復(fù)雜”,“方法不應(yīng)該有太多參數(shù)”等規(guī)則。在5.4之后的版本,即目前使用的版本則進(jìn)一步簡化,代碼質(zhì)量對應(yīng)的掃描規(guī)則直接歸屬于“壞味道”大類,具體的規(guī)則可以打上多種標(biāo)簽來歸類,分類和配置更加靈活。
代碼質(zhì)量的度量
那么,這些規(guī)則應(yīng)該怎么量化呢?或者說,如何度量代碼違背規(guī)則的程度,而且這種度量是可以加總的,畢竟規(guī)則間差異很大,上文也解釋過,直接按數(shù)量匯總肯定是不合理的。
怎么辦呢?SQALE方法的分析模型解決了這個(gè)問題,由此我們也引出了本文中的第二個(gè)重要概念:技術(shù)債TechnicalDebts。
“技術(shù)債”這一概念最早出現(xiàn)在1992年,其本義是指,開發(fā)人員為了加速軟件開發(fā),在應(yīng)該采用最佳方案時(shí)進(jìn)行了妥協(xié),改用了短期內(nèi)能加速軟件開發(fā)的方案,從而在未來給自己帶來的額外開發(fā)負(fù)擔(dān)。這個(gè)定義暗示了這種“負(fù)債”是一種刻意的、理性的經(jīng)過權(quán)衡的行為,后文中我們進(jìn)一步探討技術(shù)債務(wù)的類型時(shí)會指出這一定義僅僅代表了技術(shù)債中相對良性的一類,是一個(gè)比較“溫和”的定義。此處我們關(guān)注的重點(diǎn)是使用技術(shù)債這一隱喻來幫助大家理解度量代碼質(zhì)量的方法。
既然談的是“債”,自然就應(yīng)該和錢有關(guān)了。因此,技術(shù)債的“本金”就定義為修復(fù)代碼質(zhì)量問題所需消耗人力資源估值,例如,針對java語言,修復(fù)一個(gè)圈復(fù)雜度為15的方法需要一個(gè)開發(fā)人員15分鐘的時(shí)間(以sonar java分析器缺省設(shè)置為例),這個(gè)值就是負(fù)債的本金。代碼掃描工具中對應(yīng)代碼質(zhì)量的每條掃描規(guī)則都對應(yīng)著一個(gè)債務(wù)計(jì)算方法,有的規(guī)則是設(shè)定了固定的債務(wù)值,有的則根據(jù)違規(guī)程度有相應(yīng)的計(jì)算公式。引入技術(shù)債的概念后,SQALE方法就可以把不同規(guī)則對應(yīng)的代碼質(zhì)量度量統(tǒng)一為人力資源的消耗這一單一指標(biāo)上。
根據(jù)圖2質(zhì)量模型所示由右向左的方向逐級匯總,就可以得到待評價(jià)軟件的代碼質(zhì)量度量值。我們的其中一個(gè)度量難題:如何客觀評價(jià)代碼的質(zhì)量,由此就得到了解答。
技術(shù)債的利息
關(guān)于技術(shù)債另外還有一個(gè)概念值得在這兒強(qiáng)調(diào)一下,即負(fù)債的利息。我們知道,通常借錢是有利息的,有的負(fù)債利息很低(如安居計(jì)劃利息為0),有的利息較高(如信用卡欠款),有的則高到令人絕望(如高利貸)。同樣,技術(shù)債也是有利息的,存在利滾利的情況,有的違規(guī)項(xiàng)馬上修復(fù)要10分鐘,如果放著不管一段時(shí)間后,也許就需要20分鐘甚至更多的時(shí)間來修復(fù)(由于代碼細(xì)節(jié)的知識隨時(shí)間流逝,以及破窗效應(yīng)造成代碼問題加速惡化等原因)。有的代碼掃描工具會針對規(guī)則定義本金和利息的計(jì)算方法,如Coder Gears的CppDepend,我們目前使用的SonarQube平臺上的代碼掃描插件不支持計(jì)算利息,因此本文就不過多討論,大家只需要記住,因?yàn)槔⒌拇嬖?#xff0c;技術(shù)債務(wù)不及時(shí)償還的話,會在未來呈現(xiàn)出非線性增長,造成始料不及的損失。后續(xù)文章在討論技術(shù)債的危害時(shí),我們還會時(shí)常提及技術(shù)債的非線性特征。
不同類型代碼的比較
現(xiàn)在我們還剩下一個(gè)度量問題:如何知道兩段代碼的質(zhì)量差異?現(xiàn)在有了技術(shù)債本金這個(gè)絕對值,但是不同規(guī)模,不同類型的代碼應(yīng)該如何比較呢?SQALE方法中繼續(xù)借鑒了“負(fù)債率”這個(gè)術(shù)語,計(jì)算公式為:償還債務(wù)所需耗費(fèi)的資源(即本金)除以重寫所有代碼的預(yù)估耗費(fèi)的資源。在掃描工具的實(shí)現(xiàn)中,分母是通過代碼量和開發(fā)生產(chǎn)力水平計(jì)算得出,其中的生產(chǎn)力是一個(gè)配置項(xiàng),如SonarQube上可以配置編寫一行代碼的平均估計(jì)耗時(shí)。SQALE進(jìn)一步使用了術(shù)語“債務(wù)等級”,定義了從A(非常好)到E(非常差)五個(gè)等級,根據(jù)負(fù)債率數(shù)值所在區(qū)間對應(yīng)不同的等級,例如SonarQube中缺省[0, 5%]是A,(5%, 10%]是B,(10%,20%]是C,(20%, 50%]是D,高于50%是E。當(dāng)負(fù)債率達(dá)到100%時(shí),即債務(wù)開始超過資產(chǎn),資不抵債,這時(shí)就稱這種情況為“技術(shù)破產(chǎn)”。當(dāng)然,日常工作中碰到這種情況時(shí),我們不會用這么嚇人的術(shù)語,通常是打著“重構(gòu)”的旗號重寫一遍。
下圖是CppDepend的一個(gè)掃描匯總結(jié)果的示例,包含了我們討論的所有概念(使用CppDepend為例是為了展示更全面的信息)。
圖3技術(shù)債度量示例(CppDepend)
上圖中工具掃描的代碼行數(shù)為19862行,共負(fù)債32天,債務(wù)的年息是9天2小時(shí),負(fù)債率是6.39%,債務(wù)等級是B級。
我們?nèi)粘9ぷ魇褂玫墓ぞ咂脚_是SonarQube,如下圖所示:
圖4技術(shù)債度量示例(SonarQube)
圖中的項(xiàng)目負(fù)債12天,共有923個(gè)壞味道(即違規(guī)項(xiàng)數(shù)量),負(fù)債率(圖中翻譯為“技術(shù)債務(wù)比率”)為6.3%,債務(wù)等級(圖中為SQALE評級)為B級。
SQALE給我們提供一套有效合理衡量代碼質(zhì)量的方法和工具,下圖中SQALE方法流程清晰的展示了整個(gè)方法流程各個(gè)環(huán)節(jié):
圖5 SQALE方法流程
有了方法和工具(SonarQube)的支持,我們可以看看我們自己的代碼質(zhì)量是個(gè)什么狀況。從掃描結(jié)果來看,與一些優(yōu)秀的開源項(xiàng)目相比,我們還是有一些差距。部門EP(Engineering Productivity)極社根據(jù)掃描結(jié)果,挑選出了比較重要的以下4條規(guī)則:
Source files should nothave any duplicated blocks,
Classes should not becoupled to too many other classes,
Methods should not be toocomplex,
Control flow statements"if", "for", "while", "switch" and"try" should not be nested too deeply.
注:SonarQube中有些語言對應(yīng)的掃描插件不支持第2條規(guī)則,如C++和Python。
這4條規(guī)是我們需要優(yōu)先償還的技術(shù)債,目前已經(jīng)在整個(gè)部門推廣實(shí)施。
讀到這里,很多人也許忍不住想問,如此這般折騰有啥用?代碼質(zhì)量相對不高也沒有影響到公司業(yè)務(wù)呀,提高這種代碼質(zhì)量除了讓我們忙上加忙外,能有什么好處?或者說有什么價(jià)值?跟我的KPI有啥關(guān)系?
好吧,既然代碼質(zhì)量不好就是“負(fù)債”,那么欠債還錢不就是天經(jīng)地義么,畢竟“出來混,遲早要還的。”顯然這樣的蒼白說教無法服眾,所以我們后續(xù)文章的重點(diǎn)就是深入理解技術(shù)債,深入分析提升代碼質(zhì)量的必要性和緊迫性。
So:讀者朋友們,你們所在的團(tuán)隊(duì)或組織是否也在重視代碼質(zhì)量呢?
給大家推薦一個(gè)程序員學(xué)習(xí)交流一群:878249276,群里有分享的視頻,面試指導(dǎo),架構(gòu)資料,還有思維導(dǎo)圖
群公告有視頻,都是干貨的,你可以下載來看。主要分享分布式架構(gòu)、高可擴(kuò)展、高性能、高并發(fā)、性能優(yōu)化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式項(xiàng)目實(shí)戰(zhàn)學(xué)習(xí)架構(gòu)師視頻。
總結(jié)
以上是生活随笔為你收集整理的代码质量与规范,那些年你欠下的技术债的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: poj2411 Mondriaan's
- 下一篇: netcat 使用