如何衡量一段代码函数的复杂性

最近遇到一个课题 当我拿到一段静态代码,如何更好地衡量出这段源代码的复杂性,也就是说如何从静态(不运行起来地情况下)去衡量一段函数或者代码片段的所含信息和所含结构复杂的程度。

这个其实属于代码度量 领域,跟代码的维护性息息相关。

1 各种度量

通过搜集资料,发现其实从各种角度都可以去衡量代码复杂度,简单例如:

  1. 代码行数:通常情况下,代码行数越多,程序复杂度越高。
  2. 程序结构:程序结构的复杂度可以通过层数、循环、分支等结构来衡量。
  3. 变量的数量和类型:变量的数量和类型越多,程序复杂度越高。
  4. 函数的数量和复杂度:函数的数量和复杂度越高,程序复杂度越高。
  5. 对象的数量和关系:如果程序中使用了面向对象编程,那么对象的数量和关系也是衡量复杂度的一个重要指标。
  6. 控制流程的复杂度:控制流程的复杂度可以通过程序的控制流图来衡量,控制流程的复杂度越高,程序的复杂度也越高。
  7. 数据结构的复杂度:数据结构的复杂度可以通过数据结构的类型、数量、层级等来衡量

然而,我希望能拥有一个公式,将上述的多个项目都用起来,这就引出了 MI 指数( Maintainability),也就是 可维护性指数

2 可维护性指数 MI

MI指数计算代码度量的经典公式,如下所示:

Maintainability Index = 171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code)

其中的三个参数是:

  • Halstead Volume(代码容量)
  • Cyclomatic Complexity(圈复杂度)
  • Lines of Code(代码行数)

值得注意的是,使用此公式意味着值的范围是从 171 到无限负数。

当代码趋向于 0 时,显然很难维护代码,并且值为 0 的代码和为某个负值的代码之间的差异没什么用处。 由于负数的用处很低,并且我们希望让指标尽可能清晰,我们决定将所有 0 或更小的指数都视为 0,然后重新定义 171 或定义成更小的范围,即 0 到 100

出于此原因,公式可以修改为:

Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171)

写成python代码就是

 mi = max(float(0), (171 - 5.20 * np.log(hv) - 0.23 * cc - 16.20 * np.log(lloc)) * 100.0 / 171.0)

3 示例函数

这里写一段计算mi用的核心python函数

注意:前面需要让源代码经过解析器解析,拿到代码行数、条件语句数、操作符、操作数等信息,这里我用的是将代码转成AST抽象语法树再处理)

def mi_val_calcu(lloc, node_list, operator_dict, operands_dict):
    """ calculate MI value of function frags
    	lloc: 逻辑代码行数,一句完整的代码语句,才是一行
    	node_list:通过代码解析器得到的AST的每个结点的列表
    	operator_dict:存储了这段代码里的操作符operator的种类和个数 {'return':1, '+':2,'printf':3,...}
    	operands_dict:存储了这段代码里的操作数operands的种类和个数 {’a‘:1,'0':2,'ans':1,...}
    """
    # 1. 逻辑代码行数 LOC
    # 这里的lloc通过cpp代码的“;”分号来计算
    # [1,...] 使用max是为了避免log(lloc)趋近负无穷
    lloc = max(1, lloc)  
    
    # 2. 圈复杂度 CC 
    # 计算条件节点个数加1, 即 cc = P + 1
    # 这里是在数if  elseif  switch-case的数目
    cc = len([n for n in node_list if n in if_statements]) + 1
    
    # 3. HV体积
    # 计算操作符和操作数
    # n = n1 + n2  Program Vocabulary
    n = len(operator_dict.keys()) + len(operands_dict.keys()) 
    # N = N1 + N2  Program length
    N = sum(operator_dict.values()) + sum(operands_dict.values())
    # Halstead Volume  
    # [1,...] 使用max避免log(lloc)趋近负无穷 
    hv = N * np.log2(n) if n * N != 0 else 1  
    
    # MI 公式 结果范围在(0,100)
    mi = max(float(0), (171 - 5.20 * np.log(hv) - 0.23 * cc - 16.20 * np.log(lloc)) * 100.0 / 171.0)

    return mi

谢谢!

参考资料来源:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值