转载自: 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-chart
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 质量检测中心
    该系统由我们团队开发,目前仅限美团点评公司内部使用,系统部分截图如下

首页

首页项目总览

详情页-总览

详情页-代码复杂度检测详情

详情页-代码复杂度检测详情

详情页-代码复杂度检测详情

作者:美团点评点餐
链接:https://juejin.im/post/59bb8b546fb9a00a4247532e
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

[代码质量] 代码质量管控 -- 复杂度检测 (JavaScript)的更多相关文章

  1. Java代码规范与质量检测插件SonarLint

    1.  SonarLint SonarLint是一个代码质量检测插件,可以帮助我们检测出代码中的坏味道 下载与安装 在需要检测的单个文件或者单个项目上右键 --> Analyze --> ...

  2. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  3. 编写高质量代码:改善Java程序的151个建议(第二章:基本类型)

    编写高质量代码:改善Java程序的151个建议(第二章:基本类型) 目录 建议21:用偶判断,不用奇判断 建议22:用整数类型处理货币 建议23:不要让类型默默转换 建议24:边界还是边界 建议25: ...

  4. 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)

    编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...

  5. (第一章)改善JavaScript,编写高质量代码。

    根据<编写高质量代码改善JavaScript程序的188个建议>这本书,来记录我目前所了解的建议方式. 建议1:警惕Unicode乱码 根据ECMA标准规定JavaScript语言可以使用 ...

  6. 编写高质量代码改善C#程序的157个建议——建议114:MD5不再安全

    建议114:MD5不再安全 MD5不再安全不是就算法本身而言的.如果从可逆性的角度出发,MD5值不存在被破解的可能性. MD5被广泛应用于密码验证和消息完整性验证.假设新注册一个用户,当注册用户的密码 ...

  7. 编写高质量代码改善C#程序的157个建议——建议87:区分WPF和WinForm的线程模型

    建议87:区分WPF和WinForm的线程模型 WPF和WinForm窗体应用程序都有一个要求,那就是UI元素(如Button.TextBox等)必须由创建它的那个线程进行更新.WinForm在这方面 ...

  8. 编写高质量代码改善C#程序的157个建议——建议79:使用ThreadPool或BackgroundWorker代替Thread

    建议79:使用ThreadPool或BackgroundWorker代替Thread 使用线程能极大地提升用户体验度,但是作为开发者应该注意到,线程的开销是很大的. 线程的空间开销来自: 1)线程内核 ...

  9. 编写高质量代码改善C#程序的157个建议——建议77: 正确停止线程

    建议77: 正确停止线程 开发者总尝试对自己的代码有更多的控制.例如,“让那个还在工作的线程马上停止下来”.然而,并非我们想怎样就可以怎样的,这至少涉及两个问题. 第一个问题 正如线程不能立即启动一样 ...

随机推荐

  1. 钻石diamaund外语

    Whenever I fail as a father or husband... a toy and a diamond always works. I never worry about diet ...

  2. jsonpath 一个简单实用的工具

    import jsonpath import json data = "{\"a\": \"11\", \"c\": {\&quo ...

  3. Java文件流下载并提示文件不存在

    做文件下载功能的时候,一般使用流的形式下载文件, 如果源文件不存在,下载页面可能就会没有提示,或者一片空白 用户操作之后可能一头雾水,那如何友好提示呢? 想到的有两种 1.可以尝试下载一个名称为:文件 ...

  4. Node: 包管理机制

    Node.js 的模块机制可以很好地解决业务代码混乱的难题,但对于第三方模块包,就有些力不从心了,因为第三方模块包分散存放在各地,无法集中式管理.这就需要一个包管理机制,在 Node.js 中,Isa ...

  5. emqx配置ssl

    1.生产自签证书 mkdir /etc/emqttd/certs/ && cd /etc/emqttd/certs/ openssl genrsa -out ca-key.pem 20 ...

  6. java继承 、方法重写、重写toString方法

    1.Java的继承,关键词Extends package cn.mwf.oo; public class TextExtends { public static void main(String[] ...

  7. 【笔记】MAML-模型无关元学习算法

    目录 论文信息: Finn C, Abbeel P, Levine S. Model-agnostic meta-learning for fast adaptation of deep networ ...

  8. MySQL/MariaDB数据库的Galera高可用性集群实战

      MySQL/MariaDB数据库的Galera高可用性集群实战 作者:尹正杰  版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Galera Cluster概述 1>.什么是Gale ...

  9. jquery 属性筛选选择器

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content ...

  10. 使用树莓派GPIO控制继电器

      一.使用方法总结: VCC接+5v,GND接负,IN1接GPIO口, 二.然后使用Linux命令或者编程控制GPIO口高低电位即可,如:执行下列命令: gpio readall 列出所有针角 gp ...