重構指標之如何監控程式碼圈複雜度

京東雲發表於2022-08-04

1 引言

軟體應用在發展到適當時機,”重構”,是開發過程中不可避免需要進行的一項工作。重構程式碼,以適配當前模組設計之初未考慮到的多樣化場景,並增加模組的可維護性、健壯性、可測試性。那麼,如何明確重構的方向,以及量化重構的結果呢?程式碼圈複雜度可以是一個供選擇的指標。下文介紹如何獲取應用的程式碼圈複雜度做到線上監控,給到覆盤程式複雜程度的資料支撐。

2 背景知識

2.1 圈複雜度

圈複雜度(Cyclomatic complexity,簡寫CC)也稱為條件複雜度,是一種程式碼複雜度的衡量標準。由托馬斯·J·麥凱布(Thomas J. McCabe, Sr.)於1976年提出,用來表示程式的複雜度,其符號為VG或是M。它可以用來衡量一個模組判定結構的複雜程度,數量上表現為獨立現行路徑條數,也可理解為覆蓋所有的可能情況最少使用的測試用例數。圈複雜度大說明程式程式碼的判斷邏輯複雜,可能質量低且難於測試和維護。程式的可能錯誤和高的圈複雜度有著很大關係。

2.2 圈複雜度計算方式

常用結構圈複雜度計算

  • 順序結構:順序結構複雜度為1。
  • if-else-else、switch-case:每增加一個分支,複雜度增加1,&& 、|| 運算也為一個分支。
  • 迴圈結構:增加一個迴圈結構,複雜度增加1。
  • return:增加一條return語句,複雜度將加1。

2.3 圈複雜度度量標準

如上列出行業內相對認可的度量資料,實際這個完全是看自己的業務體量和專案情況來決定的。假設你的業務很簡單,而且是個單體應用,功能都是很簡單的CRUD,那你的圈複雜度即使想上去也沒有那麼容易。此時你就可以選擇把圈複雜度的重構閾值設定為10.

假設你的業務十分複雜,而且涉及到多個其他的微服務系統呼叫,再加上各種業務中的corner case的判斷,圈複雜度上100可能都不在話下。

2.4 降低圈複雜度方法

1)函式提煉與拆分,單一職責

  • 拆分成子函式
  • 每個函式要有明確的功能實現,不要為了追求行數少而合併功能實現
  • 邏輯模組和資料模組要區分開編寫

2)最佳化演算法

  • 減少不必要條件、迴圈分支,儘量少用 if …else … ,採用三元表示式替換if else

3)表示式邏輯最佳化

  • 合併條件表示式,比如使用a || b || c

4)減少提前return

3 方案概述

3.1 指令碼設計

1)開發語言

  • python

2)依賴環境

  • lizard
  • APScheduler
  • smtplib
  • pymysql

3)指令碼架構

3.2 功能介紹

1)支援檢索語言範圍:

支援15種開發語言,包含常用語言如下

  • C/C++ (works with C++14)
  • Java
  • C# (C Sharp)
  • JavaScript (With ES6 and JSX)
  • Python
  • Golang

2)掃描引數配置說明:
利用lizard執行掃描,常用命令如下:

配置檢查範圍:

  • 列出要分析的程式語言。如果留空,將搜尋支援的所有語言。
-l LANGUAGES, --languages LANGUAGES
  • 排除與模式匹配的檔案。匹配一切?匹配任何單個字元,“/folder/”遞迴地排除資料夾中的所有內容。可以指定多個模式。不要忘了在模式周圍加“”號。
-x EXCLUDE, --exclude EXCLUDE
  • 設定白名單, 預設’./whitelizard.txt’
 -W WHITELIST, --whitelist WHITELIST

配置閥值警告:

  • 圈複雜度數警告的閾值,預設值為15,>15會產生警告。
-C CCN, --CCN CCN
  • 設定欄位的限制數。可以程式碼行數,圈複雜度,令牌數,引數數或自定義欄位。如果函式設定超過了限制數會報警。
-T THRESHOLDS, --Threshold THRESHOLDS

配置報告輸出:

  • 根據格式輸出到檔案
-o OUTPUT_FILE, --output_file OUTPUT_FILE

官網地址:http://www.lizard.ws
原始碼地址:https://github.com/terryyin/lizard

3)定時執行掃描任務:

  • 透過BackgroundScheduler建立排程任務,自動觸發掃描方法,結果寫庫
def dojob():    scheduler = BackgroundScheduler()    scheduler.add_job(func, "cron", hour=21, minute=30)    scheduler.start()

3.3 結果展示

3.3.1 報告名詞解釋
  • Cyclomatic complexity,圈複雜度也就是分支複雜度,最好保持在15 以下,目前指令碼設定閥值10。
  • LOC,包含註釋的程式碼行數,目前設定200閥值。
  • Token count ,token的個數,一個程式最多可以有 8192 個令牌, 每個令牌都是一個詞,例如關鍵字,識別符號,常量,標點符號,運算子。 對括號和字串計數作為 1 個令牌。 逗號、句點、LOCAL、分號、END 和註釋不計算在內。
  • Parameter count,引數統計就是函式的引數個數,目前指令碼設定閥值10。
3.3.2 執行結果展示
  • Windows環境執行指令碼,輸入file_root(檔案地址)執行掃描,支援自動彈出瀏覽器展示本次執行的Html報告

  • 每週定期執行,按照系統維度掃描,支援觸發郵件通知對應系統研發檢視超過閥值方法名稱

3.3.3 應用資料監控
  • 每週定期拉取指定分支最新程式碼,執行檔案分析,儲存掃描結果,透過資料圖表展示

4 總結

對於軟體程式碼好壞的衡量,圈複雜度可以作為一個參考指標,研發可以透過提煉拆分函式、最佳化演算法、最佳化邏輯表示式等方法降低模組(函式)圈複雜度。以上闡述圈複雜度一種線上監控方法,利用好線上化資料,結合現有團隊專案情況,才能形成更好的實踐機制。


相關文章