typhonjs-escomplex 代码可读性 可维护度探索

目前市面上的前端代码质量评分中的代码可维护度是大都是基于 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 可以在一次扫描中快速检测到应用程序和基础库代码中的各种安全漏洞和质量问题,你可以一键修复所有报告的问题,或者点击定位到源代码逐条来修复。

Doctor

FE Doctor

FE 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.operatorshalstead.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 的源代码的实现逻辑,想要基于此扫码库提升代码质量以及相关指标数据可以从以下几个方面进行:

  1. 减少操作符和操作数的种类,尽量使用简单、常见的操作符和操作数,避免过于复杂或冗余的语言特性,以及使用更多的内置函数来实现。

  2. 模块化或组件复用,将代码分解为独立的模块或组件,每个模块负责特定的功能,这样可以减少重复的代码也会提示代码可维护度。

  3. 简化复杂逻辑,分解更小、更易管理的函数或模块来实现,避免过多的条件判断和嵌套循环,从而降低代码的复杂度。

  4. 代码重构:定期审查和重构代码,使其更加清晰、简洁,并符合良好的代码设计原则。

优化代码质量目标是减少代码的复杂性和冗余,提高代码的可读性和可维护性。但在实际优化过程中也需要权衡对代码的改造是否存在过大的风险和需要更多的投入回归测试。如果你对代码质量的要求高,那么这是一件需要长期要去做的事情,需要定期审查过往代码是否存在优化提升的空间。


看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/145691.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux操作系统使用及C高级编程-D5Linux shell命令(进程管理、用户管理)

进程管理 查看进程ps 其中ps -eif可显示父进程 实时查看进程top 按q退出 树状图显示进程pstree 以父进程&#xff0c;子进程以树状形式展示 发送信号kill kill -l&#xff1a;查看都有哪些信号 9&#xff1a;进程终止 kill不指定信号&#xff0c;默认发送的是15信号SIGT…

接口自动化测试,必须要掌握post提交数据的这4种方式

我们都知道POST一般用于向服务端提交数据&#xff0c;POST提交数据的4种格式即Content-Type的4种形式&#xff0c;尤其注意每种格式中http发送请求时body中数据的格式。4种形式分别是&#xff1a; 一、application/x-www-form-urlencoded&#xff1a;URL encoded。 二、multi…

【C++】:STL——标准模板库介绍 || string类

&#x1f4da;1.什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且 是一个包罗数据结构与算法的软件框架 &#x1f4da;2.STL的版本 原始版本 Alexander Stepanov、Meng Lee 在…

Linux--makefile

一、makefile的作用 makefile是一个文件&#xff0c;是围绕依赖关系和依赖方法的自动化编译工具 一个工程中的源文件有很多&#xff0c;按照不同的类型、功能、模块放在不同的目录中。而makefile定义了一系列的规则来指定&#xff0c;那些文件需要先编译&#xff0c;那些文件…

[文件读取]Druid 任意文件读取 (CVE-2021-36749)

1.1漏洞描述 漏洞编号CVE-2021-36749漏洞类型文件读取漏洞等级⭐⭐⭐漏洞环境VULFOCUS攻击方式 描述: 由于用户指定 HTTP InputSource 没有做出限制&#xff0c;可以通过将文件 URL 传递给 HTTP InputSource 来绕过应用程序级别的限制。攻击者可利用该漏洞在未授权情况下&…

Android设计模式--原型模式

一&#xff0c;定义 原型模式就是用原型实例指定创建对象的种类&#xff0c;并通过拷贝这些原型创建新的对象 也就是说用户从一个实例中复制出一个内部属性一致的对象&#xff0c;这个被复制的对象就是原型。 原型模式多用于创建复杂的或者构造耗时的实例&#xff0c;因为这…

6-8.4V输入 双节锂电输入 输出19V 1.5-2A 外置MOS 大电流升压芯片

6-8.4V输入 双节锂电输入 输出19V 1.5-2A 外置MOS 大电流升压芯片

C++:OJ练习(每日练习!)

编程题&#xff1a; 题一&#xff1a;计算日期到天数的转换 计算日期到天数转换_牛客题霸_牛客网 (nowcoder.com) 示例1 输入&#xff1a; 2012 12 31 输出&#xff1a; 366 思路一&#xff1a; 第一步&#xff1a;创建年&#xff0c;月&#xff0c;日的变量&#xff0c;并按…

