加入收藏 | 设为首页 |

双氧水-前端代码质量-圈复杂度原理和实践

海外新闻 时间: 浏览:260 次

1. 导读

你们是否也有过下面的主意?

  • 重构一个项目还不如新开发一个项目...
  • 这代码是谁写的,我真想...

你们的项目中是否也存在下面的问题?

  • 单个项目也越来越巨大,团队成员代码风格纷歧致,无法对全体的代码质量做全面的掌控
  • 没有一个精确的规范去衡量代码结构杂乱的程度,无法量化一个项目的代码质量
  • 重构代码后无法当即量化重构后代码质量是否提高

针对上面的问题,本文的主角 圈杂乱度 重磅上台,本文将从圈杂乱度原理动身,介绍圈杂乱度的核算办法、怎么下降代码的圈杂乱度,怎么获取圈杂乱度,以及圈杂乱度在公司项目的实践运用。

2. 圈杂乱度

2.1 界说

圈杂乱度 (Cyclomatic complexity) 是一种代码杂乱度的衡量规范,也称为条件杂乱度或循环杂乱度,它能够用来衡量一个模块断定结构的杂乱程度,数量上体现为独立现行途径条数,也可了解为掩盖一切的或许状况最少运用的测验用例数。简称 CC 。其符号为 VG 或是 M 。

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

圈杂乱度大阐明程序代码的判别逻辑杂乱,或许质量低且难于测验和保护。程序的或许过错和高的圈杂乱度有着很大联系。

2.2 衡量规范

代码杂乱度低,代码纷歧定好,但代码杂乱度高,代码必定欠好。

3.1 操控流程图

操控流程图,是一个进程或程序的笼统体现,是用在编译器中的一个笼统数据结构,由编译器在内部保护,代表了一个程序履行进程中会遍历到的一切途径。它用图的方式表明一个进程内一切基本块履行的或许流向, 也能反映一个进程的实时履行进程。

下面是一些常见的操控流程:

3.2 节点断定法

有一个简略的核算办法,圈杂乱度实践上便是等于断定节点的数量再加上1。向上面说到的:if else 、switch case 、 for循环、三元运算符等等,都归于一个断定节点,例如下面的代码:

function testComplexity(*param*) {
let result = 1;
if (param > 0) {
result--;
}
for (let i = 0; i < 10; i++) {
result += Math.random();
}
switch (parseInt(result)) {
case 1:
result += 20;
break;
case 2:
result += 30;
break;
default:
result += 10;
break;
}
return result > 20 ? result : result;
}

上面的代码中一共有1个if句子,一个for循环,两个case句子,一个三元运算符,所以代码杂乱度为 4+1+1=6。别的,需求留意的是 || 和 && 句子也会被算作一个断定节点,例如下面代码的代码杂乱为3:

function testComplexity(*param*) {
let result = 1;
if (param > 0 && param < 10) {
result--;
}
return result;
}

3.3 点边核算法

M 双氧水-前端代码质量-圈复杂度原理和实践= E − N + 2P
仿制代码
  • E:操控流图中边的数量
  • N:操控流图中的节点数量
  • P:独立组件的数目

前两个,边和节点都是数据结构图中最基本的概念:

P代表图中独立组件的数目,独立组件是什么意思呢?来看看下面两个图,左边为连通图,右侧为非连通图:

  • 连通图:关于图中恣意两个极点都是连通的

一个连通图即为图中的一个独立组件,所以左边图中独立组件的数目为1,右侧则有两个独立组件。

关于咱们的代码转化而来的操控流程图,正常状况下一切节点都应该是连通的,除非你在某些节点之前履行了 return,明显这样的代码是过错的。所以每个程序流程图的独立组件的数目都为1,所以上面的公式还能够简化为 M = E − N + 2 。

4. 下降代码的圈杂乱度

咱们能够经过一些代码重构手法来下降代码的圈杂乱度。

重构需谨慎,示例代码只是代表一种思维,实践代码要远远比示例代码杂乱的多。

4.1 笼统装备

经过笼统装备将杂乱的逻辑判别进行简化。例如下面的代码,依据用户的选择项履行相应的操作,重构后下降了代码杂乱度,而且假如之后有新的选项,直接参加装备即可,而不需求再去深化代码逻辑中进行改动:

4.2 单一责任 - 提炼函数

单一责任准则(SRP):每个类都应该有一个单一的功用,一个类应该只要一个发作改变的原因。

