软件开发始于需求却不能终于需求,我们在满足需求的同时,还需要考虑代码本身的质量,包括可读性、可维护性、可扩展性、可移植性、安全性、高效性等。
因为,“屎山代码”将是整个团队的梦靥。
上一篇文章《汽车软件单元测试的要点与意义》提到了诸如静态分析与覆盖度测试的一些代码评价手段,这一篇讲一些具体指标。
这些指标主要来自于ASIL D软件的实践经验。
1
每个函数的语句数
该指标是指函数内部语句的个数,是一种基础的代码复杂度度量方式。在多数语言中,我们可以使用工具自动计算语句个数。
常见语句包含以下类型:
以分号(;)结尾的简单语句
if语句
for语句
while语句
do语句
switch语句
break语句
continue语句
return语句
goto语句
语句个数应尽量维持在10~20,最多不要超过50。
2
return语句的数量
为了提高代码的可读性,我们最好遵循“单一出口原则”,也就是尽量保证一个函数只有一个出口点(函数结束执行的地方)。
出口点通常是return语句,所以我们也建议尽量减少其数量,比如,一个函数的return维持在1~2个之内。
3
代码行长度
写文章要短句,是为了便于阅读。代码也是一样,太长的代码行会明显增加阅读代码的难度,很现实的问题是,需要开发人员左右滚动屏幕。
在保证合理的逻辑、换行和缩进的前提下,要尽可能将长代码拆分。通常,低于160个字符的代码行可以认为是一个合理目标。
4
圈复杂度
圈复杂度是指通过源代码线性独立路径的个数,也是用来衡量代码复杂度。
如何计算呢?我们可以通过以下3个代码控制流图来看。
一种计算公式为,圈复杂度M=控制流图边数E-节点N+2
故,
图1:M=1-2+2=1,即无判定节点,圈复杂度为1。
图2:M=4-4+2=2,即一个判定节点,圈复杂度为2。
图3:M=7-6+2=3,即两个判定节点,圈复杂度为3。
除了评价本身代码判定逻辑的复杂性之外,圈复杂度还能够用来确定最少需要多少个测试用例来满足分支和路径的覆盖度。
一般经验是,圈复杂度应小于10,以达到较好的可测性。
5
非循环路径数
这个指标也被称为NPATH,是指通过软件所有可能路径的数量。其中,循环中的循环(for, while, do-while)只访问一次。
因此,NPATH也给出了达到路径覆盖所需的测试用例的最大数量。而圈复杂度给出了测试用例的最小数量。
如下图,对应的圈复杂度和NPATH分别为2和4。NPATH建议限制在80个以内。
6
每个函数的嵌套级别
嵌套级别用来描述函数之间调用的深度层次。
当引入控制结构(if, while…)时,就会发生嵌套,每将控制结构放置在其他控制结构内部一次,嵌套级别就会增加一次。
以下为一个嵌套级别为2的代码段示意。
if (a < K) {
if (b > L) {
function);
}
}
嵌套级别建议不超过4。
7
调用图递归
调用图是软件工程中用于表示函数调用关系的有向图,它显示了哪个函数调用了哪个函数。
调用图内部的递归是一个函数直接或间接(通过至少一个其他函数)再次调用自身的模式。
递归是一种很好的编程技巧,但在嵌入式中有一些缺点。
要想停止递归时,必须有一个结束条件,否则,递归将导致应用程序崩溃,但是,无论是直接递归还是间接递归,确定结束条件并不容易。
此外,由于递归算法需要更多的函数调用和堆栈操作,其使用会造成性能阻塞、可读性差或堆栈溢出等问题。
一般不建议使用递归。
8
不同函数的调用次数
更多的函数调用必然带来更大的复杂性,整体最好不超过7次。
9
参数数量
参数的数量是函数复杂性和接口复杂性的另一个指标。存在的参数越多,就越容易在调用函数时出错,比如,参数顺序错误。
如果函数参数超过了5个,可以试着把函数分成使用较少参数的逻辑部分。
10
goto语句
goto语句可以使程序直接跳转到同一函数中的预定义位置。
goto是一个很有争议的语句。在处理错误或跳出多层循环时,有很直接的效果,但非逻辑性的跳转会让代码很难理解、出了错误也很难追踪。
所以,通常强烈建议不要使用goto语句。
11
注释密度
除了从语句结构上降低外,代码复杂度还有一种应对方式是代码注释。
函数功能的文本化描述就是注释,这显然有助于理解代码。特别地,代码已经长时间没有被修改,或者代码必须由原始编写人分析或修改时,这些注释更加有用。
一种算法是,注释密度是指第一个语句之后找到的注释数量与语句数量之间的比率,20%是一个可参考的下限。
12
写在最后
本文基于嵌入式ASIL D产品的开发经验和行业标准,分享了11个常用的代码指标及推荐限值,供参考。
-end-
分享不易,恳请点个【👍】和【在看】