降低代码圈复杂度优化技巧

简介: 降低代码圈复杂度优化技巧

当一个项目经过持续迭代,不断增加功能,逐渐变成一个复杂的产品时,新功能的开发变得相对困难。其中一个很大的原因是代码复杂度高,导致可维护性和可读性都很差。本文将从前端JavaScript的角度出发,介绍一些有效的方法和技巧来优化前端代码的圈复杂度

什么是圈复杂度

圈复杂度的计算基于程序中的决策结构,如条件语句(if语句)、循环语句(for、while语句)、分支语句(switch语句)等。每当程序流程图中增加一个决策点,圈复杂度就会增加1。圈复杂度的值越高,表示代码的复杂性越大,代码的可读性、可测性和可维护性也会受到影响。

通常情况下,圈复杂度的推荐值应该在1到10之间。超过10的代码模块可能需要进行重构,以提高代码的可理解性和可测试性,并降低引入错误的风险。

image.png

辅助工具

VScode插件Code Metrics

VScode插件Code Metrics可以帮助我们快速发现那些需要优化复杂度的代码,安装好插件后如下图所示,在代码上方会出现对应的复杂度值,根据值的大小可以看出哪些代码是急需优化提升可读性。

image.png

鼠标点击所提示复杂度数值的地方可以看到具体是哪些代码影响了复杂度,可以进行针对性的优化。

image.png

eslint检查

可以使用 eslint 帮助检查代码的圈复杂度,当超出了某个值就会报错。

rules: {
      complexity: [
        'error',
        {
          max: 10
        }
      ]
    }

如上面的配置就是超出了 10 就会出现报错信息。

圈复杂度的常用解决方法

函数拆分和重构,单一职责

较高的圈复杂度往往意味着函数或方法内部有过多的决策路径。通过将复杂的函数分解成多个小而清晰的函数,可以降低每个函数的圈复杂度,并使代码更易于理解和维护。拆分函数时,可根据功能模块或责任进行分类,确保每个函数只负责一项具体的任务。

优化前代码:

function handle(arr) {
    // 去重
    let _arr=[],_arrIds=[];
    for(let i=0;i<arr.length;i++){
        if(_arrIds.indexOf(arr[i].id)===-1){
            _arrIds.push(arr[i].id);
            _arr.push(arr[i]);
        }
    }
    // 替换
    _arr.map(item=>{
        for(let key in item){
            if(item[key]===''){
                item[key]='--';
            }
        }
    });
    // 排序
    _arr.sort((item1,item2)=>item1.id-item2.id);
    return _arr;
}

优化后代码:

function removeDuplicates(arr) {
  const uniqueArr = [];
  const uniqueIds = [];
  for(let i = 0; i < arr.length; i++) {
    if(uniqueIds.indexOf(arr[i].id) === -1) {
      uniqueIds.push(arr[i].id);
      uniqueArr.push(arr[i]);
    }
  }
  return uniqueArr;
}
function replaceEmptyValues(arr) {
  const processedArr = arr.map(item => {
    for(let key in item) {
      if(item[key] === '') {
        item[key] = '--';
      }
    }
    return item;
  });
  return processedArr;
}
function sortById(arr) {
  const sortedArr = arr.sort((item1, item2) => item1.id - item2.id);
  return sortedArr;
}
function handle(arr) {
  const uniqueArr = removeDuplicates(arr);
  const processedArr = replaceEmptyValues(uniqueArr);
  const sortedArr = sortById(processedArr);
  return sortedArr;
}

以上将原始函数拆分成了三个函数。removeDuplicates 函数用于去除数组中的重复元素,replaceEmptyValues 函数用于遍历替换空值,sortById 函数用于根据 id 进行排序。每个函数都只负责一个明确的职责。

卫语句可以减少分支

对输入条件进行多重判断时,使用卫语句可以减少分支语句的使用,提高代码的可读性和可维护性。

// 优化前
function calculateScore(score) {
  if (score < 0) {
    return "Invalid score";
  } else if (score < 50) {
    return "Fail";
  } else if (score < 70) {
    return "Pass";
  } else if (score < 90) {
    return "Good";
  } else {
    return "Excellent";
  }
}
// 优化后
function calculateScore(score) {
  if (score < 0) {
    return "Invalid score";
  }
  if (score < 50) {
    return "Fail";
  }
  if (score < 70) {
    return "Pass";
  }
  if (score < 90) {
    return "Good";
  }
  return "Excellent";
}

通过使用卫语句,我们将每个条件判断独立出来,避免了嵌套的分支语句。这种优化方式使得代码更加清晰,每个条件判断都独立成为一个逻辑块,并且消除了使用 else 的需要。这样做不仅提高了代码的可读性,还方便了后续对每个条件判断的修改和维护。

