关于代码规范:如何有效的解决代码的圈复杂度

38次阅读

共计 2462 个字符,预计需要花费 7 分钟才能阅读完成。

作者:京东批发 杨学刚

背景介绍

不论小型公司还是大型互联网公司,很多我的项目债台高筑,新性能开发艰难。其中一个很大的起因就是代码简单,可读性差。Sonar 开发团队曾上纲上线的戏称开发人员的 7 宗罪,其中很要害的一条就是“复杂度”。那复杂度有没有一个明确的衡量标准,咱们又如何去解决代码的圈复杂度呢?明天我在这里和大家聊一下。

圈复杂度的计算方法

咱们先来看一下圈复杂度与代码品质以及测试和保护老本之间的一个关系。

咱们能够看到当圈复杂度,在 1 -10 之间的时候,代码是清晰,结构化的。可测试性比拟高,保护老本也比拟低。随着圈复杂度的升高,代码的情况开始好转,当大于 30 的时候,代码曾经逐渐变为不可读,保护老本十分高。

点边计算法

那圈复杂度是如何计算的呢,罕用的第一种办法叫做点边计算法,它圈复杂度的计算形式 V(G) = E − N + 2,咱们用下边图来解释一下这个公式:

其中公式之中的 E 指的是控制流图中边的数量,N 指的是控制流图中的节点数量。这两个图形指的就是控制流图。那咱们能够计算一下,第一个控制流图的圈复杂度是:4-4+2=2.

节点断定法

除此之外圈复杂度还有一种更为直观的计算方法,因为圈复杂度实际上体现了“断定条件”的数量,所以圈复杂度实际上就是等于断定节点的数量再加上 1。它的计算公式为:V (G) = P + 1 其中断定节点 (P) 指的是咱们罕用的分支语句。例如 if 语句、while 语句、case 语句等。

那如何来升高圈复杂度呢?

圈复杂度的罕用解决办法

提炼函数

接下来咱们重点介绍一些升高圈简单的办法,我通过工作中常见的代码,来表述一下,如何去升高复杂度,如果你有更好的办法,也欢送留言跟我交换。在咱们的工作中,做业务零碎的时候,通过异步音讯进行数据传递,是比拟罕用的一种形式,在咱们监听到对端系统的音讯的时候,个别会做这几件事件。判断音讯是否为空 –> 转换音讯为数据传输对象 DTO–> 进一步的判断对象的数据是否非法 –> 进行业务逻辑的解决。这几个典型的步骤,很多童鞋可能用右边图的形式进行解决。这个时候,如果每一个步骤的办法比较复杂的时候,这个总的办法会非常复杂,这个时候,咱们能够通过提炼办法的形式,对高内聚的操作,提炼到一个独立的办法中,来分治复杂性。

应用卫语句

咱们晓得圈复杂度的一个因素就是分支语句多,咱们在写业务代码的时候,常见到这样的一种代码,if-then-else 的层层嵌套。卫语句的准则是,如果某个条件极其常见,就应该独自查看该条件,并在该条件为真时,立即返回。上面是一个生产中的场景,如果记账申请落库胜利后就进行余额的操作,如果不胜利就返回失败后果。因为落库失败是不常见的,所以咱们采纳卫语句的形式,来缩小分支语句。让代码更清晰。

合并条件

常常遇到一种状况,咱们对谬误的解决,须要返回给调用方,外部的错误码,为了不便快读的定位谬误会十分具体,然而对外可能会泛化这种错误码,这个时候咱们能够通过合并条件的形式,简化条件分支,来升高圈复杂度。上面是一个生产中的场景,如果记账失败,则对谬误后果进行包装解决,并返回给调用方。这个时候咱们能够将错误码合并,这里它是合并到 map 中,而后针对这组错误码对立进行了解决。

通过多态形式代替条件式

在咱们开发中,如果是一个平台化的零碎,很多时候,有这样的需要。例如:不同的租户、不同的业务甚至不同的订单类型都会有不同的解决流程。这个时候最简略的形式,就是通过条件分支来进行不同的解决。然而当业务繁多的时候,解决分支会显得凌乱,从而导致圈复杂度的升高,这个时候咱们通过利用多态的形式,能够无效的升高复杂度。咱们看一下下边这段代码,不同的订单类型,应用不同的解决流程,这里他应用了在枚举中实现多态的形式。咱们发现,其实他是实现了工厂模式。

替换算法

简单算法会导致 bug 可能性的减少及可了解性 / 可维护性的升高,如果函数对性能要求不高,提倡应用简单明了的算法。这里我援用了重构中的一个例子,咱们能够一起看一下。这里传入一个人名的数组,如果数组中蕴含指定的名称,就立刻返回名称。

合成条件式

在面对大块头的代码时,你能够通过提炼办法的形式,将它合成为多个办法。依据每个小块代码的用处,命名新的办法名。对于条件逻辑,将每个分支条件分解成新办法能够突出条件逻辑,并更分明的表白每个分支的作用。比方上面的例子中,冬季的时候商品的折扣和非夏天的商品折扣,是不同的计算方法。这个时候,咱们能够把两种算法,提炼到两个不同的办法中.

移除管制标记

有时候咱们会通过管制标记来对循环进行解决,咱们看一下这样的一段常常应用的代码,同一个数组列表中查找邪恶的人,匹配到任意一个邪恶的人后返回。这里 found 是管制标记,咱们可通过下边的形式去掉管制标记,来缩小一层循环,达到削减复杂度的成果。

圈复杂度的思辨

那是不是当咱们检测到圈复杂度高的时候他就肯定简单呢,上面的代码是一个生产上的例子,他通过传入的 MQ 的名字,对 MQ 进行手动的暂停。这个中央实际上是能够通过 mq 的名称,从 spring 的容器中,获取 bean 的。这里的例子次要是让大家看到,尽管,这个分支比拟多,然而这种扁平化的构造可读性还是能够的。不过如果它做的不仅仅是一个暂停的操作,而是一个很简单的操作,这个时候,可能就须要通过提炼办法的形式进行重构。如果提炼办法重构后,这个类还是过长,那就须要咱们通过应用多态的个性,利用工厂模式等形式进行进一步的重构。如果一开始咱们就通过利用一些简单的设计模式进行重构,就会存在适度设计的弊病,使代码更不易于了解。

总结

首先介绍了什么是圈复杂度,而后介绍了解决圈复杂度的几种办法。

通过圈复杂度计算的两种形式咱们能够看到,圈复杂度的外围是分支语句。那解决问题的外围就集中在如何去缩小分支语句。

不过最初咱们也看到了,实际上,只是刻板的应用圈复杂度的算法,去度量一个段代码的清晰度,有时候也是不可取的,所以咱们在重构零碎的时候,能够通过圈复杂度的工具,进行复杂度的统计,而后对复杂度高的代码,具体场景,具体分析。而不能一味的教条。

最初咱们通过思维导图来梳理一下:

正文完
 0