查看原文
其他

只使用一个指标,引导“祖传”代码的质量改进?

乔梁 持续交付2.0 2019-11-30


盖茨获美国“童子军”最高奖项银牛奖(2010年,新浪科技)



上期我们谈到了“圈复杂度”这个指标。


有人问,这个指标到底是如何定义的?这个指标已经有数十年的历史,但业内知道的人并不多。我上次在某大公司分享的时候,台下100多号开发人员,几乎没有人知道,听说过的人也说不清楚,因为从来都没有认真思考过。


1、McCabe度量标准


想了解“圈复杂度”,需要先了解一下McCabe度量标准。McCabe度量标准是将软件的流程图转化为有向图,然后以图论的知识和计算方法来衡量软件的质量。McCabe复杂度包括圈复杂度(Cyclomatic complexity)、基本复杂度、模块复杂度、设计复杂度和集成复杂度等。控制流程图分析是一个静态的分析过程,它提供静态的度量标准技术,一般主要运用在白盒测试的方法中。控制流图是McCabe复杂度计算的基础。


控制流图的一个重要性质是它的可规约性(reducibility)。如果程序中不存在从循环外跳到循环内的goto语句,那么这个程序对应的控制流图是可规约的(reducible),反之这个控制流图就是不可规约的(irreducible)。因此,模块符合结构化程序设计的准则是控制流图可规约的基础。


2、圈复杂度


程序环路复杂性也即为McCabe复杂性度量,它一般常用圈复杂度来描述,记录为V(G)。它用来衡量一个程序模块所包含的判定结构的复杂程度,数量上表现为独立路径的条数,即合理地预防错误所需测试的最少路径条数,圈复杂度大的程序,说明其代码可能质量低且难于测试和维护。经验表明,程序的可能存在的Bug数和圈复杂度有着很大的相关性。


圈复杂度的计算方法很简单,计算公式为:V(G)=e-n+2。其中,e表示控制流图中边的数量,n表示控制流图中节点的数量。其实,圈复杂度的计算还有更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上1,也即控制流图的区域数,对应的计算公式为:V(G)=区域数=判定节点数+1。


对于多分支的CASE结构或IF-ELSEIF-ELSE结构,统计判定节点的个数时需要特别注意一点,要求必须统计全部实际的判定节点数,也即每个ELSEIF语句,以及每个CASE语句,都应该算为一个判定节点。判定节点在模块的控制流图中很容易被识别出来,所以,针对程序的控制流图计算圈复杂度V(G)时,最好还是采用第一个公式,也即V(G)=e-n+2;而针对模块的控制流图时,可以直接统计判定节点数,这样更为简单。


简单的说,“圈复杂度的数量就是:你的程序中到底有多少条执行路径。


3、如何正确使用圈复杂度


很多相关扫描软件使用“平均圈复杂度”作为定义软件内部质量的一个指标。


我认为,使用“平均圈复杂度”有欠妥当


因为,在一个软件基本上是由大量的圈复杂度较低的函数和少量的圈复杂度较高的函数组成。很多Issue发生于这个高圈复杂度的函数中,或者由其引发。但“平均圈复杂度”会将这些高复杂度的函数合理化(被平均了)。


因此,我们应该使用“超标圈复杂度”来衡量一个软件的复杂程度。


4、什么是超标圈复杂度


超标圈复杂度是指超出某一阈值的所有函数的圈复杂度总数(或者单个函数的圈复杂度减去该阈值,结果大于0的所有数字之和)。例如,

  • 可以设定Java程序的阈值为20。

  • 有三个函数A、B、C,其圈复杂度分别为 19,27,25。

  • 它们与阈值的差值分别为 -1 , 7 和 5。

  • 那么,复杂度超标的函数只有B和C。

  • 超标圈复杂度为 7+5 = 12。


5、如何使用超标圈复杂度?


“祖传”代码的超标圈复杂度通过比较高,改进的方法也很简单,使用“童子军营地原则”。


即:在每次提交代码之前,都计算代码库的超标圈复杂度。如果新的超标圈复杂度超过上一次计算的圈复杂度,就禁止提交代码。


这样,可以保持代码至少不会变得更差~





    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存