简化条件表达式

有相同逻辑代码进行条件合并输出,减少条件判断代码,提升可读性。

// 优化前
function a (num) {
    if (num === 0) {
        return 0;
    } else if (num === 1) {
        return 1;
    } else if (num === 2) {
        return 2;
    } else {
        return 3;
    }
}
// 优化后
function a (num) {
    if ([0, 1, 2].indexOf(num) > -1) {
        return num;
    } else {
        return 3;
    }
}
---
// 优化前
function a() {
  if (this.a == 0) return;
  if (!this.b) return;
  ...
}
// 优化后
function a() {
  if (this.a == 0 || !this.b) return;
  ...
}
---
// 优化前
function a (type) {
    if (type === 'a') {
        return 'String';
    } else if (type === 'b') {
        return 'Number';
    } else if (type === 'c') {
        return 'Object';
    }
}
// 优化后
function a (type) {
    let obj = {
        'a': 'String',
        'b': 'Number',
        'c': 'Object'
    };
    return obj[type];
}

表达式逻辑优化

逻辑计算也会增加圈复杂度,优化一些结构复杂的逻辑表达式,减少不必要的逻辑判断,也将一定程度上降低圈复杂度。

// 优化前
a && b || a && c
// 优化后
a && (b || c)

通过多态方式替代条件式。

通过多态方式替代条件式是一种优化技巧,多态允许我们根据不同的类型执行不同的操作,而不需要使用复杂的条件判断逻辑。

优化前的代码:

class Shape {
  constructor(type) {
    this.type = type;
  }
  calculateArea() {
    if (this.type === "circle") {
      // 计算圆形的面积
    } else if (this.type === "rectangle") {
      // 计算矩形的面积
    } else if (this.type === "triangle") {
      // 计算三角形的面积
    }
  }
}

优化后的代码:

class Shape {
  calculateArea() {
    throw new Error("calculateArea() method must be implemented");
  }
}
class Circle extends Shape {
  calculateArea() {
    // 计算圆形的面积
  }
}
class Rectangle extends Shape {
  calculateArea() {
    // 计算矩形的面积
  }
}
class Triangle extends Shape {
  calculateArea() {
    // 计算三角形的面积
  }
}

使用多态的方式,我们可以通过调用相应对象的calculateArea方法来执行特定形状的面积计算,而无需使用复杂的条件判断逻辑。

替换算法,优化复杂度

当发现某个算法的时间复杂度较高时,可以考虑替换为一个具有更优时间复杂度的算法,以提高代码的性能。

// 优化前
function findDuplicates(nums) {
  let duplicates = [];
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] === nums[j]) {
        duplicates.push(nums[i]);
      }
    }
  }
  return duplicates;
}
// 优化后
function findDuplicates(nums) {
  let freq = {};
  let duplicates = [];
  for (let num of nums) {
    if (freq[num]) {
      duplicates.push(num);
    } else {
      freq[num] = true;
    }
  }
  return duplicates;
}

需要注意的是,优化算法并不总是适用于所有情况。在选择替代算法时,应该综合考虑数据规模、特定问题的特性以及算法的复杂度等因素。

分解条件式,拆分函数

当遇到复杂的条件判断式或函数时,可以考虑将其分解为更小的部分,以提高代码的可读性和维护性。

优化前代码:

function calculateScore(player) {
  if (player.score >= 100 && player.level === "expert") {
    return player.score * 2;
  } else if (player.score >= 50 || player.level === "intermediate") {
    return player.score * 1.5;
  } else {
    return player.score;
  }
}

优化后代码:

function hasHighScore(player) {
  return player.score >= 100 && player.level === "expert";
}
function hasIntermediateScore(player) {
  return player.score >= 50 || player.level === "intermediate";
}
function calculateScore(player) {
  if (hasHighScore(player)) {
    return player.score * 2;
  } else if (hasIntermediateScore(player)) {
    return player.score * 1.5;
  } else {
    return player.score;
  }
}

将原始的复杂条件判断式拆分成了两个独立的函数:hasHighScorehasIntermediateScore。这样calculateScore函数中的条件判断变得更加清晰和可读。通过分解条件式和拆分函数,我们可以提高代码的可读性、可维护性和重用性。

减少return出现

当前大多数圈复杂度计算工具对return个数也进行计算,如果要针对这些工具衡量规则进行优化,减少return语句个数也为一种方式。

// 优化前
function a(){
    const value = getSomething();
    if(value) {
        return true;
    } else {
        return false;
    }
}
// 优化后
function a() {
    return getSomething();
}

移除控制标记,减少变量

移除控制标记可以使代码更加简洁、可读性更高,并且减少了不必要的变量使用。

优化前的代码:

