代码质量规范测量

news2024/11/27 17:38:44

圈复杂度介绍

圈复杂度(Cyclomatic complexity)是一种代码复杂度的衡量标准,在1976年由Thomas J. McCabe, Sr. 提出。在软件测试的概念里,圈复杂度用来衡量一个模块判定结构的复杂程度,数量上表现为线性无关的路径条数,即合理的预防错误所需测试的最少路径条数。圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验:

程序的可能错误和高的圈复杂度有着很大关系。

圈复杂度衡量标准

圈复杂度代码状况可测性维护成本
0 - 5良好
5 - 10良好中等中等
10 - 20较差
20 - 30很低很高

圈复杂度计算

计算公式

计算公式1

V(G)=e-n+2p。其中,e表示控制流图中边的数量,n表示控制流图中节点的数量,p图的连接组件数目(图的组件数是相连节点的最大集合)。因为控制流图都是连通的,所以p为1.

计算公式2

V(G)=区域数=判定节点数+1。其实,圈复杂度的计算还有更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上1,也即控制流图的区域数。

对于多分支的CASE结构或IF-ELSEIF-ELSE结构,统计判定节点的个数时需要特别注意一点,要求必须统计全部实际的判定节点数,也即每个ELSEIF语句,以及每个CASE语句,都应该算为一个判定节点。

计算公式3

计算公式3:V(G)=R。其中R代表平面被控制流图划分成的区域数。

圈复杂度

针对程序的控制流图计算圈复杂度V(G)时,最好还是采用第一个公式,也即V(G)=e-n+2;而针对模块的控制流图时,可以直接统计判定节点数,这样更为简单;针对复杂的控制流图是,使用区域计算公式V(G)=R更为简单。

推荐使用第一种计算方法。

圈复杂度示例

典型的控制流程,如if-else,While,until和正常的流程顺序:

圈复杂度的计算还有更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上1,也即控制流图的区域数,对应的计算公式为:

V (G) = P + 1

  1. if语句
  2. while语句
  3. for语句
  4. case语句
  5. catch语句
  6. and和or布尔操作
  7. ?:三元运算符

示例:

function sort(A: number[]): void {
  let i = 0
  const n = 4
  let j = 0
  while (i < n - 1) {
      j = i + 1
      while (j < n) {
          if (A[i] < A[j]) {
              const temp = A[i]
              A[i] = A[j]
              A[j] = temp
          }
      }
      i = i + 1
  }
}

使用点边计算法绘出控制流图:

其圈复杂度为:V(G) = 9 - 7 + 2 = 4

圈复杂度的检测工具

项目sonarqubeeslintcodemetrics
圈复杂度度量标准支持支持支持
检测效率
精度
支持的编程语言一般一般
对圈复杂度高的代码的指导性

sonarqube

  1. 安装SonarQube服务器

SonarQube服务器可以通过下载和安装来获取,也可以在云上进行部署。你可以从SonarQube的官方网站上下载并安装相应的版本。安装完成后,需要启动SonarQube服务器。

  1. 配置SonarQube服务器

安装完成后,需要在SonarQube服务器中进行一些配置,例如配置数据库和LDAP等。你可以参考SonarQube的官方文档来进行相应的配置。

  1. 安装SonarQube扫描器

SonarQube扫描器可以安装在本地开发机器或者CI/CD服务器上。你需要下载并安装相应的扫描器,然后配置扫描器与SonarQube服务器的连接。

  1. 配置项目

在SonarQube服务器中,你需要为每个项目进行相应的配置。你可以在SonarQube界面中手动创建项目,也可以使用SonarQube API进行自动化配置。在配置项目时,需要设置项目名称、语言类型、代码仓库地址等信息。

  1. 运行SonarQube扫描器

在配置完成后,你需要使用SonarQube扫描器对代码进行扫描。在扫描时,你需要指定要扫描的代码路径、扫描器的参数等。扫描器将会把扫描结果上传到SonarQube服务器中。


eslint

使用ESLint检测圈复杂度的步骤:

  1. 安装ESLint

在命令行中执行以下命令安装ESLint:

npm install eslint --save-dev
  1. 安装eslint-plugin-complexity插件

在命令行中执行以下命令安装eslint-plugin-complexity插件:

npm install eslint-plugin-complexity --save-dev
  1. 配置ESLint

在项目根目录下创建.eslintrc.js文件,配置ESLint和eslint-plugin-complexity插件。示例如下:

module.exports = {
  env: {
    browser: true,
    es6: true,
  },
  extends: [
    'eslint:recommended',
  ],
  plugins: [
    'complexity',
  ],
  rules: {
    'complexity': ['error', { 'max': 10 }],
  },
};

其中,"max"表示允许的最大圈复杂度,上述配置将检测每个函数的圈复杂度是否大于10。

  1. 运行ESLint

在命令行中执行以下命令来运行ESLint:

npx eslint yourfile.js

其中,"yourfile.js"为待检测的JavaScript文件。

运行结果将会显示每个函数的圈复杂度是否超过了配置的最大值,如果超过了,ESLint将会给出相应的警告和建议。

codeMetrics

插件商店直接搜索codeMetrics,直接安装既可。

检测结果。

如何保障代码质量

  1. 单一职责原则

单一职责原则是指每个类或方法应该只有一个责任。如果一个方法或类的职责过于复杂,那么它就很容易产生高圈复杂度。通过将复杂的方法或类拆分为多个小方法或类,每个方法或类只关注一个特定的任务,可以有效地降低圈复杂度。

before:

class User {
  login(username: string, password: string): boolean {
    // 验证用户身份
    // ...
    return true;
  }

  getProfile(userId: number): object {
    // 获取用户信息
    // ...
    return {};
  }
}

after:

class Authenticator {
  login(username: string, password: string): boolean {
    // 验证用户身份
    // ...
    return true;
  }
}

class UserProfile {
  getProfile(userId: number): object {
    // 获取用户信息
    // ...
    return {};
  }
}
  1. 开闭原则

开闭原则是指软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。通过采用开闭原则,可以使我们的代码更加容易扩展,从而避免出现复杂的控制流程和高圈复杂度。

before

function quickSort(data: number[]) {
  // 快速排序算法的实现
}

function mergeSort(data: number[]) {
  // 归并排序算法的实现
}

after:

interface SortStrategy {
  sort(data: number[]): number[];
}

class QuickSortStrategy implements SortStrategy {
  sort(data: number[]) {
    // 快速排序算法的实现
  }
}

class MergeSortStrategy implements SortStrategy {
  sort(data: number[]) {
    // 归并排序算法的实现
  }
}

class Sorter {
  private strategy: SortStrategy;

  constructor(strategy: SortStrategy) {
    this.strategy = strategy;
  }

  sort(data: number[]) {
    return this.strategy.sort(data);
  }
}
  1. 去除重复代码

重复的代码是代码复杂度的一种来源。通过去除重复的代码,可以将代码块中的控制流程减少到最小,从而降低圈复杂度。

  1. 提炼函数

将复杂的代码块提炼到一个单独的函数中,可以将控制流程减少到最小,从而降低圈复杂度。

before:

// 原始代码
function calculateTotalPrice(products) {
  let totalPrice = 0;
  for (let i = 0; i < products.length; i++) {
    const product = products[i];
    totalPrice += product.price * product.quantity;
    if (product.isOnSale) {
      totalPrice -= product.discount;
    }
  }
  return totalPrice;
}

after:

// 重构后的代码
function calculateTotalPrice(products) {
  let totalPrice = 0;
  for (let i = 0; i < products.length; i++) {
    const product = products[i];
    totalPrice += calculateProductPrice(product);
  }
  return totalPrice;
}

function calculateProductPrice(product) {
  let productPrice = product.price * product.quantity;
  if (product.isOnSale) {
    productPrice -= product.discount;
  }
  return productPrice;
}
  1. 引入多态

引入多态可以避免复杂的条件语句,从而使代码更加简洁和易于理解。多态是一种在运行时根据对象类型选择方法的机制,它可以避免使用复杂的条件语句,从而减少代码块中的控制流程,降低圈复杂度。

// 抽象的动物类
abstract class Animal {
  abstract makeSound(): void;
}

// 具体的狗类
class Dog extends Animal {
  makeSound(): void {
    console.log("汪汪汪!");
  }
}

// 具体的猫类
class Cat extends Animal {
  makeSound(): void {
    console.log("喵喵喵!");
  }
}

// Animal 类型的数组
const animals: Animal[] = [new Dog(), new Cat()];

// 遍历数组,调用不同的 makeSound 方法
animals.forEach(animal => animal.makeSound());
  1. 提前返回

通过提前返回可以避免过多的条件语句和嵌套,从而减少圈复杂度。使用break和return提前返回。

function calculateBonus(salary: number, level: string) {
  if (salary <= 0) {
    return 0;
  }
  
  let bonus = 0;
  switch (level) {
    case 'A':
      bonus = salary * 0.2;
      break;
    case 'B':
      bonus = salary * 0.1;
      break;
    case 'C':
      bonus = salary * 0.05;
      break;
    default:
      break;
  }
  
  return bonus;
}
  1. 使用多个小函数

使用多个小函数可以使代码更加模块化,从而降低圈复杂度。每个小函数只需要关注一个具体的任务,从而避免出现复杂的控制流程。

before:

// 大函数
function processItems(items: any[]) {
  const results = [];
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    // 执行一系列操作...
    if (item.isValid) {
      results.push(item.value * 2);
    }
    // 执行一系列操作...
    if (item.isValid && item.value > 10) {
      results.push(item.value * 3);
    }
    // 执行一系列操作...
  }
  return results;
}

after:

// 使用多个小函数
function processItems(items: any[]) {
  const results = [];
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    const result = processItem(item);
    if (result !== null) {
      results.push(result);
    }
  }
  return results;
}

function processItem(item: any): any {
  const result1 = processItemPart1(item);
  const result2 = processItemPart2(item);
  if (result1 !== null && result2 !== null) {
    return result1 * 2 + result2 * 3;
  }
  return null;
}

function processItemPart1(item: any): any {
  // 执行一系列操作...
  if (item.isValid) {
    return item.value;
  }
  return null;
}

function processItemPart2(item: any): any {
  // 执行一系列操作...
  if (item.isValid && item.value > 10) {
    return item.value;
  }
  return null;
}
  1. 使用函数式编程

函数式编程是一种通过函数组合来构建复杂程序的编程范式,它可以避免出现复杂的控制流程和高圈复杂度。函数式编程中的函数通常都是纯函数,即给定相同的输入,始终返回相同的输出,因此不会受到外部环境的影响。

function sum(array) {
  let result = 0;
  for (let i = 0; i < array.length; i++) {
    result += array[i];
  }
  return result;
}

after:

