【騰訊TMQ】代碼質量與技術債

提到“質量”二字時,我們的第一反應往往是“有多少BUG?”“性能好不好?“這樣的問題。我們對軟件產品或服務的質量定義看其能不能滿足用戶的需求,包括功能、性能和體驗等維度的指標,我們可以通過各種類型的檢測手段來給出其質量高低的度量。但是,如果直接拿出一段源代碼放在我們面前,問這段代碼的質量好壞時,我們又該如何作答呢?

有人說:“好的代碼就像好的笑話一樣,它不需要解釋(Good code is like a good joke: It needs no explanation)”。有編碼經驗的人對代碼都有一定的“鑑賞力”,能憑感覺給出代碼好壞的主觀評價,看到所謂的“意大利麪條式代碼”都會感到不舒服,但是這樣憑感覺的方式太個性化、太隨意了,有沒有一種公認的標準來鑑定代碼質量呢?

Bob大叔在其著作《代碼整潔之道》的前言中引用了這樣一幅漫畫:

圖1代碼質量的唯一有效度量指標

使用漫畫中的“每分鐘爆粗數量”來衡量代碼質量是個很有趣的玩笑,強調了代碼的可讀易懂等這樣的“內在”質量屬性。相對於滿足需求規範這樣的“外在”質量屬性,“內在”的代碼質量屬性強調的是支持實現功能需求的代碼內部結構的質量。《Sonar code quality testing essential》一書中從七個維度定義了代碼的這種內在質量,Sonar開發團隊上綱上線的戲稱爲開發人員七宗罪:

  • 編碼規範:是否遵守了編碼規範,遵循了最佳實踐。

  • 潛在的BUG:可能在最壞情況下出現問題的代碼,以及存在安全漏洞的代碼。

  • 文檔和註釋:過少(缺少必要信息)、過多(沒有信息量)、過時的文檔或註釋。

  • 重複代碼:違反了Don’tRepeat Yourself原則。

  • 複雜度:代碼結構太複雜(如圈複雜度高),難以理解、測試和維護。

  • 測試覆蓋率:編寫單元測試,特別是針對複雜代碼的測試覆蓋是否足夠。

  • 設計與架構:是否高內聚、低耦合,依賴最少。

Martin Fowler在其著作《重構:改善即有代碼的設計》中生動形象的使用“代碼壞味道(Bad Code Smells)”來比喻低質量的代碼設計和實現所顯現的“症狀”。書中羅列了22種代碼壞味道以及對應的重構手法。

參照這些資料,現在我們可以用可測性,可讀性,可理解性,容變性等代碼可維護性維度的質量屬性來衡量代碼質量。代碼質量指的是代碼內在的非功能性的質量,用戶不能直接體驗到這種質量的好壞,代碼質量不好,最直接的“受害者”是開發者或組織自身,因爲代碼質量好壞直接決定了軟件的可維護性成本的高低,例如重複代碼會造成維護成本的成倍增加;不規範的代碼、不良註釋和複雜度過高的代碼會增加閱讀和理解代碼的難度,複雜度過高也會極大增加測試覆蓋的難度,耗費過多人力,而缺少測試覆蓋的代碼會使得定位問題和修復問題的難度加大;結構不良、低內聚高耦合的代碼則會使得哪怕是微小的需求變更或功能擴展都無從下手,修改的代價很可能超過了重寫的代價。

至此,我們得到了一些定性的辦法來衡量代碼的質量,我們可以藉助一些代碼掃描工具來暴露代碼的質量問題,也有了相應的重構方法和技巧來應對這些問題。但是,我們還是難以回答某段代碼有多好或多差,兩段代碼相比哪個更好這樣的問題,因爲我們仍然沒有完全解決代碼質量的量化問題:同樣都是代碼質量問題,重複代碼和過多註釋的危害肯定是不一樣的;同樣都是方法太複雜,圈複雜度爲10的方法和圈複雜度爲20的方法相比,危害和修改難度也差別很大。所以我們不能直接用問題的數量來衡量質量,需要找到更精細合理的量化度量方法。

