代码质量管控——程序复杂度
1. 程序复杂度
一个软件的复杂度主要由构成软件模块程序的复杂度体现,程序的复杂度主要指的是模块程序之间的复杂性。常用衡量程序复杂性的的方法有:
【1】代码行度量法
【2】T.McCabe度量法,即圈复杂度
【3】Halstead 软件科学法,即Halstead 复杂度
1.1 衡量程序复杂度意义
程序复杂度的意义不言而喻,对于程序员或者项目本身都具有很大意义。事实证明,软件出现bug的概率和程序的数量、复杂度等成正相关,这是统计学和概率论原理得出,而不是程序员的编码水平问题,当然也与编码水平有一定关系。因此,明确程序的复杂度,对于项目进度、软件可靠性、程序质量等等提升的同时,并能计划安排测试、优化代码工作量。
【1】提升代码质量,降低bug的概率
【2】提升程序员的编码水平
【3】项目规划,针对不同复杂度的模块作出不同的方法,如模块重构或者优化
【4】错误率预测,定位测试重点,如对复杂度高的模块增加测试手段
2. 代码行度量法
代码行方法度量是一种最简单的方法,是一种很容易让人理解的,该方法认为,代码行越多,软件越容易产生漏洞;例如,在初学编程时,很大一部人包括我自己都是几百上千行代码塞在一个main函数里面,功能和逻辑耦合在一起,不便于阅读和维护。程序复杂性随着程序规模的增加不均衡的增长,以及控制程序规模的方法最好是采用分而治之的办法。
代码行度量法只是一个简单的、估计得很粗糙的方法。代码行度量指的是代码中的估算行数,并不是源代码文件中的确切行数,该计算不包括空白行、注释、括号以及成员、类型和命名空间的声明。行数估计过高可能表示某个对象或方法正在尝试执行过于复杂的任务,表示该实现方式不便于维护,可考虑对任务处理代码进行分解。
3. Halstead 软件科学法(Halstead 复杂度)
3.1 什么是Halstead 复杂度
Halstead 复杂度 (Maurice H. Halstead, 1977) 是软件科学提出的第一个计算机软件的分析“定律”,用以确定计算机软件开发中的一些定量规律。Halstead 复杂度是根据程序中语句行的操作符和操作数的数量来计算得出的。操作符和操作数与程序复杂度成正相关,即操作符和操作数的量越大,程序结构就越复杂。
3.2 Halstead 复杂度要素
【1】操作符,编程语言保留字(int、if)、函数调用、运算符(+、-)、分隔符(分号、括号)等
【2】操作数,常数、变量等
3.3 Halstead 复杂度计算
关于Halstead复杂度,有多个术语来衡量。用 n1 表示程序中不同的操作符个数,n2 表示程序中不同的操作数个数,N1 表示程序中出现的操作符总数,N2表示程序中出现的操作数总数。则Halstead复杂度几个标准计算如下。
名称 | 计算公式 | 含义 |
---|---|---|
程序词汇表长度(n) | n=n1+n2 | |
程序长度(N) | N=N1+N2 | |
程序预测长度(N^) | N^=n1 log2 n1 + n2 log2 n2 | 预估程序的实际长度 |
程序体积或容量(V) | V = Nlog2 (n) | 程序在词汇上的复杂性 |
程序级别(L^) | L^ = (2/n1) × (n2 /N2) | 程序最紧凑形式的程序量与实际程序量比值,反映了程序的效率 |
程序难度(D) | D=1/L^ | 实现算法的困难程度 |
编程工作量(L^ ) | Lʹ = L^ × L^ × V | |
编程时间( T^) | T^ = E/(S × f) | |
平均语句(X) | X=N/语句数 | |
程序错误数预测值(B) | B = V/3000 |
3.4 Halstead 复杂度优缺点
【1】优点
- 计算方法简单
- 不需要对程序进行深层次的分析,就能够预测程序错误率,预测维护工作量
- 复杂度计算与所用的高级程序设计语言类型无关
【2】缺点
- 仅仅考虑程序数据量和程序体积,未考虑到程序控制流的情况
- 不能从根本上反映程序复杂性
4. T.McCabe度量法(圈复杂度)
4.1 什么是圈复杂度
圈复杂度(Cyclomatic Complexity)是一种代码复杂度的衡量标准,由 Thomas McCabe 于 1976年定义。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和维护。程序的可能错误和高的圈复杂度有着很大关系。[百度百科]
4.2 圈复杂度计算
圈复杂度主要与分支语句(if、else、switch )的数量成正相关,即程序分支语句越多,圈复杂度越大,也就是程序复杂度越大。如果一段源码中不包含控制流语句(条件或决策点),即此段代码只有一条路径,那么代码的圈复杂度为1。如果一段代码中仅包含一个if语句,且if语句仅有一个条件,那么这段代码的圈复杂度为2;包含两个嵌套的if语句,或是一个if语句有两个条件的代码块的圈复杂度为3。
圈复杂度的计算有三种方式,这三种方式根据程序流图进行计算,是很容易理解的。以下图程序流图为例。
【1】V(G) = e - n + 2p
其中,e 表示控制流图中的边数量(即程序顺序结构的部分),n 表示控制流图中的判断节点数量(执行节点),控制流图是联通的,p取值为1。所以上图的圈复杂度为:V(G)=7-6+2=3。
【2】V(G) = n+1
其中,n表示控制流图判断节点数量。则上图圈复杂度为:V(G)=2+1=3。
【3】V(G)=s
其中,s表示程序流图将平面划分的区域数量,上图平面区域数量为3(内部2个,外围1个),则V(G)=3。
4.3 圈复杂度参考标准
McCabe&Associates 公司建议尽可能使 V(G) ≤ 10。国家标准技术研究所(NIST)认为在一些特定情形下,模块圈复杂度上限放宽到15。
- 0≤ V(G) ≤ 10,代码质量不错
- 11 ≤ V(G) ≤ 15,可能存在需要拆分的代码,应当尽可能考虑重构
- V(G) ≥ 16,代码必须进行重构
5. 总结
在初入职场前期或者公司起步阶段,相信很多人在代码质量这块关注比较少,几乎是本着“能用”的目标进行,一方面是项目经历少、缺乏职场经验,没有这方面的意识;另一方面是公司追求快速开发,产品快速上市。然而,慢慢地,回过头再看曾经自己写过的代码,可能自己都会觉得“惨不忍睹”。所以,在编码前期、编码过程,可以参考相关理论,如上面的程序复杂度衡量方法,进行约束自己的编码,提高程序的质量。实践大于理论,但实践源于理论,程序员不能一直以“游击战”的方式编码,要逐步回归理论,形成一个标准化、正规化的程序设计思维和编码习惯。