function sum(array) {
  return array.reduce((acc, val) => acc + val, 0);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

脑洞大开:“Excel+中文编程”衍生新型软件,WPS用户:自家孩子

中国人脑洞有多大&#xff1f; 有时候&#xff0c;你不得不承认&#xff0c;中国人的脑洞有时候真是让人意想不到&#xff1a; 你只知道Excel表格&#xff0c;你也知道中文编程&#xff0c;但是你有没有想过&#xff0c;用Excel和中文编程嫁接起来的话&#xff0c;会衍生出一…

sessionStorage、localStorage、cookie你真的会用么

文章目录 前言会话存储&#xff1a;本地存储&#xff1a; 一、sessionStorage介绍使用演示存储数据到sessionStorage从sessionStorage中获取数据删除sessionStorage中的某个键值对清空sessionStorage中的所有数据从Chrome查看sessionStorage的存储情况 二、localStorage介绍使用…

最新 Adobe Photoshop AI (Beta) 下载安装教程

Adobe最近推出了新的Adobe Photoshop AI&#xff08;Beta&#xff09;&#xff0c;这是一款融合了生成性AI和Adobe Firefly的新功能。 能够根据用户的自然语言提示&#xff0c;创建出极其出色的图像。这些提示可以用来添加内容、删除或替换图像的某部分&#xff0c;甚至扩展图…

Flutter:动画

前言 学习参考&#xff1a;老孟 flutter动画 基本上开发时使用的组件都有其动画&#xff0c;关于动画方面的知识&#xff0c;一般情况很少会用到。因此这里只学习关于动画的基本知识。 AnimationController Flutter中的AnimationController是一个用于控制动画的类。它可以控…

深入理解Qt多线程编程:QThread、QTimer与QAudioOutput的内在联系__Qt 事件循环

深入理解Qt多线程编程&#xff1a;QThread、QTimer与QAudioOutput的内在联系__QObject的主线程的事件循环 1. Qt多线程编程的基础1.1 QObject和线程&#xff08;QObject and Threads&#xff09;1.2 QThread的使用和理解&#xff08;Understanding and Using QThread&#xff0…

ubuntu下安装transition_amr_parser

ubuntu下安装transition_amr_parser transition_amr_parser是IBM公司开源的AMR paraing和AMR text-to-generation工具&#xff0c;在NLP领域中经常会用到&#xff0c;但是这个安装过程中可能会存在很多坑&#xff0c;transition_amr_parser的github主页安装教程不清晰&#xf…

探秘美颜SDK的动态贴纸的技术原理

美颜SDK作为美颜相机的重要组成部分&#xff0c;其动态贴纸技术也是很多用户喜爱的功能之一。本文将探秘美颜SDK的动态贴纸技术&#xff0c;从技术原理、应用场景和未来发展等方面进行分析。 一、技术原理 **1. 人脸识别技术。**在添加动态贴纸时&#xff0c;第一步要做的肯定…

图的导航 - 最短路径算法

一个 恋爱关系图 胡图图love:98于小美 胡图图love:48何壮壮 胡图图love:99小怪 于小美love:10张帅子 何壮壮love:45张帅子 小怪love:100张帅子 胡图图到张帅子的最短路径 确定不是恋爱路径? 算法实现 先看猛料再看是否实现思路 // 定义深度优先搜索状态 struct DepthFirs…

【Android】WMS(六)Surface的创建和操作

Surface的创建流程 在Android系统中每个Activity都有一个独立的画布&#xff08;在应用侧称为Surface,在SurfaceFlinger侧称为Layer&#xff09;&#xff0c; 无论这个Activity安排了多么复杂的view结构&#xff0c;它们最终都是被画在了所属Activity的这块画布上。 1.Surfac…

618数码好物全推荐,几款科技感满满的数码好物分享

6月18日将迎来一年一度的618狂欢购物节&#xff0c;这可谓是一场精彩绝伦的购物盛宴。每年都有诸多品牌参与其中&#xff0c;我们不仅需要应对复杂的折扣&#xff0c;还需要面对眼花缭乱的产品&#xff0c;这让我们不可避免地陷入了“选择困难症”的困扰中。为了让大家在今年61…

【Android】手持设备规格参数

系列文章 【Android】手持设备规格参数 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/130604517 【H5】avalon前端数据双向绑定 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/131067187 【H5】安卓自动更新方案&…

天空卫士在中国数据安全软件市场位列前五

IDC-中国数据安全软件市场份额&#xff0c;2022 数字经济迅速崛起 中央网信办5月份发布的《数字中国发展报告(2022年)》显示&#xff0c;2022年&#xff0c;我国数字经济规模达50.2万亿元&#xff0c;总量稳居世界第二&#xff0c;同比名义增长10.3%&#xff0c;占国内生产总…

C#,码海拾贝(39)——求解“对称正定方程组”的“共轭梯度法”之C#源代码

using System; namespace Zhou.CSharp.Algorithm { /// <summary> /// 求解线性方程组的类 LEquations /// 原作 周长发 /// 改编 深度混淆 /// </summary> public static partial class LEquations { /// <summary> /…

vue3 script setup 获取父组件函数与参数方法

<script setup>真的可以说是一种非常方便的开发方式了 但没有直接性的setup 拿取父组件的参数和函数就会有点问题 其实vue官方给我提供了defineProps和defineEmits这两个方法是官方提供 不需要引入 直接用就好了 例如这里 我们给子组件传了三个参数 :imgSrc“user.images…

scrcpy 快捷键

可以使用键盘和鼠标在 scrcpy 窗口上执行操作 快捷方式。 在下面的列表中&#xff0c;是快捷方式修饰符。默认情况下&#xff0c;它是 &#xff08;左&#xff09;或&#xff08;左&#xff09;。MODAltSuper 可以使用 进行更改。可能的键是 、 、 和 。例如&#xff1a;–sh…

湖南大学OS-2019期末考试解析

【特别注意】 答案来源于wolf以及网络 是我在备考时自己做的&#xff0c;仅供参考&#xff0c;若有不同的地方欢迎讨论。 【试卷评析】 这张卷子有点老了&#xff0c;部分题目可能有用。如果仔细研究应该会有所收获。 【试卷与答案】 一、选择题&#xff08;15%&#xff…

6、JS-AJAX

6.3、AJAX 6.3.1、AJAX概述 传统的web交互是用户触发一个http请求服务器&#xff0c;然后服务器收到之后&#xff0c;在做出响应到用户&#xff0c;并且返回一个新的页面&#xff0c;每当服务器处理客户端提交的请求时&#xff0c;客户都只能空闲等待&#xff0c;并且哪怕只是…

制作一个电商数据可视化大屏无从下手?看这篇!

01 啥叫可视化大屏&#xff1f; 从字面意思就能看出来&#xff0c;可视化大屏就是有个大屏幕。可视化体现在里面的数据都成了图形和图标&#xff0c; 但是静止的图像也不能完全表现出多报表的结果&#xff0c;可视化大屏是将数据通过图形化、可视化的方式展现在大屏幕上的一种…

【深度学习】BERT变种—百度ERNIE 2.0

ERNIE 2.0 提出了一种持续学习的预训练框架&#xff1a;预训练使用了7种任务&#xff0c;而不是一两种简单的任务。不断引入新的预训练任务&#xff0c;让模型可以持续性地学习不同的预训练任务&#xff0c;并且不会遗忘先前学习的知识&#xff0c;以此让模型能够获得更为全面的…

让小白也能看懂,ChatGPT入门级科普“十问十答”

由于现在GPT火热&#xff0c;360老板已经开始总动员. 白领的日常工作肯定是要发生颠覆性变化的。下面我们就通过自问自答的方式带领小白用户了解一下ChatGPT. 1、ChatGPT到底是什么&#xff1f; ChatGPT 是一个由美国人工智能公司 OpenAI 开发的自然语言处理&#xff08;NLP&…