function findFirstPositive(numbers) {
  let found = false;
  let firstPositive = null;
  for (let num of numbers) {
    if (num > 0) {
      found = true;
      firstPositive = num;
      break;
    }
  }
  if (found) {
    return firstPositive;
  } else {
    return -1;
  }
}

优化后的代码:

function findFirstPositive(numbers) {
  for (let num of numbers) {
    if (num > 0) {
      return num;
    }
  }
  return -1;
}

在优化后的代码中,我们直接在找到第一个正数后立即返回结果,而无需使用控制标记和额外的变量。如果遍历完整个数组后仍未找到正数,则返回-1。

最后

如果只是刻板的使用圈复杂度的算法去衡量一段代码的清晰度,这并不可取。在重构系统时,我们可以使用代码圈复杂度工具来统计代码的复杂度,并对复杂度较高的代码进行具体的场景分析。但不是说一定要将复杂度优化到某种程度,应该根据实际的业务情况做出优化决策。


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

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

目录
相关文章
|
5月前
codereview开发问题之降低代码复杂度问题如何解决
codereview开发问题之降低代码复杂度问题如何解决
|
4月前
|
开发者 C# Android开发
震惊!Xamarin 跨平台开发优势满满却也挑战重重,代码复用、熟悉语言与性能优势并存,学习曲线与差异处理何解?
【8月更文挑战第31天】Xamarin 与 C# 结合,为移动应用开发带来高效跨平台解决方案,使用单一语言和框架即可构建 iOS、Android 和 Windows 原生应用。本文通过问答形式探讨 Xamarin 和 C# 如何塑造移动开发的未来,并通过示例代码展示其实际应用。Xamarin 和 C# 的组合不仅提高了开发效率,还支持最新的移动平台功能,帮助开发者应对未来挑战,如物联网、人工智能和增强现实等领域的需求。
56 0
|
5月前
|
消息中间件 缓存 架构师
对抗软件复杂度问题之降低代码的复杂度,如何解决
对抗软件复杂度问题之降低代码的复杂度,如何解决
|
7月前
|
数据管理 测试技术 持续交付
构建高效微服务架构:策略与实践代码之美:简洁性与可读性的平衡艺术
【5月更文挑战第27天】在现代软件开发中,微服务架构已成为构建可扩展、灵活且容错的系统的首选方法。本文将探讨构建高效微服务架构的关键策略,包括服务划分、通信机制、数据管理以及持续集成与部署。通过实际案例分析,我们将讨论如何在实践中应用这些策略,以提高系统的性能和可靠性。 【5月更文挑战第27天】在软件开发的世界中,编写出既简洁又具有高可读性的代码是一种艺术。本文将探讨如何在保持代码简洁的同时,不牺牲其可读性和可维护性。我们将深入分析几个关键原则和实践技巧,并配以示例来阐明如何实现这种平衡。文章的目标是为开发者提供实用的指导,帮助他们在编码时做出明智的决策,以提升代码质量。
|
Cloud Native 安全 Java
代码圈复杂度治理小结
我们一直在说系统很复杂,那到底什么是系统复杂度呢?作为团队的稳定性底盘负责人,也经常和大家探讨为什么会因为圈复杂度高而被扣分。那么,怎么才能写的一手可读,可扩展,可维护的好代码?本文作者尝试结合在团队内部的实践,分享下过程中心得。
代码圈复杂度治理小结
|
人工智能 安全 搜索推荐
如果你在选型低代码平台,可以从这5个角度去分析抉择
如果你在选型低代码平台,可以从这5个角度去分析抉择
147 0
【C#编程最佳实践 十一】降低圈复杂度最佳实践
【C#编程最佳实践 十一】降低圈复杂度最佳实践
139 0
|
XML 存储 设计模式
Kotlin 源码 | 降低代码复杂度的法宝
Kotlin 源码 | 降低代码复杂度的法宝
93 0
|
设计模式 XML 数据可视化
降低前端业务复杂度新视角:状态机范式
无论做业务需求还是做平台需求的同学,随着需求的不断迭代,通常都会出现逻辑复杂、状态混乱的现象,维护和新增功能的成本也变的十分巨大,苦不堪言。下图用需求、业务代码、测试代码做对比:
338 0
降低前端业务复杂度新视角:状态机范式
想学习UI设计,但是审美能力很差怎么办?
最近很多学员问我,说自己审美能力差,还能不能学习UI设计。针对这个问题我就来和大家一起探讨一下,如何提升自己的审美能力。 其实审美,并不是天生就有的,审美全是后天培养的,不管是设计,还是其他的一些事情,对于美与丑,都是后天的学习与指导产生的,也就是说这都是从小就开始养成的,从小就开始培养的,就跟小时候大人教我们那些事物是好的,那些事物是坏的,是一样的道理。
1044 0