目前市面上的前端代码质量评分中的代码可维护度是大都是基于 typhonjs-escomplex
这个库扫描而来,但是这个库的官方文档并没有介绍相关指标数据的计算规则,不知道规则如何提升指标数据呢?所以本文对 typhonjs-escomplex
源码进行探索,探索其关键指标计算逻辑。
使用方式
使用方式很简单,引用后调用 analyze
方法传入需要检测的源代码即可。
import escomplexModule from 'typhonjs-escomplex-module';
const ast = <some parsed AST>;
const report = escomplexModule.analyze(ast);
analyze
的第二个参数可以传入相关配置项。
commonjs: true,
logicalor: true,
newmi: true,
但此库只支持js
文件的检测,对应像vue
这类文件无法检测,我们可以通过以下方式提取script
中的内容后进行检测。
const fs = require('fs');
const doc = fs.readFileSync('test.vue', 'utf-8');
const escomplex = require('typhonjs-escomplex');
const srcs = doc.match(/(?<=<script>)[\s\S]*?(?=<\/script>)/g);
srcs.map((src) => {
const report = escomplex.analyzeModule(src);
});
调用analyzeModule
返回的report
即是代码的相关检测指标数据。
需要注意的是此库已经5年没有更新了,由于这方面的库比较少加上本身的能力也比较强大,就算过去了5年没有维护的情况下现在的每周下载仍在1万+的数量。
VSCode插件
在VScode中可以安装插件 FE Doctor
或 AppWorks 的 Doctor
。
FE Doctor
这个插件可以帮你一键生成质量报告,且执行速度很快。
Doctor
可以在一次扫描中快速检测到应用程序和基础库代码中的各种安全漏洞和质量问题,你可以一键修复所有报告的问题,或者点击定位到源代码逐条来修复。
这两款插件底层都是基于typhonjs-escomplex
库扫码代码,只是UI交互有些不一样,有兴趣的可以安装试试看,看看你的代码能打多少分。
关键指标
typhonjs-escomplex
执行检测完返回的report
的数据如下,有所删减:
ModuleReport {
lineEnd: 21,
lineStart: 1,
maintainability: 63.213,
methods: [],
aggregateAverage: MethodAverage {
cyclomatic: 3,
cyclomaticDensity: 30,
halstead: HalsteadAverage {
bugs: 0.053,
difficulty: 8.182,
effort: 1293.737,
length: 36,
time: 71.874,
vocabulary: 21,
volume: 158.123,
operands: [Object],
operators: [Object]
},
paramCount: 0,
sloc: { logical: 10, physical: 21 }
},
}
需要重点关注的有以下几个数据,后续计算需要用到:
cyclomatic
: 代码圈复杂度halstead.difficulty
: 代码可读性halstead.effort
: 代码工作量(volume与difficulty的乘积)sloc.logical
: 源代码行数maintainability
: 代码可维护度
这里着重解释一下代码圈复杂度和代码可读性,代码可维护度是基于前面的几个值进一步计算而来。
代码圈复杂度
代码圈复杂度(cyclomatic)是一种度量代码复杂性的指标,它用于衡量程序中的单个方法或函数的复杂程度。
圈复杂度常用的计算公式如下:
圈复杂度 = E - N + 2P
- E表示程序中的边(一条边表示一条语句或条件语句等控制流转换)的数量;
- N表示程序中的节点(基本块,即一组顺序执行的语句)的数量;
- P表示程序中的组件(子程序或函数)的数量。
简单来说圈复杂度技术公式的含义:圈复杂度等于边的数量减去节点的数量,再加上两个控制流图的起点和终点。
较高的代码圈复杂度意味着代码逻辑较为复杂,难以理解和维护,也意味着代码中存在潜在的错误和缺陷。建议控制代码圈复杂度在一个可管理的范围内,一般上限为10-15之间,以提高代码的质量和可读性。
详细解析可参考维基百科:https://en.wikipedia.org/wiki/Cyclomatic_complexity
代码可读性
上面report
中的halstead
对象相关指标是指霍尔斯特德复杂度测量(Halstead complexity measures
),代码可读性是halstead
中的difficulty
字段。
霍尔斯特德复杂度测量是由霍尔斯特德在1977年提出的一种软件度量方法,是有关软件开发经验科学的论文中的一部分。 霍尔斯特德观察到软件度量应该要反映在不同编程语言中算法实现的方式,但又要独立于使用的平台及语言。这些度量要可以由静态代码中计算而得。
相关指标计算公式如下,详细解析可参考维基百科:https://en.wikipedia.org/wiki/Halstead_complexity_measures
:
从公式可以看出核心计算的基础主要是以下数值:
- η 1 \eta_1 η1 为不同运算子的个数。
- η 2 \eta_2 η2 为不同算子的个数。
- N 1 N_1 N1 为所有运算子合计出现的次数。
-
N
2
N_2
N2 为所有算子合计出现的次数。
上述的运算子包括传统的运算子及保留字,算子包括变数及常数。依上述数值,可以计算以下的量测量: - 程式词汇数(Program vocabulary): η = η 1 + η 2 \eta = \eta _1+\eta_2 η=η1+η2
- 程式长度(Program length): N = N 1 + N 2 N=N_1+N_2 N=N1+N2。
程式词汇数可以理解为代码中的不同操作符和操作数的总数,较大数值意味着程序中使用了更多不同的操作符和操作数,可能需要更多的理解和维护工作。
程序长度是指程序中操作符和操作数的总数。突出程序的规模和复杂性,较长的程序长度意味可能需要更多的时间和资源来开发和维护。
知道其计算逻辑后我们的代码优化可以考虑以下几个方向:
-
识别并减少代码中不同操作符和操作数的数量。可以通过简化复杂表达式、删除不必要的变量或函数,以及使用更多的内置函数来实现。
-
删除冗余或重复的代码,重构代码、消除不必要代码带来的复杂性。使用高效的算法和数据结构,来减少程序长度。
-
复杂任务分解为更小、更易管理的函数或模块来实现。
-
使用更高级的控制结构和抽象代码,提高程序水平。使代码更易读、易于维护和理解。
代码可维护度
report
中的 maintainability
代表代码可维护度数值,1991年,保罗·奥曼(Paul Oman)和杰克·哈格迈斯特(Jack Hagemeister)在爱达荷大学设计了这个度量标准,该度量标准是根据其他三个度量标准的平均值在整个程序或模块级别上计算的,使用以下公式:
171 -
(3.42 * ln(mean effort)) -
(0.23 * ln(mean cyclomatic complexity)) -
(16.2 * ln(mean logical LOC))
最终取值范围从负无穷到171,较大的数值表示更高的可维护性水平。在他们的原始论文中,奥曼和哈格迈斯特确定65
是一个程序应被视为难以维护的阈值。
由此公式可以看出想要获取较大的数值需要让后面的减数足够小,那么我们就需要优化代码可读性,减小代码复杂度以及文件中代码的行数。
核心源码实现
接下来根据返回的report
查阅其源码找到maintainability
的计算代码如下:
function calculateMaintainabilityIndex(report, settings, averageCyclomatic, averageEffort, averageLoc) {
report.maintainability =
171 -
3.42 * Math.log(averageEffort) -
(0.23 * averageCyclomatic === 0 ? 0 : Math.log(averageCyclomatic)) -
16.2 * Math.log(averageLoc);
if (report.maintainability > 171) {
report.maintainability = 171;
}
if (settings.newmi) {
report.maintainability = Math.max(0, report.maintainability * 100 / 171);
}
}
calculateMaintainabilityIndex
函数用于计算代码的可维护度数据,核心计算和上面提到的公式一致。另外如果settings对象的newmi
属性为真,那么将maintainability
更新为其与171的比值的百分比,并确保其最小值为0且最大值为100,方便日常查看理解。
根据代码调用往上找到代码可读性halstead
相关属性的计算逻辑,和上面维基百科截图中的计算一致。
function calculateHalsteadMetrics(halstead) {
halstead.length = halstead.operators.total + halstead.operands.total;
if (halstead.length === 0) {
halstead.reset();
} else {
halstead.vocabulary = halstead.operators.distinct + halstead.operands.distinct;
halstead.difficulty = halstead.operators.distinct / 2 * (halstead.operands.distinct === 0 ? 1 : halstead.operands.total / halstead.operands.distinct);
halstead.volume = halstead.length * (Math.log(halstead.vocabulary) / Math.log(2));
halstead.effort = halstead.difficulty * halstead.volume;
halstead.bugs = halstead.volume / 3000;
halstead.time = halstead.effort / 18;
}
}
从这段代码可以看出整个计算都是基于 halstead.operators
和 halstead.operands
这两个数值,为了方便理解,扫码以下代码断点查看所影响的具体内容。
export const bType = {
0: '全部',
1: '淘宝',
2: '天猫',
3: '京东'
}
export const selectList = [
{
name: '今日',
id: 0
},
{
name: '近7日',
id: 1
},
{
name: '本月',
id: 2
}
]
以下是调试截图,可以看到 halstead.operators
最终的数据是代码中的操作符,而 halstead.operands
则是代码中的相关变量名称和对应的值。通过这两个数值可以看出要精简代码,简化复杂表达式、删除不必要的变量或函数才能提高相关数值。
总结
通过查阅代码圈复杂度和代码可读性的计算方法,并分析扫描库 typhonjs-escomplex
的源代码的实现逻辑,想要基于此扫码库提升代码质量以及相关指标数据可以从以下几个方面进行:
-
减少操作符和操作数的种类,尽量使用简单、常见的操作符和操作数,避免过于复杂或冗余的语言特性,以及使用更多的内置函数来实现。
-
模块化或组件复用,将代码分解为独立的模块或组件,每个模块负责特定的功能,这样可以减少重复的代码也会提示代码可维护度。
-
简化复杂逻辑,分解更小、更易管理的函数或模块来实现,避免过多的条件判断和嵌套循环,从而降低代码的复杂度。
-
代码重构:定期审查和重构代码,使其更加清晰、简洁,并符合良好的代码设计原则。
优化代码质量目标是减少代码的复杂性和冗余,提高代码的可读性和可维护性。但在实际优化过程中也需要权衡对代码的改造是否存在过大的风险和需要更多的投入回归测试。如果你对代码质量的要求高,那么这是一件需要长期要去做的事情,需要定期审查过往代码是否存在优化提升的空间。
看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~
专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)