在 JavaScript 中,需求用到的类的场景并不太多,单一责任准则则是更多地运用在目标或许办法等级上面。

函数应该做一件事,做好这件事,只做这一件事。 — 代码整齐之道

关键是怎么界说这 “一件事” ,怎么将代码中的逻辑进行笼统,有用的提炼函数有利于下降代码杂乱度和下降保护本钱。

4.3 运用 break 和 return 替代操控符号

咱们常常会运用一个操控符号来标明当时程序运转到某一状况,许多场景下,运用 break 和 return 能够替代这些符号并下降代码杂乱度。

4.4 用函数替代参数

setField 和 getField 函数便是典型的函数替代参数,假如么有 setField、getField 函数,咱们或许需求一个很杂乱的 setValue、getValue 来完结特点赋值操作:

4.5 简化条件判别 - 逆向条件

某些杂乱的条件判别或许逆向考虑后会变的更简略。

4.6 简化条件判别 -兼并条件

将杂乱冗余的条件判别进行兼并。

4.7 简化条件判别 - 提取条件

将杂乱难明的条件进行语义化提取。

5. 圈杂乱度检测办法

5.1 eslint规矩

eslint供给了检测代码圈杂乱度的rules:

咱们将敞开 rules 中的 complexity 规矩,并将圈杂乱度大于 0 的代码的 rule severity 设置为 warn 或 error 。

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

这样 eslint 就会自动检测出一切函数的代码杂乱度,并输出一个相似下面的 message。

Method 'testFunc' has a complexity of 12. Maximum allowed is 0
Async function has a complexity of 6. Maximum allowed is 0.
...

5.2 CLIEngine

咱们能够凭借 eslint 的 CLIEngine ,在本地运用自界说的 eslint 规矩扫描代码,并获取扫描成果输出。

初始化 CLIEngine :

const eslint = require('eslint');
const { CLIEngine } = eslint;
const cli = new CLIEngine({
parserOptions: {
ecmaVersion: 2018,
},
rules: {
complexity: [
'error',
{ max: 0 }
]
}
});

运用 executeOnFiles 对指定文件进行扫描,并获取成果,过滤出一切 complexity 的 message 信息。

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);
}
}
}

5.3 提取message

经过 eslint 的检测成果将有用的信息提取出来,先测验几个不同类型的函数,看看 eslint 的检测成果:

function func1() {
console.log(1);
}
const func2 = () => {
console.log(2);
};
class TestClass {
func3() {
console.log(3);
}
}
async function func4() {
console.log(1);
}

履行成果:

Function 'func1' has a complexity of 1. Maximum allowed is 0.
Arrow function has a complexity of 1. Maximum allowed is 0.
Method 'func3' has a complexity of 1. Max双氧水-前端代码质量-圈复杂度原理和实践imum allowed is 0.
Async function 'func4' has a complexity of 1. Maximum allowed is 0.

能够发现,除了前面的函数类型,以及后边的杂乱度,其他都是相同的。

函数双氧水-前端代码质量-圈复杂度原理和实践类型:

  • Function :一般函数
  • Arrow function : 箭头函数
  • Method : 类办法
  • Async function : 异步函数

截取办法类型:

const REG_FUNC_TYPE = /^(Method |Async function |Arrow function |Function )/g;
function getFunctionType(message) {
let hasFuncType = REG_FUNC_TYPE.test(message);
return hasFuncType && RegExp.$1;
}

将有用的部分提取出来:

const MESSAGE_PREFIX = 'Maximum allowed is 1.';
const MESSAGE_SUFFIX = 'has a complexity of ';
function getMain(message) {
return message.replace(MESSAGE_PREFIX, '').replace(MESSAGE_SUFFIX, '');
}

提取办法称号:

function getFunctionName(message) {
const main = getMain(message);
let test = /'([a-zA-Z0-9_$]+)'/g.test(main);
return test ? RegExp.$1 : '*';
}

截替代码杂乱度:

function getComplexity(message) {
const main = getMain(message);
(/(\d+)\./g).test(main);
return +RegExp.$1;
}

除了 message ,还有其他的有用信息:

  • 函数方位:获取 messages 中的 line 、column 即函数的行、列方位
  • 当时文件称号:reports 成果中能够获取当时扫描文件的绝对途径 filePath ,经过下面的操作获取实在文件名:
filePath.replace(process.cwd(), '').trim()
仿制代码
  • 杂乱度等级,依据函数的杂乱度等级给出重构主张:

6.架构规划

将代码杂乱度检测封装成根底包,依据自界说装备输出检测数据,供其他运用调用。

上面的展现了运用 eslint 获替代码杂乱度的思路,下面咱们要把它封装为一个通用的东西,考虑到东西或许在不同场景下运用,例如:网页版的剖析陈述、cli版的指令行东西,咱们把通用的才能笼统出来以 npm包 的方式供其他运用运用。

在核算项目代码杂乱度之前,咱们首要要具有一项根底才能,代码扫描,即咱们要知道咱们要对项目里的哪些文件做剖析,首要 eslint 是具有这样的才能的,咱们也能够直接用 glob 来遍历文件。可是他们都有一个缺点,便是 ignore 规矩是不同的,这关于用户来讲是有必定学习本钱的,因而我这儿把手动封装代码扫描,运用通用的 npm ignore 规矩,这样代码扫描就能够直接运用 .gitignore这样的装备文件。别的,代码扫描作为代码剖析的根底才能,其他代码剖析也是能够共用的。

  • 根底才能
  • 代码扫描才能
  • 杂乱度检测才能
  • ...
  • 运用
  • 指令行东西
  • 代码剖析陈述
  • ...

7. 根底才能 - 代码扫描

本文触及的 npm 包和 cli指令源码均可在我的开源项目 awesome-cli中检查。

awesome-cli 是我新建的一个开源项目:风趣又有用的指令行东西,后边会继续保护,敬请重视,欢迎 star。

代码扫描(c-scan)源码:github.com/ConardLi/aw…

代码扫描是代码剖析的底层才能,它首要协助咱们拿到咱们想要的文件途径,应该满意咱们以下两个需求:

  • 我要得到什么类型的文件
  • 我不想要哪些文件

7.1 运双氧水-前端代码质量-圈复杂度原理和实践用

npm i c-scan --save
const scan = require('c-scan');
scan({
extensions:'**/*.js',
rootPath:'src',
defalutIgnore:'true',
ignoreRules:[],
ignoreFileName:'.gitignore'
});

7.2 返回值

契合规矩的文件途径数组:

7.3 参数

  • extensions
  • 扫描文件扩展名
  • 默许值:**/*.js
  • rootPath
  • 扫描文件途径
  • 默许值:.
  • defalutIgnore
  • 是否敞开默许疏忽(glob规矩)
  • glob ignore规矩为内部运用,为了一致ignore规矩,自界说规矩运用gitignore规矩
  • 默许值:true
  • 默许敞开的 glob ignore 规矩:
const DEFAULT_IGNORE_PATTERNS = [
'node_modules/**',
'build/**',
'dist/**',
'output/**',
'common_build/**'
];
  • ignoreRules
  • 自界说疏忽规矩(gitignore规矩)
  • 默许值:[]
  • ignoreFileName
  • 自界说疏忽规矩装备文件途径(gitignore规矩)
  • 默许值:.gitignore
  • 指定为null则不启用ignore装备文件

7.4 中心完成

依据 glob ,自界说 ignore 规矩进行二次封装。

/**
* 获取glob扫描的文件列表
* @param {*} rootPath 跟途径
* @param {*} extensions 扩展
* @param {*} defalutIgnore 是否敞开默许疏忽
*/
function getGlobScan(rootPath, extensions, defalutIgnore) {
return new Promise(resolve => {
glob(`${rootPath}${extensions}`,
{ dot: true, ignore: defalutIgnore ? DEFAULT_IGNORE_PATTERNS : [] },
(err, files) => {
if (err) {
console.log(err);
process.exit(1);
}
resolve(files);
});
});
}
/**
* 加载ignore装备文件,并处理成数组
* @双氧水-前端代码质量-圈复杂度原理和实践param {*} ignoreFileName
*/
async function loadIgnorePatterns(ignoreFileName) {
const ignorePath = path.resolve(process.cwd(), ignoreFileName);
try {
const ignores = fs.readFileSync(ignorePath, 'utf8');
return ignores.split(/[\n\r]|\n\r/).filter(pattern => Boolean(pattern));
} catch (e) {
return [];
}
}
/**
* 依据ignore装备过滤文件列表
* @param {*} files
* @param {*} ignorePatterns
* @param {*} cwd
*/
function filterFilesByIgnore(files, ignorePatterns, ig淋巴结肿大图片noreRules, cwd = process.cwd()) {
const ig = ignore().add([...ignorePatterns, ...ignoreRules]);
const filtered = files
.map(raw => (path.isAbsolute(raw) ? raw : path.resolve(cwd, raw)))
.map(raw => path.relative(cwd, raw))
.filter(filePath => !ig.ignores(filePath))
.map(raw => path.resolve(cwd, raw));
return filtered;
}

8. 根底才能 - 代码杂乱度检测

代码杂乱度检测(c-complexity)源码:github.com/ConardLi/aw…

代码检测根底包应该具有以下几个才能:

  • 自界说扫描文件夹和类型
  • 支撑疏忽文件
  • 界说最小提示代码杂乱度

8.1 运用

npm i c-complexity --save
const cc = require('c-complexity');
cc({},10);

8.2 返回值

  • fileCount:文件数量
  • funcCount:函数数量
  • result:具体成果
  • funcType:函数类型
  • funcName;函数称号
  • position:具体方位(队伍号)
  • fileName:文件相对途径
  • complexity:代码杂乱度
  • advice:重构主张

8.3 参数

  • scanParam承继自上面代码扫描的参数
  • min最小提示代码杂乱度,默许为1

9. 运用 - 代码杂乱度检测东西

代码杂乱度检测(conard cc)源码:github.com/ConardLi/aw…

9.1 指定最小提示杂乱度

能够触发提示的最小杂乱度。

  • 默许为 10
  • 经过指令 conard cc --min=5 自界说

9.2 指定扫描参数

自界说扫描规矩

  • 扫描参数承继自上面的 scan param
  • 例如: conard cc --defalutIgnore=false

10. 运用 - 代码杂乱度陈述

部分截图来历于咱们内部的项目质量监控渠道,圈杂乱度作为一项重要的目标,关于衡量项目代码质量起着至关重要的效果。

代码杂乱杂乱度改变趋势

守时使命爬替代码每日的代码杂乱度、代码行数、函数个数,经过每日数据制作代码杂乱度和代码行数改变趋势折线图。

经过 [ 杂乱度 / 代码行数 ] 或 [ 杂乱度 / 函数个数 ] 的改变趋势,判别项目开展是否健康。

  • 比值若一直在上涨,阐明你的代码在变得越来越难以了解。这不只使咱们面对意外的功用交互和缺点的危险,因为咱们在具有或多或少相关功用的模块中所面对的过多认知担负,也很难重用代码并进行修正和测验。(下图1)
  • 若比值在某个阶段发作骤变,阐明这段期间迭代质量很差。(下图2)

  • 杂乱度曲线图能够很快的帮你更早的发现上面这两个问题,发现它们后,你或许需求重构代码。杂乱性趋势关于盯梢你的代码重构也很有用。杂乱性趋势的下降趋势是一个好征兆。这要么意味着您的代码变得更简略(例如,把 if-else 被重构为多态处理方案),要么代码更少(将不相关的部分提取到了其他模块中)。(下图3)
  • 代码重构后,你还需求继续探究杂乱度改变趋势。常常发作的工作是,咱们花费很多的时间和精力来重构,无法处理根本原因,很快杂乱度又滑回了原处。(下图4)你或许觉得这是个例,可是有研讨标明,在剖析了数百个代码库后,发现呈现这种状况的频率很高。因而,时间调查代码杂乱度改变趋势是有必要的。

代码杂乱度文件散布

核算各杂乱度散布的函数数量。

代码杂乱度文件概况

核算每个函数的代码杂乱度,从高到低顺次列出高杂乱度的文件散布,并给出重构主张。

实践开发中并纷歧定一切的代码都需求被剖析,例如打包产品、静态资源文件等等,这些文件往往会误导咱们的剖析成果,现在剖析东西会默许疏忽一些规矩,例如:.gitignore文件、static目录等等,实践这些规矩还需求依据实践项目的状况去不断完双氧水-前端代码质量-圈复杂度原理和实践善,使剖析成果变得更精确。

小结

期望看完本篇文章能对你有如下协助:

  • 了解圈杂乱度的含义和核算办法
  • 在项目中能实践运用圈杂乱度提高项目质量

文中如有过错,欢迎在谈论区纠正,假如这篇文章协助到了你,欢迎点赞和重视。

作者:ConardLi

链接:https://juejin.im/post/5da34216e51d4578502c24c5

来历:掘金