代碼質量管控 -- 複雜度檢測

背景

代碼的複雜度是評估一個項目的重要標準之一。較低的複雜度既能減小項目的維護成本,又能避免一些不可控問題的出現。然而在平常的開發中卻沒有一個明確的標準去衡量代碼結構的複雜程度,你們只能憑着經驗去評估代碼結構的複雜程度,好比,代碼的程度、結構分支的多寡等等。當前代碼的複雜度究竟是個什麼水平?何時就須要咱們去優化代碼結構、下降複雜度?這些問題咱們不得而知。
所以,咱們須要一個明確的標準去衡量代碼的複雜度。git

衡量標準

Litmus 是咱們團隊建設的一個代碼質量檢測系統,目前包括代碼的風格檢查、重複率檢查以及複雜度檢查。litmus 採用代碼的 Maintainability(可維護性)來衡量一個代碼的複雜度,而且經過如下三個方面來定義一段代碼的 Maintainability 的值:es6

  • Halstead Volume(代碼容量)
  • Cyclomatic Complexity(圈複雜度)
  • Lines of Code(代碼行數)

根據這三個參數計算出 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

Halstead Volume(代碼容量)

代碼的容量關注的是代碼的詞彙數,有如下幾個基本概念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:functionlet,=,+,return
// n2:tFunc,opt,result,1
// N1: functionlet,=,+,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:獨立組件的個數

幾種常見的語句控制流圖

一個例子優化

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

flow-chart
flow-chart

flow-graph
flow-graph
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
}複製代碼

檢測工具

  1. 本地檢測:es6-plato
    npm install --save es6-plato
    es6-plato -r -d report ./複製代碼
  2. litmus 質量檢測中心
    該系統由咱們團隊開發,目前僅限美團點評公司內部使用,系統部分截圖以下

首頁
首頁

首頁項目總覽
首頁項目總覽

詳情頁-總覽
詳情頁-總覽

詳情頁-代碼複雜度檢測詳情
詳情頁-代碼複雜度檢測詳情

詳情頁-代碼複雜度檢測詳情
詳情頁-代碼複雜度檢測詳情

詳情頁-代碼複雜度檢測詳情
詳情頁-代碼複雜度檢測詳情
相關文章
相關標籤/搜索