如何正确使用 JavaScript 中的 slice() 方法

在 JavaScript 中&#xff0c;slice() 是一个常用的数组方法&#xff0c;用于从现有数组中提取一部分元素&#xff0c;然后返回一个新的数组。它是一个非常有用的工具&#xff0c;可以帮助你在不改变原始数组的情况下操作数组的子集。本文将介绍 slice() 的基本概念、使用方法、…

阿里云+宝塔部署项目(Java+React)

阿里云服务器宝塔面板部署项目&#xff08;SpringBoot React&#xff09; 1. 上传所需的文件到服务器 比如jdk包和java项目的jar&#xff1a;这里以上传jar 为例&#xff0c;创建文件夹&#xff0c;上传文件&#xff1b; 在创建的文件夹下上传jar包 上传jdk 2. 配置jdk环境 3.…

No201.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

【游戏开发算法每日一记】使用随机prime算法生成错综复杂效果的迷宫(C#,C++和Unity版)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

java实现选择排序

算法步骤 首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置再从剩余未排序元素中继续寻找最小&#xff08;大&#xff09;元素&#xff0c;然后放到已排序序列的末尾。重复第二步&#xff0c;直到所有元素均排序完毕。 动图演…

【Linux网络】ssh服务与配置,实现安全的密钥对免密登录

目录 一、SSH基础 1、什么是ssh服务器 2、对比一下ssh协议与telnet协议 3、常见的底层为ssh协议的软件&#xff1a; 4、拓展 二、SSH软件学习 1、ssh服务软件学习 2、sshd公钥传输的原理&#xff1a; 3、ssh命令学习&#xff1a; 4、学习解读sshd服务配置文件&#x…

自定义Matplotlib中的颜色映射(cmap)

要自定义Matplotlib中的颜色映射&#xff08;cmap&#xff09;&#xff0c;您可以按照以下步骤进行操作&#xff1a; 导入所需的库&#xff1a; import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap创建自定义颜色映…

一篇博客读懂栈——Stack

目录 一、栈的概念与结构 1.1栈的概念 1.2栈的结构 二、栈的实现 2.1开始前准备stack.h 2.2栈的初始化STInit 2.3栈的销毁STDestroy 2.4栈顶插入STPush 2.5栈顶删除STPop 2.6取栈顶元素STTop 2.7检查栈是否为空STEmpty 2.8栈的元素个数STSize 一、栈的概念与结…

房产中介租房小程序系统开发搭建:详细指南教你如何构建

随着微信小程序的日益普及&#xff0c;越来越多的企业和个人开始尝试开发自己的小程序。以下是制作一个房地产微信小程序的详细教程&#xff0c;希望对大家有所帮助。 一、注册登录乔拓云平台&#xff0c;进入后台 首先&#xff0c;需要注册并登录乔拓云平台&#xff0c;该平台…

JavaEE初阶(18)(JVM简介:发展史,运行流程、类加载:类加载的基本流程,双亲委派模型、垃圾回收相关:死亡对象的判断算法,垃圾回收算法,垃圾收集器)

接上次博客&#xff1a;初阶JavaEE&#xff08;17&#xff09;Linux 基本使用和 web 程序部署-CSDN博客 目录 JVM 简介 JVM 发展史 JVM 运行流程 JVM的内存区域划分 JVM 执行流程 堆 堆的作用 JVM参数设置 堆的组成 垃圾回收 堆内存管理 类加载 类加载的基本流…

Vue事件绑定

目录 前言 Vue事件处理 指令的语法格式 事件绑定指令—v-on 回调函数 前言 我们知道如何在原生js中实现事件绑定&#xff0c;那在vue中如何实现呢&#xff1f; Vue事件处理 指令的语法格式 <标签 v-指令名&#xff1a;参数名"表达式">{{插值语法}}</…

MQ四大消费问题一锅端:消息不丢失 + 消息积压 + 重复消费 + 消费顺序性

RabbitMQ-如何保证消息不丢失 生产者把消息发送到 RabbitMQ Server 的过程中丢失 从生产者发送消息的角度来说&#xff0c;RabbitMQ 提供了一个 Confirm&#xff08;消息确认&#xff09;机制&#xff0c;生产者发送消息到 Server 端以后&#xff0c;如果消息处理成功&#xff…