轉載自: https://juejin.im/post/59bb8b546fb9a00a4247532e
背景
代碼的復雜度是評估一個項目的重要標准之一。較低的復雜度既能減少項目的維護成本,又能避免一些不可控問題的出現。然而在日常的開發中卻沒有一個明確的標准去衡量代碼結構的復雜程度,大家只能憑着經驗去評估代碼結構的復雜程度,比如,代碼的程度、結構分支的多寡等等。當前代碼的復雜度到底是個什么水平?什么時候就需要我們去優化代碼結構、降低復雜度?這些問題我們不得而知。
因此,我們需要一個明確的標准去衡量代碼的復雜度。
衡量標准
Litmus
是我們團隊建設的一個代碼質量檢測系統,目前包括代碼的風格檢查、重復率檢查以及復雜度檢查。litmus 采用代碼的 Maintainability(可維護性)來衡量一個代碼的復雜度,並且通過以下三個方面來定義一段代碼的 Maintainability 的值:
- Halstead Volume(代碼容量)
- Cyclomatic Complexity(圈復雜度)
- Lines of Code(代碼行數)
根據這三個參數計算出 Maintainability,也就是代碼的可維護性,公式如下:
Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171)復制代碼
代碼行數不做贅述,下面我們具體介紹代碼容量、圈復雜的含義以及它們的計算原理
Halstead Volume(代碼容量)
代碼的容量關注的是代碼的詞匯數,有以下幾個基本概念
參數 | 含義 |
---|---|
n1 | Number of unique operators,不同的操作元(運算子)的數量 |
n2 | Number of unique operands,不同的操作數(算子)的數量 |
N1 | Number of total occurrence of operators,為所有操作元(運算子)合計出現的次數 |
N2 | Number of total occurrence of operands,為所有操作數(算子)合計出現的次數 |
Vocabulary | n1 + n2,詞匯數 |
length | N1 + N2,長度 |
Volume | length * Log2 Vocabulary,容量 |
一個例子
function tFunc(opt) { let result = opt + 1; return result; } // n1:function,let,=,+,return // n2:tFunc,opt,result,1 // N1: function,let,=,+,return // N2:tFunc,opt,result,opt,1,result // Vocabulary = n1 + n2 = 9 // length = N1 + N2 = 11 // Volume = length * Log2 Vocabulary = 34.869復制代碼
Cyclomatic Complexity(圈復雜度)
概念
圈復雜度(Cyclomatic complexity,簡寫CC)也稱為條件復雜度,是一種代碼復雜度的衡量標准。由托馬斯·J·麥凱布(Thomas J. McCabe, Sr.)於1976年提出,用來表示程序的復雜度,其符號為VG或是M。它可以用來衡量一個模塊判定結構的復雜程度,數量上表現為獨立現行路徑條數,也可理解為覆蓋所有的可能情況最少使用的測試用例數。圈復雜度大說明程序代碼的判斷邏輯復雜,可能質量低且難於測試和 維護。程序的可能錯誤和高的圈復雜度有着很大關系。
如何計算

如果在控制流圖中增加了一條從終點到起點的路徑,整個流圖形成了一個閉環。圈復雜度其實就是在這個閉環中線性獨立回路的個數。

如圖,線性獨立回路有:
- e1→ e2 → e
- e1 → e3 → e
所以復雜度為2
對於簡單的圖,我們還可以數一數,但是對於復雜的圖,這種方法就不是明智的選擇了。
計算公式
V(G) = e – n + 2 * p復制代碼
- e:控制流圖中邊的數量(對應代碼中順序結構的部分)
- n:代表在控制流圖中的判定節點數量,包括起點和終點(對應代碼中的分支語句)
- ps:所有終點只計算一次,即使有多個 return 或者 throw
- p:獨立組件的個數
幾種常見的語句控制流圖

一個例子
code
function test(index, string) { let returnString; if (index == 1) { if (string.length < 2) { return '分支1'; } returnString = "returnString1"; } else if (index == 2) { if (string.length < 5) { return '分支2'; } returnString = "returnString2"; } else { return '分支3' } return returnString; }復制代碼
flow-chart

flow-graph

計算
e(邊):9
n(判定節點):6
p:1
V = e - n + 2 * p = 5復制代碼
如何優化
主要針對圈復雜度
大方向:減少判斷分支和循環的使用
(下面某些例子可能舉的不太恰當,僅用以說明這么一種方法)
提煉函數
// 優化前,圈復雜度4
function a (type) { if (type === 'name') { return `name:${type}`; } else if (type === 'age') { return `age:${type}`; } else if (type === 'sex') { return `sex:${type}`; } } // 優化后,圈復雜度1 function getName () { return `name:${type}`; } function getAge () { return `age:${type}`; } function getSex () { return `sex:${type}`; }復制代碼
表驅動
// 優化前,圈復雜度4
function a (type) { if (type === 'name') { return 'Ann'; } else if (type === 'age') { return 11; } else if (type === 'sex') { return 'female'; } } // 優化后,圈復雜度1 function a (type) { let obj = { 'name': 'Ann', 'age': 11, 'sex': 'female' }; return obj[type]; }復制代碼
簡化條件表達式
// 優化前,圈復雜度4
function a (num) { if (num === 0) { return 0; } else if (num === 1) { return 1; } else if (num === 2) { return 2; } else { return 3; } } // 優化后,圈復雜度2 function a (num) { if ([0,1,2].indexOf(num) > -1) { return num; } else { return 3; } }復制代碼
簡化函數
// 優化前,圈復雜度4
function a () { let str = ''; for (let i = 0; i < 10; i++) { str += 'a' + i; } return str } function b () { let str = ''; for (let i = 0; i < 10; i++) { str += 'b' + i; } return str } function c () { let str = ''; for (let i = 0; i < 10; i++) { str += 'c' + i; } return str } // 優化后,圈復雜度2 function a (type) { let str = ''; for (let i = 0; i < 10; i++) { str += type + i; } return str }復制代碼
檢測工具
- 本地檢測:es6-plato
npm install --save es6-plato es6-plato -r -d report ./復制代碼
- litmus 質量檢測中心
該系統由我們團隊開發,目前僅限美團點評公司內部使用
,系統部分截圖如下






作者:美團點評點餐
鏈接:https://juejin.im/post/59bb8b546fb9a00a4247532e
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。