常见代码复杂度解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 代码质量评价维度,很多都是些主观性的评价维度,需要有专门的人员去查看评判代码,对于审核的人员代码能力要求比较高,而且有时候往往不同的人审核会得出不同的结论,会有争议。然而也有些对代码客观的分析方式可以帮助我们识别代码质量,节省大量人力去分析代码。比如代码复杂度的分析。

代码质量评价维度,很多都是些主观性的评价维度,需要有专门的人员去查看评判代码,对于审核的人员代码能力要求比较高,而且有时候往往不同的人审核会得出不同的结论,会有争议。然而也有些对代码客观的分析方式可以帮助我们识别代码质量,节省大量人力去分析代码。比如代码复杂度的分析。

判断代码复杂度有多种不同的方式,下面介绍一些其中比较重要的判断代码复杂度的方式:

一 圈复杂度(Cyclomatic complexity,V(G))

1.1 圈复杂度介绍

圈复杂度(Cyclomatic complexity)也称为条件复杂度或循环复杂度,是一种软件度量,是由Thomas J. McCabe在1976年提出,用来表示程序的复杂度,其符号为VG或是M。圈复杂度是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,

即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。独立路径组成的集合称为基本路径集合,独立路径数就是指基本路径集合中路径的数量。

基本路径集合不是唯一的,独立路径数也就不唯一。因此,圈复杂度是最大独立路径数。

1.2 控制流图

圈复杂度的计算一般都是需要根据控制流图来进行计算,控制流图(Control Flow Graph, CFG)是一个过程或程序的抽象表现,代表了一个程序执行过程中会遍历到的所有路径。流图中有3个概念:节点、边、区域。流图中的圆称为节点,一个节点代表一条或多条语句;

流图中的箭头线称为边,代表控制流;由边和节点围成的部分称为区域。

下面是从网上借鉴了一个程序流程图转换成控制流图的示意图,如下所示:

其中,2、3和4、5节点都是对应程序流程图中的两个语句,但它们都是在同一条路径上,所以在流图中只是一个节点。

1.3 圈复杂度计算

圈复杂度计算一般有以下几计算方式,下面根据流图再来看一下圈复杂度计算方式:

1.通过流图中点边进行计算:  V(G) = E−N+2

其中E是流图中边的条数,N是流图中节点数。所以使用这种方式计算,上图的V(G) =11 - 9 +2 = 4

2. 通过流图中判定节点计算: V(G) = P+1

其中P是流图中判定节点的数目。在流图中当一个节点分出两条或多条边指向其他节点时,这个节点就是一个判定节点。上面流图中p为3 所以上图的 V(G) = 3+1 = 4

3. 通过流图中区域计算: V(G) = R

其中R是区域数。计算区域数时不仅包括由边和节点围起来的区域,也包括图外部未被围起来的那个区域。上面流图中R为4 所以上图的V(G) = 4

4. 通过条件表达式计算:

上面的常规计算方法需要画出控制流图有些麻烦,我们可以粗暴一点根据判定条件节点计算法粗略估计,其实也就是上图中的第二个计算方式。

一般圈复杂度中的判定节点是指下面这些条件语句:

条件语句: if语句、while语句(包含do...while...语句)、for语句(包含foreach语句)、switch case语句。

条件表达式(二元或多元):&& 表达式、|| 表达式、三元运算符。

注: 这里强调下圈复杂度是不包括try/catch的,网上凡是带有try/catch的文章,写的都是错的。

我们计算时,正常的顺序代码,圈复杂度加1,然后遇到上面的判定节点就加1。比如现在有段代码,先把圈复杂度初始为1,然后遇到一个if语句就加1,遇到一个for语句就加1 ,那这段代码圈复杂度就是3。

需要注意的是,判定节点计算法对于多分支的 case 结构或多个 if - else 结构,每个case或者 else 都需要统计。


圈复杂度是合理的预防错误所需测试的最少路径条数。圈复杂度大说明程序代码的判断逻辑复杂,程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。

二 认知复杂度(Cognitive Complexity, Cogc)

前面介绍了圈复杂度,但是圈复杂度是有一些缺陷的,以下引用自sonar官方白皮书翻译:

Thomas J. McCabe 的圈复杂度长期以来一直是衡量方法控制流复杂度的事实标准。它最初的目的是“识别难以测试或维护的软件模块”[1],但虽然它准确计算了完全覆盖一个方法所需的最少测试用例数,但它并不是一个令人满意的可理解性度量。这是因为具有相同圈复杂度的方法不一定给维护者带来相同的难度,导致测量通过高估某些结构而低估其他结构的“假象”。

同时,圈复杂度不再是全面的。它于 1976 年在 Fortran 环境中制定,不包括现代语言结构,如try/catch和lambdas。最后,因为每个方法的最小圈复杂度分数为 1,所以不可能知道任何具有高聚合圈复杂度的给定类是属于大型的、易于维护的类还是具有复杂控制流的小类。除了类级别之外,人们普遍认为应用程序的圈复杂度分数与其代码行总数相关。换句话说,圈复杂度在方法级别之上几乎没有用处。

简单来说就是,圈复杂度已经有些过时并不能涵盖现有程序语言结构,而且也并不能衡量代码的可理解性,那么有什么方式解决圈复杂度的这些问题呢?

针对这点,我们可以使用认知复杂度:

为了解决这些问题,我们制定了认知复杂度(Cognitive Complexity)用于解决现代语言结构,并在类和应用程序级别产生有意义的值。更重要的是,它脱离了基于数学模型评估代码的实践,因此它可以产生与程序员对理解这些流程所需的心理或认知相对应的控制流评估。

认知复杂度打破了使用数学模型来评估软件可维护性的做法。它使用人类判断来评估应该如何计算结构,并决定应该将什么添加到整个模型中。因此,它产生的方法复杂性分数让程序员觉得比以前的模型更公平的可理解性相对评估。此外,由于认知复杂度

不收取方法的“入门成本”,因此它不仅在方法级别,而且在类和应用程序级别都会产生更公平的相对评估。

如果你需要识别代码的理解难度,那就应该使用认知复杂度。比如下面我引用下sonar官方的一个例子,下面这段代码,圈复杂度都是4,但是我们很明显的可以看出来,两边代码的可理解性是不相同的

我们再来看看如果用认知复杂度,结果是什么

通过这个例子,可以比较明显的看出,相比圈复杂度,认知复杂度更适合我们对代码可理解性的进行量化评估。

我们再来看下认知复杂是怎么计算的,下面引用下认知复杂度基本规则评估方式:

认知复杂度分数根据三个基本规则进行评估:

1. 忽略允许将多个语句易于理解地简写成一个的情况。

2. 在代码线性流程中的每一次中断都增加(+1)(复杂度)。

3. 断流结构嵌套时增加(复杂度)


简单来说,认知复杂度中,多个语句简写为一个语句,复杂度不会增加,如果线性代码逻辑中,逻辑被一些条件语句或条件表达式打断一次,复杂度就会加一,如果打断逻辑的语句是一个嵌套的语句,那么复杂度需要额外再加上嵌套所带来的复杂度。

下面再举个例子说明下:

认知复杂度更多相关内容,可以参考sonar官方白皮书,详见以下链接:

https://www.sonarsource.com/docs/CognitiveComplexity.pdf

三 基本复杂度(Essential Complexity, ev(G))

基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。

将圈复杂度图中的结构化部分简化成一个点,计算简化以后流程图的圈复杂度就是基本复杂度。

四 模块设计复杂度(Module Design Complexity, iv(G))

     模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。

      除了上面介绍的一些复杂度之外,还有些其他复杂度比如设计复杂度,集成复杂度,规范化复杂度等等,就不再展开。

五 复杂度计算工具

上面复杂度计算都有些麻烦,平时如果自己去计算工作量不小,不过目前都已经有专门的工具用来计算这些复杂度,比如Metrics Reloaded,SonarLint等等,这些工具都有相应的idea插件,平时开发时使用这些工具就可以做到代码实时检测分析代码复杂度,可以有效

的帮我们识别代码复杂度。至于使用方法,这里也不再展开了。

下面这个是常见的复杂度标准,以供参考:

六 如何降低复杂度

降低代码复杂度的方式比较多,复杂度主要是条件语句和条件表达式引起的,所以降低复杂度的方法,也就是减少这些语句,一般有以下常用方法:


1.将方法拆分,抽出部分独立逻辑组成小的方法。

2.尽量简化、合并条件表达式,比如合并多个同义的if语句,使用多元运算符代替if else等。

3.减少else,比如可以调整表达式顺序,使用卫语句等等

