前端代码-圈复杂度

发布于 2019-12-23  232 次阅读


介绍

圈复杂度 (Cyclomatic complexity) 是一种代码复杂度的衡量标准,也称为条件复杂度或循环复杂度,它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。简称 CC 。其符号为 VG 或是 M 。

圈复杂度 在 1976 年由 Thomas J. McCabe, Sr. 提出。

简单的说圈复杂度可以表明你的代码逻辑复杂程度和代码质量,会导致代码难以维护,易出错。

衡量标准

圈复杂度 代码状况 建议
0 - 5 清晰 无需重构
5 - 10 略显复杂 建议重构
> 10 比较复杂 强烈建议重构

以下更合理:(看项目代码性质决定)
* V(G) ∈ [ 0 , 10 ]:代码质量不错;
* V(G) ∈ [ 11 , 15]:可能存在需要拆分的代码,应当尽可能想措施重构;
* V(G) ∈ [ 16 , ∞ ):必须进行重构;

计算方法

1. 点边计算法

M = E − N + 2P
  • E: 表示控制流程图中边的数量
  • N:表示控制流程图中节点的数量
  • P:独立组件的数目

流程控制图
控制流程图,是一个过程或程序的抽象表现,是用在编译器中的一个抽象数据结构,由编译器在内部维护,代表了一个程序执行过程中会遍历到的所有路径。它用图的形式表示一个过程内所有基本块执行的可能流向, 也能反映一个过程的实时执行过程。

2.节点判定法

有一个简单的计算方法,就是通过计算所有的判定节点的数量再加上1。判定节点有if else, switch case, for循环,|| 和 &&, 三元运算等等。

例如:

function foo (i) {
    let r = null
    if (i == 'a'’) {
        r = 1
    } else if (i == 'b') {
        r = 2
    } else {
        r = 3
    }
    switch (r) {
        case 1:
            return 'do something with a'
        case 2:
            return 'do something with b'
        case 3:
            return 'do something with c'
        default:
            return r ? 'do nothing' : 'do something you like'
    }

}

上面的代码有2个if, 一个switch case判断,包括了3个case,这里要注意下,整个switch case的绩点按照case数量来算,还有一个三元运算,所以圈复杂度是2+3+1+1=7.

如何优化代码

理论来说最简单的点就是在独立组建中(前端可以理解在一个function)少一点if else, switch case, for循环&& 和 ||以及三元运算符等等。

那么优化代码可以分为两大类:

1.fucntion优化

  • 提炼函数-单一指责

    单一职责原则:每个类都应该有一个单一的功能,一个类应该只有一个发生变化的原因。
    上面代码可以优化如下:

    function foo (i) {
        let r = null
        if (i == 'a'’) {
            r = 1
        } else if (i == 'b') {
            r = 2
        } else {
            r = 3
        }
        doThings(r)
    }
    
    function doThings (r) {
        switch (r) {
            case 1:
                return 'do something with a'
            case 2:
                return 'do something with b'
            case 3:
                return 'do something with c'
            default:
                return r ? 'do nothing' : 'do something you like'
        }
    }
    

2.判断节点优化

简单点就是利用一切方法消灭判断节点,可以利用对象,数组等其他任何合理方式。
* 抽象成对象配置

对于一些判断执行类的业务可以抽象成对象的形势,合理利用对象键值对避免使用冗余的判断节点。
对于上面的代码可以如下优化:
```
function foo (i) {

    let r = null

    if (i == 'a'’) {
        r = 1
    } else if (i == 'b') {
        r = 2
    } else {
        r = 3
    }

    const obj = {
        0: r ? 'do nothing' : 'do something you like',
        1: 'do something with a',
        2: 'do something with b',
        3: 'do something with c'
    }

    return obj[r]

}
```
看上面的例子将判断缩减3,圈复杂度就等于2+1+1=4,其实我们的代码还有很多带优化的地方,需要大家自己去研究,这里就不一一举例了。

如何查看代码的圈复杂的

1.eslint规则

eslint提供了检测代码圈复杂度的rules
我们开启eslintrulescomplexity规则,max表示允许最大圈复杂度,eslint就会报警代码的圈复杂度

rules: {
    complexity: [
        'warn', { max:0 }
    ]
}

但是这样只能一个一个文件的看,看不到项目代码整体的数据,刚好eslint有个CLIEngine(文档),它类似提供一个cli功能,下面我们介绍下如何使用:

const eslint = require('eslint')

const { CLIEngine } = eslint;

const cli = new CLIEngine({
    parserOptions: {
        ecmaVersion: 2018,
        sourceType: "module", // 允许script引入的js
        ecamFeatures: {
            jsx: true // 允许扫描jsx文件
        }
    },

    rules: {
        complexity: [
            'error',
            { max: 1 }
        ]
    },
    useEslintrc: true // 使用 eslintrc的配置
});

注: 对于vue文件我们需要配置vue-eslint-parser先解析vue文件
配置.eslintrc.js

module.exports = {
    extends: "eslint:recommended",
    parser: "vue-eslint-parser",
}

配置完成后,使用executeOnFiles对指定文件扫描,获得结果并过滤返回,

const reports = cli.executeOnFiles(['.']).results;

for (let i = 0; i < reports.length; i++) {
    const { messages } = reports[i];
    for (let j = 0; j < messages.length; j++) {
        const { message, ruleId } = messages[j];
        if (ruleId === 'complexity') {
             console.log(message);
        }
    }
}

这样我们就能拿到所有我想要的文件的圈复杂度数据了,结果展示,
results:

results: [
        {
            filePath: "./myfile.js", // 提取
            messages: [
                {
                    ruleId: 'complexity', // 只条件
                    severity: 2,
                    message:
                     'Function \'fmtNumber\' has a complexity of 2. Maximum allowed is 1.', // 提取
                    line: 8, // 提取
                    column: 1, // 提取
                    nodeType: 'FunctionDeclaration',
                    messageId: 'complex',
                    endLine: 15,
                    endColumn: 2 
                }
            ],
            errorCount: 1,
            warningCount: 0
        }
    ],

这样对有用的数据提取并汇总,就能统计项目的整体复杂度了

结果展示

markdown


avatar

### 小结

希望看完本文能最你有以下帮助:
* 理解圈复杂的计算和意义
* 提升项目代码质量,注重圈复杂度使用

下面是圈复杂度统计的统计工具

cli工具——concc

代码圈复杂度检测(check-cycle-complexity)源码:github

欢迎使用。

详细使用移步github 原理和实践


keep move