淺析代碼圈復雜度及認知復雜度


寫在開始

圈復雜度用來描述一段代碼“可測性”很好(可測性這里指需要構建完善的覆蓋全面的單元測試需要付出多少代價),但它的設計模型很難得出一個很好的“可讀性&可維護性”的測量結果

新版soanrqube引入了認知復雜度的概念,這個復雜度指標彌補了圈復雜度的一些不足,能更准確的反映一段代碼的理解成本,以及維護這段代碼的困難程度。

下面就簡要的描述下,為何認知復雜度更適合用來評價一段代碼的可讀性及可維護性。

什么是圈復雜度?

圈復雜度(Cyclomatic complexity)是一種代碼復雜度的衡量標准,在1976年由Thomas J. McCabe, Sr. 提出,目標是為了指導程序員寫出更具可測性和可維護性的代碼。

它可以用來衡量一個模塊判定結構的復雜程度,數量上表現為獨立路徑條數,也可以理解為覆蓋所有可能的情況最少需要的測試用例數量。 

代碼圈復雜度的計算方法

通常采用的計算方法為點邊計算法(當然還有節點判定法),計算公式為:

V(G) = e – n + 2 

e 代表在控制流圖中的邊的數量(對應代碼中順序結構的部分),n 代表在控制流圖中的節點數量,包括起點和終點(注:所有終點只計算一次,即便有多個return或者throw;節點對應代碼中的分支語句)

假定有如下這樣一段代碼:

圈復雜度計算方法

根據公式 V(G) = e – n + 2 = 12 – 8 + 2 = 6 ,上圖的圈復雜段為6。

注:說明一下為什么n = 8,雖然圖上的真正節點有12個,但是其中有5個節點為throw、return,這樣的節點為end節點,只能記做一個

為什么要引入認知復雜度?

圈復雜度最初的目的是用來識別“難以測試和維護的軟件模塊”,它能算出最少的全覆蓋的測試用例量,但是不能測出一個讓人滿意的“理解難度”。

這是因為同樣圈復雜度的代碼,不一定會具有相同的可維護性,我們看看下面的兩個例子:

上面這兩段代碼具有相同的圈復雜度,但顯然不具有相同的可讀性和可維護性性,這就是圈復雜度的不足之處。

因為圈復雜度理論是在1976年提出的,它不包含一些現代的語言結構,比如try-catch、lambda。

並且,每個方法都默認有一個最小圈復雜度1,這就讓我們無從得知,一個給定的類如果圈復雜度很高,它是一個大的易維護的類,還是一個很小很復雜的類。

為了解決上述這些問題,所以引入了“認知復雜度”,它將一段代碼被閱讀和理解時的復雜程度,估算成一個具體數字

認知復雜度如何評判?

認知復雜度評定基本原則

  • 對線性的代碼邏輯中,出現一個打斷邏輯的東西,復雜度+1;
  • 當打斷邏輯的是一個嵌套時,復雜度+1;
  • 忽略簡寫:把多句代碼縮寫為一句可讀的代碼,復雜度不會額外增加;

上面這種描述可能有點抽象,具體一點說,以下控制流結構會導致認知復雜度增加:

for, while, do while, 三元運算符, if/elif/else, catch語句, 跳轉語句(goto/break/continue), 以及嵌套的控制流(每一層嵌套復雜度遞增)

我們繼續拿上面提到的兩個例子舉例:

圈復雜度對於getWord方法本身會默認有1的復雜度,每多一個case復雜度+1,所以最終圈復雜度為4

而認知復雜度,對於整個 switch 結構只增加1的復雜度,因為從可理解、可維護程度來說,多幾個case並不會導致其增加(當然,大量的case也是我們應當盡力去避免的)

我們接着看另外一個例子:

如你所看到的,認知復雜度考慮到了使這個方法比前面提到的getWords()方法更難理解的因素——嵌套以及跳轉語句

因此,雖然這兩個方法的圈復雜度是一樣的,但是它們的認知復雜度數據很好的反映了它們兩者在可理解性/可維護性上的差異。

另外,相對於圈復雜度默認所有方法至少有1的復雜度,認知復雜度並沒有這樣一個評定規則,這對於entity等簡單類的復雜度評判會更加友好和客觀:

綜上所述,認知復雜度作為代碼的“可讀性/可維護性”評定指標會更加合適。


附、代碼復雜度與軟件質量關系

以上復雜度數值可以理解為方法粒度,即如果某一個方法復雜度>30,那這個方法的可讀性和可維護性就很低了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM