DevUI是一支兼具设计视角和工程视角的团队,服务于华为云 DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。
官方网站: devui.design
Ng组件库: ng-devui(欢迎Star)
重构,是咱们开发过程当中不可避免须要进行的一项工做。重构代码,以适配当前模块设计之初未考虑到的多样化场景,并增长模块的可维护性、健壮性、可测试性。那么,如何明确重构的方向,以及量化重构的结果呢?前端
代码圈复杂度(Cyclomatic complexity,CC)能够是一个供选择的指标。git
圈复杂度(Cyclomatic complexity,CC)也称为条件复杂度或循环复杂度,是一种软件度量,是由老托马斯·J·麦凯布(Thomas J. McCabe, Sr.)在1976年提出,用来表示程序的复杂度,其符号为VG或是M。圈复杂度即程序的源代码中线性独立路径的个数。github
下表为模块(函数)圈复杂度与代码情况的一个基本对照表。除了表中给出的代码情况、可测性、维护成本等指标外,圈复杂度高的模块(函数),也对应着高软件复杂度、低内聚、高风险、低可读性。咱们要下降模块(函数)的圈复杂度,就是要下降其软件复杂度、增长内聚性、减小可能出现的软件缺陷个数、加强可测试性、可读性。算法
麦凯布提出圈复杂度时,其原始目的之一就是但愿在软件开发过程当中就限制其复杂度。他建议程序设计者需计算其开发模块的复杂度,若一模块的圈复杂度超过10,需再分割为更小的模块。NIST(国家标准技术研究所)的结构化测试方法论已此做法略做调整,在一些特定情形下,模块圈复杂度上限放宽到15会比较合适。此方法论也认可有些特殊情形下,模块的复杂度须要超过上述的上限,其建议为“模块的循环复杂度需在上限范围之内,不然需提供书面数据,说明为什么此模块循环复杂度有必要超过上限。”segmentfault
优秀的代码模块间老是低耦合,高内聚的。能够预期一个复杂度较高模块的内聚性会比较低,至少不会到功能内聚性的程度。一个有高复杂度及低内聚性的模块中会有许多的决策点,这类的模块多半运行超过一个明肯定义的任务,所以内聚性较低。前端工程师
许多研究指出模块(函数)的圈复杂度和其中的缺陷个数有相关性,许多这类研究发现圈复杂度和缺陷个数有高度的正相关:圈复杂度最高的模块及方法,其中的缺陷个数也最多。框架
一个圈复杂度高的模块(函数),由下文中将描述到的计算方法来看,必然会有更多的运行分支,要对这样的模块进行如单元测试用例的编写,将会十分复杂,而且后期用例维护也是一个问题。函数
代码可读性是大型项目与团队协做间必需要考虑的一个因素。圈复杂度高的模块(函数),随着逻辑复杂度的增长,代码可读性也将下降,不利于成员间相互协做与后期维护。工具
一段程序的圈复杂度是其线性独立路径的数量。若程序中没有像IF指令或FOR循环的控制流程,由于程序中只有一个路径,其圈复杂度为1,若程序中有一个IF指令,会有二个不一样路径,分别对应IF条件成立及不成立的情形,所以圈复杂度为2。post
数学上,一个结构化程序的圈复杂度是利用程序的控制流图来定义,控制流图是一个有向图,图中的节点为程序的基础模块,若一个模块结束后,可能会运行另外一个模块,则用箭头连接二个模块,并标示可能的运行顺序。圈复杂度M能够用下式定义:
M = E − N + 2P
其中
E 为图中边的个数
N 为图中节点的个数
P 为链接组件的个数
一款VSCode插件,用于度量TS、JS代码圈复杂度。
eslint
也能够配置关于圈复杂度的规则,如:
rules: { complexity: [ 'error', { max: 15 } ] }
表明了当前每一个函数
的最高圈复杂度
为15,不然eslint
将给出错误提示
。
一款开源的代码圈复杂度检测工具(github:https://github.com/ConardLi/awesome-cli),能够生成当前项目下代码圈复杂度报告。
要下降圈复杂度,咱们就须要了解是哪些语句哪些结构致使了咱们复杂度的增长,如下为常见结构圈复杂度说明。
顺序结构复杂度为1。
例:
function func() { let a = 1, b = 1, c; c = a * b; }
如上代码,func函数内部为顺序结构,其控制流图以下:
边:1,点:2,连通分支:1,
圈复杂度:
M = 1 - 2 + 2 * 1 = 1
每增长一个分支,复杂度增长1,&& 、|| 运算也为一个分支。
例:
function func() { let a = 1, b = 1, c; if (a = 1) { c = a + b; } else { c = a - b; } }
边:4,点:4,连通分支:1,
圈复杂度:
M = 4 - 4 + 2 * 1 = 2
增长一个循环结构,复杂度增长1。、
例:
function func() { let a = 1, b = 1, c = 2; for (let i = 1; i < 10; i++) { b += a; } c = a + b; }
边:4,点:4,连通分支:1,
圈复杂度:
M = 4 - 4 + 2 * 1 = 2
从理论上来说,return并不会增长当前模块圈复杂度,但在某些度量工具看来,一条return语句将增长总体程序的一条路径,而且若是提早返回,将增长程序的不肯定性,因此在大多数计算工具中,每增长一条return语句,复杂度将加1。
既然是下降一个模块(函数)圈复杂度,那么对于复杂度极高的函数,首先须要进行就是功能的提炼与函数拆分,每一个函数职责要单一。
例:
function add(a, b) { let tempA; if (a === 10) { tempA = 11; } else if (a === 12) { tempA = 12; } let tempB; if (b === 10) { tempB = 13; } else if (b === 12) { tempB = 12; } return tempB + tempA; }
重构为:
function add(a, b) { return calcA(a) + calcB(b); } function calcA(a) { if (a === 10) { return 11; } else if (a === 12) { return 12; } } function calcB(b) { if (b === 10) { return 13; } else if (b === 12) { return 12; } }
不只下降了add函数圈复杂度,而且代码结构更加清晰,增长了可读性,同时还增长了当前代码可维护性、可测试性。
固然,过犹不及,咱们的目标为提炼函数,保持函数单一职责,不能为了下降圈复杂度而进行暴力拆分。
从圈复杂度计算上来看,条件、循环分支均会增长模块圈复杂度。从某些程度上,复杂的条件与循环结构是可优化,减小没必要要结构,从而下降圈复杂度。
例:
let a = 'a', c; if (a === 'a') { c = a + 1; } else if (a === 'b') { c = a + 2; } else if (a === 'c') { c = a + 3; } else if (a === 'd') { c = a + 4; } return c;
重构为:
let a = 'a', c; let conditionMap = { a: 1, b: 2, c: 3, d: 4 } c = a + conditionMap[a]; return c;
消除了全部条件分支,从而大幅下降了当前函数圈复杂度。
逻辑计算也将增长圈复杂度,优化一些结构复杂的逻辑表达式,减小没必要要的逻辑判断,也将必定程度上下降圈复杂度。
例:
a && b || a && c
可进行简单优化为:
a && (b || c)
从而使表达式圈复杂度下降1。
单从下降圈复杂度上来看,因为当前大多数圈复杂度计算工具将对return个数进行计算,故若要针对这些工具衡量规则进行优化,减小return语句个数也为一种方式。
例:
let a = 1, b = 1; if (a = 1) { return a + b; } else { return a - b; }
重构为:
let a = 1, b = 1, c; if (a = 1) { c = a + b; } else { c = a - b; } return c;
圈复杂度将下降1。
圈复杂度(Cyclomatic complexity,CC)高的代码必定不是好代码,对于咱们代码好坏的衡量,圈复杂度能够做为一个参考指标;能够经过控制流图计算圈复杂度;要下降模块(函数)圈复杂度,提炼拆分函数、优化算法、优化逻辑表达式均为能够尝试的方法。
循环复杂度:https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E8%A4%87%E9%9B%9C%E5%BA%A6
详解圈复杂度:http://kaelzhang81.github.io/2017/06/18/%E8%AF%A6%E8%A7%A3%E5%9C%88%E5%A4%8D%E6%9D%82%E5%BA%A6/
前端代码质量-圈复杂度原理和实践:http://www.javashuo.com/article/p-szqkmqes-hh.html
咱们是DevUI团队,欢迎来这里和咱们一块儿打造优雅高效的人机设计/研发体系。招聘邮箱:muyang2@huawei.com。
文/DevUI 砰砰砰砰
往期文章推荐