程式碼質量與規範,那些年你欠下的技術債

騰訊雲加社群發表於2018-07-03

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

本文來自雲+社群專欄,作者騰訊移動品質中心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方法的質量模型

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

下面我們簡單介紹一下SQALE方法的原理。SQALE方法包含兩種模型:質量模型和分析模型。下圖的樹型結構展示了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)

上圖中工具掃描的程式碼行數為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:讀者朋友們,你們所在的團隊或組織是否也在重視程式碼質量呢?


問答

是否有工具可用於計算專案的程式碼度量?

相關閱讀

美團外賣Android Lint程式碼檢查實踐

幾點建議幫你寫出簡潔的JS程式碼

Web前端效能基礎指標&計算方式


此文已由作者授權騰訊雲+社群釋出,原文連結:https://cloud.tencent.com/developer/article/1151495?fromSource=waitui

歡迎大家前往騰訊雲+社群或關注雲加社群微信公眾號(QcloudCommunity),第一時間獲取更多海量技術實踐乾貨哦~

相關文章