代码品质管控 -- 复杂度检测

背景

代码的复杂度是评估一个我的项目的重要规范之一。较低的复杂度既能缩小我的项目的保护老本,又能防止一些不可控问题的呈现。然而在日常的开发中却没有一个明确的规范去掂量代码构造的复杂程度,大家只能凭着教训去评估代码构造的复杂程度,比方,代码的水平、构造分支的多寡等等。以后代码的复杂度到底是个什么程度?什么时候就须要咱们去优化代码构造、升高复杂度?这些问题咱们不得而知。
因而,咱们须要一个明确的规范去掂量代码的复杂度。

衡量标准

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(代码容量)

代码的容量关注的是代码的词汇数,有以下几个基本概念

参数含意
n1Number of unique operators,不同的操作元(运算子)的数量
n2Number of unique operands,不同的操作数(算子)的数量
N1Number of total occurrence of operators,为所有操作元(运算子)共计呈现的次数
N2Number of total occurrence of operands,为所有操作数(算子)共计呈现的次数
Vocabularyn1 + n2,词汇数
lengthN1 + N2,长度
Volumelength * 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:独立组件的个数

几种常见的语句控制流图

一个例子
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(边):9n(断定节点):6p:1V = e - n + 2 * p = 5复制代码

如何优化

次要针对圈复杂度

大方向:缩小判断分支和循环的应用

(上面某些例子可能举的不太失当,仅用以阐明这么一种办法)

提炼函数

// 优化前,圈复杂度4function a (type) {    if (type === 'name') {        return `name:${type}`;    } else if (type === 'age') {        return `age:${type}`;    } else if (type === 'sex') {        return `sex:${type}`;    }}// 优化后,圈复杂度1function getName () {    return `name:${type}`;}function getAge () {    return `age:${type}`;}function getSex () {    return `sex:${type}`;}

表驱动

// 优化前,圈复杂度4function a (type) {    if (type === 'name') {        return 'Ann';    } else if (type === 'age') {        return 11;    } else if (type === 'sex') {        return 'female';    }}// 优化后,圈复杂度1function a (type) {    let obj = {        'name': 'Ann',        'age': 11,        'sex': 'female'    };    return obj[type];}

简化条件表达式

// 优化前,圈复杂度4function a (num) {    if (num === 0) {        return 0;    } else if (num === 1) {        return 1;    } else if (num === 2) {        return 2;    } else {        return 3;    }}// 优化后,圈复杂度2function a (num) {    if ([0,1,2].indexOf(num) > -1) {        return num;    } else {        return 3;    }}

简化函数

// 优化前,圈复杂度4function 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}// 优化后,圈复杂度2function a (type) {    let str = '';    for (let i = 0; i < 10; i++) {        str += type + i;    }    return str}

检测工具

本地检测:es6-plato

npm install --save es6-platoes6-plato -r -d report ./

参考文章:函数复杂度检测,美团