代碼的複雜度是評估一個項目的重要標準之一。較低的複雜度既能減小項目的維護成本,又能避免一些不可控問題的出現。然而在平常的開發中卻沒有一個明確的標準去衡量代碼結構的複雜程度,你們只能憑着經驗去評估代碼結構的複雜程度,好比,代碼的程度、結構分支的多寡等等。當前代碼的複雜度究竟是個什麼水平?何時就須要咱們去優化代碼結構、下降複雜度?這些問題咱們不得而知。
所以,咱們須要一個明確的標準去衡量代碼的複雜度。git
Litmus
是咱們團隊建設的一個代碼質量檢測系統,目前包括代碼的風格檢查、重複率檢查以及複雜度檢查。litmus 採用代碼的 Maintainability(可維護性)來衡量一個代碼的複雜度,而且經過如下三個方面來定義一段代碼的 Maintainability 的值:es6
根據這三個參數計算出 Maintainability,也就是代碼的可維護性,公式以下:github
Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171)複製代碼
代碼行數不作贅述,下面咱們具體介紹代碼容量、圈複雜的含義以及它們的計算原理npm
代碼的容量關注的是代碼的詞彙數,有如下幾個基本概念bash
參數 | 含義 |
---|---|
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,簡寫CC)也稱爲條件複雜度,是一種代碼複雜度的衡量標準。由托馬斯·J·麥凱布(Thomas J. McCabe, Sr.)於1976年提出,用來表示程序的複雜度,其符號爲VG或是M。它能夠用來衡量一個模塊斷定結構的複雜程度,數量上表現爲獨立現行路徑條數,也可理解爲覆蓋全部的可能狀況最少使用的測試用例數。圈複雜度大說明程序代碼的判斷邏輯複雜,可能質量低且難於測試和 維護。程序的可能錯誤和高的圈複雜度有着很大關係。工具
因此複雜度爲2
對於簡單的圖,咱們還能夠數一數,可是對於複雜的圖,這種方法就不是明智的選擇了。測試
V(G) = e – n + 2 * p複製代碼
一個例子優化
codeui
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
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
}複製代碼
npm install --save es6-plato
es6-plato -r -d report ./複製代碼
該系統由咱們團隊開發,目前僅限美團點評公司內部使用
,系統部分截圖以下