4.使用lambda代替部分逻辑。lambda虽然也会增加复杂度,但有些场景下会比条件表达式复杂度低

相关文章
|
18天前
|
敏捷开发 安全 测试技术
软件测试的艺术:从代码到用户体验的全方位解析
本文将深入探讨软件测试的重要性和实施策略,通过分析不同类型的测试方法和工具,展示如何有效地提升软件质量和用户满意度。我们将从单元测试、集成测试到性能测试等多个角度出发,详细解释每种测试方法的实施步骤和最佳实践。此外,文章还将讨论如何通过持续集成和自动化测试来优化测试流程,以及如何建立有效的测试团队来应对快速变化的市场需求。通过实际案例的分析,本文旨在为读者提供一套系统而实用的软件测试策略,帮助读者在软件开发过程中做出更明智的决策。
|
8天前
|
SQL 人工智能 机器人
遇到的代码部份解析
/ 模拟后端返回的数据
13 0
|
9天前
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。
|
2月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
40 1
|
2月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
68 0
|
2月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
48 0
|
2月前
|
开发者 图形学 C#
深度解密:Unity游戏开发中的动画艺术——Mecanim状态机如何让游戏角色栩栩如生:从基础设置到高级状态切换的全面指南,助你打造流畅自然的游戏动画体验
【8月更文挑战第31天】Unity动画系统是游戏开发的关键部分,尤其适用于复杂角色动画。本文通过具体案例讲解Mecanim动画状态机的使用方法及原理。我们创建一个游戏角色并设计行走、奔跑和攻击动画,详细介绍动画状态机设置及脚本控制。首先导入动画资源并添加Animator组件,然后创建Animator Controller并设置状态间的转换条件。通过编写C#脚本(如PlayerMovement)控制动画状态切换,实现基于玩家输入的动画过渡。此方法不仅适用于游戏角色,还可用于任何需动态动画响应的对象,增强游戏的真实感与互动性。
58 0
|
2月前
|
图形学 开发者
【Unity光照艺术手册】掌握这些技巧,让你的游戏场景瞬间提升档次:从基础光源到全局光照,打造24小时不间断的视觉盛宴——如何运用代码与烘焙创造逼真光影效果全解析
【8月更文挑战第31天】在Unity中,合理的光照与阴影设置对于打造逼真环境至关重要。本文介绍Unity支持的多种光源类型,如定向光、点光源、聚光灯等,并通过具体示例展示如何使用着色器和脚本控制光照强度,模拟不同时间段的光照变化。此外,还介绍了动态和静态阴影、全局光照及光照探针等高级功能,帮助开发者创造丰富多样的光影效果,提升游戏沉浸感。
39 0
|
2月前
|
开发者 图形学 UED
深度解析Unity游戏开发中的性能瓶颈与优化方案:从资源管理到代码执行,全方位提升你的游戏流畅度,让玩家体验飞跃性的顺滑——不止是技巧,更是艺术的追求
【8月更文挑战第31天】《Unity性能优化实战:让你的游戏流畅如飞》详细介绍了Unity游戏性能优化的关键技巧,涵盖资源管理、代码优化、场景管理和内存管理等方面。通过具体示例,如纹理打包、异步加载、协程使用及LOD技术,帮助开发者打造高效流畅的游戏体验。文中提供了实用代码片段,助力减少内存消耗、提升渲染效率,确保游戏运行丝滑顺畅。性能优化是一个持续过程,需不断测试调整以达最佳效果。
62 0
|
2月前
|
开发者 C# Windows
WPF与游戏开发:当桌面应用遇见游戏梦想——利用Windows Presentation Foundation打造属于你的2D游戏世界,从环境搭建到代码实践全面解析新兴开发路径
【8月更文挑战第31天】随着游戏开发技术的进步,WPF作为.NET Framework的一部分,凭借其图形渲染能力和灵活的UI设计,成为桌面游戏开发的新选择。本文通过技术综述和示例代码,介绍如何利用WPF进行游戏开发。首先确保安装最新版Visual Studio并创建WPF项目。接着,通过XAML设计游戏界面,并在C#中实现游戏逻辑,如玩家控制和障碍物碰撞检测。示例展示了创建基本2D游戏的过程,包括角色移动和碰撞处理。通过本文,WPF开发者可更好地理解并应用游戏开发技术,创造吸引人的桌面游戏。
98 0

热门文章

最新文章

推荐镜像

更多
下一篇
无影云桌面