如何評估軟件產品源代碼質量一直是業界的一大挑戰,SQALE(Software Quality Assessment based on Lifecycle Expectations,http://www.sqale.org)方法的出現提供一套科學的度量和分析方法,有效應對了這一挑戰。SQALE方法整合了ISO-25010標準與代碼規範,其目標是:以客觀、準確、可複製和自動化的方式爲評估軟件應用程序的源代碼提供支持;爲管理技術債務提供一種有效的方法。SQALE是目前衆多主流代碼分析工具的參照標準,包括我們熟知的SonarQube,和CoderGears, SQUORE等商用代碼掃描分析工具。

下面我們簡單介紹一下SQALE方法的原理。

SQALE方法包含兩種模型:質量模型和分析模型。

圖2中的樹型結構展示了SQALE方法的質量模型:樹根節點代表軟件質量(此處即代碼質量),從左向右展開,第一級定義了代碼質量的特徵分類,往下是每種特徵的子類,最後是每個子類對應的屬性/具體的度量項。

圖2SQALE方法示意圖(質量模型)

從左向右的方向是把代碼質量不斷細化分解爲更小的單元,直到最小粒度可以直接度量的屬性;從右向左的方向是把度量值逐步彙總到根節點,最終得到一個總的代碼質量的度量值。表1是SQALE質量模型分解的示例。表中第一列把代碼質量細分爲可維護性、可測性、可變更性和可靠性幾個維度,對於每個維度又有進一步的細節,如可測性又細分爲單元測試可測性和集成級可測性這樣的子特徵,進一步的,子特徵還能細化到可直接度量的屬性,或者稱爲要求(表中第三列,即我們通常說的代碼掃描規則),例如單元測試可測性再細分爲“模塊測試路徑數量<11”和“模塊調用參數數量<6”這樣的規則:

表1 SQALE質量模型示例(Java語言,節選):

注:我們使用的SonarQube並沒有完全照般SQALE的質量模型,在5.4及之前的版本中還存在與SQALE類似的可測性、易變更性、可理解性和可讀性等維度,整個模型只有兩級,即第一列和第二列合併了,例如可測性維度下直接對應了“表達式不應該太複雜”,“方法不應該太複雜”,“方法不應該有太多參數”等規則。在5.4之後的版本,即目前使用的版本則進一步簡化,代碼質量對應的掃描規則直接歸屬於“壞味道”大類,具體的規則可以打上多種標籤來歸類,分類和配置更加靈活。

那麼,這些規則應該怎麼量化呢?或者說,如何度量代碼違背規則的程度,而且這種度量是可以加總的,畢竟規則間差異很大,上文也解釋過,直接按數量彙總肯定是不合理的。

怎麼辦呢?SQALE方法的分析模型解決了這個問題,由此我們也引出了本文中的第二個重要概念:技術債TechnicalDebts。

“技術債”這一概念最早出現在1992年,其本義是指,開發人員爲了加速軟件開發,在應該採用最佳方案時進行了妥協,改用了短期內能加速軟件開發的方案,從而在未來給自己帶來的額外開發負擔。這個定義暗示了這種“負債”是一種刻意的、理性的經過權衡的行爲,後文中我們進一步探討技術債務的類型時會指出這一定義僅僅代表了技術債中相對良性的一類,是一個比較“溫和”的定義。此處我們關注的重點是使用技術債這一隱喻來幫助大家理解度量代碼質量的方法。

既然談的是“債”,自然就應該和錢有關了。因此,技術債的“本金”就定義爲修復代碼質量問題所需消耗人力資源估值,例如,針對java語言,修復一個圈複雜度爲15的方法需要一個開發人員15分鐘的時間(以sonar java分析器缺省設置爲例),這個值就是負債的本金。代碼掃描工具中對應代碼質量的每條掃描規則都對應着一個債務計算方法,有的規則是設定了固定的債務值,有的則根據違規程度有相應的計算公式。引入技術債的概念後,SQALE方法就可以把不同規則對應的代碼質量度量統一爲人力資源的消耗這一單一指標上。

根據圖2質量模型所示由右向左的方向逐級彙總,就可以得到待評價軟件的代碼質量度量值。我們的其中一個度量難題:如何客觀評價代碼的質量,由此就得到了解答。

關於技術債另外還有一個概念值得在這兒強調一下,即負債的利息。

我們知道,通常借錢是有利息的,有的負債利息很低(如安居計劃利息爲0),有的利息較高(如信用卡欠款),有的則高到令人絕望(如高利貸)。同樣,技術債也是有利息的,存在利滾利的情況,有的違規項馬上修復要10分鐘,如果放着不管一段時間後,也許就需要20分鐘甚至更多的時間來修復(由於代碼細節的知識隨時間流逝,以及破窗效應造成代碼問題加速惡化等原因)。有的代碼掃描工具會針對規則定義本金和利息的計算方法,如Coder Gears的CppDepend,我們目前使用的SonarQube平臺上的代碼掃描插件不支持計算利息,因此本文就不過多討論,大家只需要記住,因爲利息的存在,技術債務不及時償還的話,會在未來呈現出非線性增長,造成始料不及的損失。後續文章在討論技術債的危害時,我們還會時常提及技術債的非線性特徵。

現在我們還剩下一個度量問題:如何知道兩段代碼的質量差異?現在有了技術債本金這個絕對值,但是不同規模,不同類型的代碼應該如何比較呢?SQALE方法中繼續借鑑了“負債率”這個術語,計算公式爲:償還債務所需耗費的資源(即本金)除以重寫所有代碼的預估耗費的資源。在掃描工具的實現中,分母是通過代碼量和開發生產力水平計算得出,其中的生產力是一個配置項,如SonarQube上可以配置編寫一行代碼的平均估計耗時。SQALE進一步使用了術語“債務等級”,定義了從A(非常好)到E(非常差)五個等級,根據負債率數值所在區間對應不同的等級,例如SonarQube中缺省[0, 5%]是A,(5%, 10%]是B,(10%,20%]是C,(20%, 50%]是D,高於50%是E。當負債率達到100%時,即債務開始超過資產,資不抵債,這時就稱這種情況爲“技術破產”。當然,日常工作中碰到這種情況時,我們不會用這麼嚇人的術語,通常是打着“重構”的旗號重寫一遍。

下圖是CppDepend的一個掃描彙總結果的示例,包含了我們討論的所有概念(使用CppDepend爲例是爲了展示更全面的信息)。

圖3技術債度量示例(CppDepend)

圖3中工具掃描的代碼行數爲19862行,共負債32天,債務的年息是9天2小時,負債率是6.39%,債務等級是B級。

我們日常工作使用的工具平臺是SonarQube,如下圖所示:

圖4技術債度量示例(SonarQube)

圖中的項目負債12天,共有923個壞味道(即違規項數量),負債率(圖中翻譯爲“技術債務比率”)爲6.3%,債務等級(圖中爲SQALE評級)爲B級。

SQALE給我們提供一套有效合理衡量代碼質量的方法和工具,下圖中SQALE方法流程清晰的展示了整個方法流程各個環節:

圖5 SQALE方法流程

圖片來源:
http://www.sqale.org

有了方法和工具(SonarQube)的支持,我們可以看看我們自己的代碼質量是個什麼狀況。從掃描結果來看,與一些優秀的開源項目相比,我們還是有一些差距。部門EP(Engineering Productivity)極社根據掃描結果,挑選出了比較重要的以下4條規則:

(1)Source files should nothave any duplicated blocks,

(2)Classes should not becoupled to too many other classes,

(3)Methods should not be toocomplex,

(4)Control flow statements”if”, “for”, “while”, “switch” and”try” should not be nested too deeply.

注:SonarQube中有些語言對應的掃描插件不支持第2條規則,如C++和Python。

這4條規是我們需要優先償還的技術債,目前已經在整個部門推廣實施。

讀到這裏,很多人也許忍不住想問,如此這般折騰有啥用?

代碼質量相對不高也沒有影響到公司業務呀,提高這種代碼質量除了讓我們忙上加忙外,能有什麼好處?或者說有什麼價值?跟我的KPI有啥關係?

好吧,既然代碼質量不好就是“負債”,那麼欠債還錢不就是天經地義麼,畢竟“出來混,遲早要還的。”顯然這樣的蒼白說教無法服衆,所以我們後續文章的重點就是深入理解技術債,深入分析提升代碼質量的必要性和緊迫性。

So:讀者朋友們,你們所在的團隊或組織是否也在重視代碼質量呢?

關注騰訊移動品質中心TMQ,獲取更多測試乾貨!

版權所屬,禁止轉載!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章