转载自: 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. linux下测试某网址或IP端口能否访问

    参考这篇 Linux系统下怎么测试端口的连通性 测试端口连通 nc -v -w 1 183.xx.oo.ee -z 82 1linux主机centos操作系统,项目里面需要访问一个URL路径的接口ht ...

  2. day 06 预科

    目录 if判断 if判断习题 for循环 for循环习题 微信机器人 if判断 # 一条狗朝你过来了,你会干吗? 判断: 如果狗是大长腿牵来的狗--->打招呼:如果是条疯狗,跑. # if:如果 ...

  3. Python 3.6 版本-使用Pytesseract 模块进行图像验证码识别

    环境: (1) win7 64位 (2) Idea (3) python 3.6 (4) pip install pillow <&nbsp>pip install pytesse ...

  4. http,socket,进程通信,网络通信(1)

    众所周知,网络通信本质上就是进程间通信,进程间通信有以下常见的通信方式: 1,管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常指父子进 ...

  5. Linux设备管理——sysfs、udev

    What is the use of Sysfs sysfs is a pseudo file system provided by the Linux kernel that exports inf ...

  6. 视图查询的数据和sql查询的数据不一样

    视图查询的数据和sql查询的数据不一样. 手动刷新视图 exec sp_refreshview  视图名称

  7. Python+Appium学习之启动手机APP或者浏览器

    一.启动浏览器:pycharm中python脚本如下: from appium import webdriver desired_caps ={ 'platformName':'Android', ' ...

  8. mybatis配置打印sql

    mybatis配置打印sql: <settings> <setting name="logImpl" value="STDOUT_LOGGING&quo ...

  9. CentOS 7.5下KVM的安装与配置

    由于没有物理机可用,在自己的VMware Workstation中CentOS 7.5下搭建完成. 首先查看VMware Workstation是否支持虚拟化,把红框内打钩即可. 虚拟化开启并安装Ce ...

  10. Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'qingmu' for key 'PRIMARY'

    ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolatio ...