2.2 软件缺陷分类
以前述研究为基础,软件缺陷学领域学者进一步对软件缺陷分类进行了大量研究,获得了多种软件缺陷分类法。根据软件缺陷属性进行软件缺陷分类也是研究软件缺陷的基础。目前软件缺陷分类有基于缺陷起源、基于缺陷性质、基于对用户的影响、基于缺陷等级、基于缺陷发现的测试环节、基于故障产生位置等多种方法,每种分类方法都有自己的关注点。这里主要介绍应用比较广泛的基于缺陷性质的分类和ODC,其中ODC本质上是一种缺陷分析方法,由Ram Chillarege和他的同事基于在IBM的工程实践于1992年提出。ODC通过给每个缺陷添加一些额外的属性,利用对这些属性的归纳和分析来反映产品设计、代码质量、测试水平等方面的问题,从而得到一些办法来对工作进行改进。例如,对于测试团队,通过ODC可以知道测试工作是否变得更加复杂;每个测试阶段是否利用了足够多的触发条件来发现缺陷;退出当前测试阶段有什么风险;哪个测试阶段做得好,哪个测试阶段需要改进等。对于开发团队,利用ODC可以知道产品设计和代码编写的质量情况。采用ODC方法可以提高用户满意度,减少产品投入市场后的维护花费。这种方法也是目前使用最多、影响最广的缺陷分类方法之一。
表2.1所示为几种典型软件缺陷分类法简介,主要包括基于缺陷性质的分类和立体分类。立体分类是指采用多个正交属性来刻画缺陷,以最大限度地获得语义并在此基础上实施度量的缺陷分类方法。基于缺陷性质的分类的代表分类法包括Goel软件缺陷分类法和Thayer软件缺陷分类法;立体分类的代表分类法包括ODC和IEEE软件缺陷分类法。
表2.1 几种典型软件缺陷分类法简介
续表
2.2.1 Goel软件缺陷分类法
Goel软件缺陷分类法是一种按照缺陷性质分类的方法。这种分类法可以显示缺陷的属性,以及各类缺陷的比例,因此有助于加深对缺陷发生规律的认识,并有助于寻求避免缺陷产生的方法。Goel软件缺陷分类法将软件缺陷分为语法缺陷、语义缺陷、运行缺陷、说明缺陷、性能缺陷五大类别。
1.语法缺陷
语法缺陷是指程序不符合编程语言的语法规则所造成的缺陷。语法缺陷可以用目察的方法发现,也可以用编译程序中的语法分析程序和词法分析程序在上机编译时发现,这类缺陷最容易察觉,并且大多出自缺乏经验的程序员之手。
2.语义缺陷
语义缺陷是指程序不符合计算机环境的语义分析程序要求所造成的缺陷。常见的语义缺陷有类型检查缺陷、执行限制缺陷。这些缺陷可以用目察的方法发现,也可以在上机编程时发现。
3.运行缺陷
运行缺陷是指在程序实际运行中发生的缺陷,这些缺陷又分为以下三类。
(1)定义域错误:是指程序变量值超出变量说明规定的范围,或者超出硬件描述的物理极限。变量说明有隐式和显式两种。例如,Pascal语言可以用枚举或子域来说明变量值的范围。有的编译程序能产生检查定义域错误的运行代码,有的编译程序对定义域错误有恢复功能。某些语言(如Pascal)的编译程序能自动检查超出变量说明规定的范围的变量值,但是用有的语言(如Fortran)编制的程序,在运行中一旦出现定义域错误,程序便中断执行。定义域错误是一种严重的错误,它会使程序给出错误的结果,使程序中断执行。对于实时系统,程序中断执行可能造成非常严重的后果。
(2)计算错误:是指程序给出错误的输出。计算错误又称为逻辑错误,由计算公式的错误、控制流的错误、变量的赋值错误及参数错误等原因产生。在程序执行过程中,不可能产生测定计算错误的运行代码,因为计算错误是由程序输出和程序说明之间的偏离所造成的,现有的软件测试技术无法保证消除全部计算错误。
(3)非终止错误:是指在没有外界干预的情况下,程序无法终止运行。在非终止错误中,最常见的是程序进入无限循环。如果一组并行的程序陷入死锁状态,也可能出现非终止错误。在软件测试中,通常通过执行程序中的循环语句来查找无限循环。这个方法不能保证消除无限循环,因为某些无限循环只有在变量达到特定值时才发生。
4.说明缺陷
说明缺陷是指由需求说明与用户陈述要求不符,或者用户陈述要求与用户实际要求不符所造成的缺陷。目前还没有完善的方法可用来检查和消除说明缺陷,因为没有一种非常有效的需求规格说明语言,能够将用户的需求翻译成清晰、完备和一致的术语。说明缺陷又分为三类:不完全说明、不一致说明、多义性说明。
5.性能缺陷
性能缺陷是指程序的实际性能与要求的性能之间出现差异。程序的性能一般可通过以下几个方面来衡量:响应时间、运行时间、存储空间、工作区要求等。
2.2.2 Thayer软件缺陷分类法
Thayer软件缺陷分类法是另一种按照缺陷性质分类的方法。Thayer软件缺陷分类法中用于缺陷分类的原始信息是软件测试和使用中填写和反馈的软件问题报告,因此Thayer软件缺陷分类法中包括不属于软件本身的缺陷,如操作系统及系统支持软件的缺陷或操作员缺陷。
Thayer软件缺陷分类法的特点是类别详细,适用于各种类型的程序。Thayer软件缺陷分类法包括下列16种类型。
(1)计算缺陷:指从程序方程的代码中产生的缺陷。
(2)逻辑缺陷:指设计程序时出现的逻辑上的错误,如错误的通路、错误的循环、死循环、错误的逻辑、错误的判断条件,以及没有对旗标和规定的数据值进行检验等。
(3)输入/输出缺陷:指由程序输入/输出语句产生的缺陷,如输出格式、输出位置及输出数据的完备性不符合要求等。
(4)数据加工缺陷:指在数据的读写、移动、存储和变更时发生的缺陷。
(5)操作系统及系统支持软件的缺陷:指在程序运行时,控制、支持程序运行的软件产生的缺陷。这些缺陷不属于程序本身,需要把它们和程序本身的缺陷区别开。在Thayer研究的三个软件项目中,这一类型的缺陷数量很少。
(6)配置缺陷:指软件经过修改后发生的,不能与操作系统或其他应用软件兼容的缺陷。这类缺陷会导致灾难性的后果,它们往往是由赶进度、违背配置管理规则造成的。
(7)接口缺陷:指在程序与分程序的接口、程序与系统软件的接口、程序与数据库的接口、程序与用户的接口处所发生的缺陷。
(8)用户需求改变:指在程序投入使用后,用户对软件功能提出的新需求使程序无法适应。
(9)预置数据库缺陷:指在数据库中,预置变量初始值的错误和常数的错误。
(10)全程变量缺陷:指适用于全部程序的变量或常数的错误。
(11)重复缺陷:指重复发生的同样的缺陷。Thayer软件缺陷分类法把重复缺陷作为一个专项列出,借以从数量上考察缺陷重复发生的可能性。
(12)文档缺陷:指软件文档中存在的缺陷。
(13)需求一致性缺陷:指软件偏离需求规格说明而产生的缺陷。
(14)性质不明的缺陷:指根据已有信息,无法判明其性质的缺陷。
(15)操作员缺陷:指在操作员或测试人员运行程序时产生的人为缺陷。
(16)问题:指软件问题报告中提出的需要答复的问题。
Thayer软件缺陷分类法中,在16种类型之下,还有164个子类别,详细内容参见文献[9]。
2.2.3 层次化软件缺陷分类法
经验表明,通过代码审查能够有效地发现软件中30%~70%的逻辑设计和编码缺陷。IBM通过使用代码审查方法发现,缺陷的检测效率高达全部查出缺陷的80%。Myers的研究发现,代码审查和代码走查平均能查出全部缺陷的38%。在某些情况下,代码审查可以比动态测试更有效地发现某些特定类型的缺陷,并且实施无需特别条件,成本较低。对代码缺陷进行分类并收集实例是有效实施代码审查的基础。目前已有很多针对软件缺陷分类的研究,但权威、实用且专门针对代码审查阶段的软件缺陷分类标准较少,不利于对实际审查工作进行指导或为开发人员提供良好的编程准则。各个开发和测评单位使用的缺陷分类方法缺乏统一标准,导致数据收集质量和共享使用效率较低。本节介绍层次化软件缺陷分类法,并将其范围限定为代码级,其他层级的软件缺陷分类方法将在后续章节讨论。
程序由符号序列组成,词法缺陷即符号序列在组成程序时可能出现的问题。组成程序的上述符号可以进一步作为语句和声明的序列,在更大单元的过程,如符号组成过程中,可能出现不符合编程语言语法规则的问题,即语法缺陷。即使编程时避免了以上两类缺陷,程序仍然可能与编程者实际想表达的意思不一致,即产生语义缺陷。语义缺陷可以分为内存使用缺陷、指针使用缺陷、计算缺陷、数据加工缺陷和控制流缺陷等。可维护性缺陷是专门考察程序注释问题和变量、语句多余问题的缺陷类型。在此分类基础上,本节给出缺陷产生原因和缺陷影响分析,为缺陷预防提供依据。本节总结的缺陷类型基于航空型号软件的测试经验,编程语言为C语言和一些汇编语言,详细的缺陷分类如表2.2所示,其中A表示笔误;B表示违反编码风格或标准;C表示设计缺陷;D表示表意不清;E表示对某些应用场景欠考虑;F表示理解错误。
表2.2 详细的缺陷分类
续表
续表
2.2.3.1 词法缺陷
词法缺陷主要关注程序的基本组成单元,即符号的错误使用。第一个字符是数字0的整型常量被视为八进制数。有时在上下文中为了使格式对齐,可能将十进制数写成八进制数。例如:
2.2.3.2 语法缺陷
语法缺陷关注符号在组成声明、表达式、语句和程序时产生的错误。
1.预处理缺陷
(1)宏参数未用括号围起问题。例如:
考虑如下例子:
而程序本意是要得到(2+6)×7=56。
(2)宏展开式未用括号围起问题。例如:
考虑如下例子:
而程序本意是要得到(5+5)×(5+5)=100。
(3)使用宏定义产生副作用问题。例如:
一种错误的使用情况如下:
如果++j的结果不小于k,则j会递增2次,而这可能与编程人员的本意不一致。
2.其他缺陷
(1)遗漏分号问题。例如:
return语句后遗漏了一个分号,但这段代码仍然能通过编译,并将logrec.date=x[0];作为return的操作数。若代码所在函数声明的返回值为void,则编译器会因为实际返回值类型与函数声明返回值类型不一致而报错。当函数不需要返回值时,有些编译器会默认函数返回值类型为int,编译器将无法检测到错误。
(2)遗漏程序分支的返回值问题。在下例中,b为1的分支返回值未定义,因此当执行到该分支时,程序的结果是不确定的。
2.2.3.3 语义缺陷
即使程序的词法和语法都无缺陷,程序仍然可能无法表达编程者原本的意思,即产生语义缺陷。
1.内存使用缺陷
内存使用缺陷主要指内存的读写冲突问题。例如:
中断服务程序insert从硬件端口读一组字符到数组中,程序remove将这些字符删除。因为remove执行时没有关中断,中断服务程序将干扰remove的正常执行,可能导致严重后果。
2.指针使用缺陷
(1)未对某指针是否为空做判断问题。例如:
在执行后两句代码之前未对str是否为空做判断。若malloc执行失败,则将导致str为空指针,使后两句代码无法正常执行。因此,应在char*str=malloc(1024);后加条件判断语句if(str!=null),再执行后面的语句。
(2)错误地使用“野指针”问题。“野指针”不是NULL指针,而是指向“垃圾”内存的指针。指针p指向的内存被释放后未置为NULL,让人误以为p是合法指针。free操作释放指针所指内存,而没有清除指针本身。通常用语句if(p!=NULL)进行防错处理,但此时if语句无法起到防错作用,因为即使p不是NULL指针,它也不指向合法内存块。例如:
3.计算缺陷
(1)判浮点数相等问题。浮点数在计算机中的二进制表达方式决定了大多数浮点数都无法精确表达,并且浮点数的计算精度有限。在进行浮点运算时,浮点数的计算精度限制通常会导致运算结果与实际期望结果之间有误差。因此,要避免浮点型变量之间的“==”或“!=”比较,一般的做法是将两个浮点数相减,然后与允许误差进行比较来确定这两个浮点数是否相等。
(2)精度损失问题。要谨防各类数值型数据混合运算产生精度损失。例如,double型数据向float型数据转换及浮点型数据向整型数据转换都会产生精度损失。
4.数据加工缺陷
数据加工缺陷主要是指循环控制变量类型与其使用形式不一致问题。例如:
在上例中,当参数b的值大于127时,由于循环变量i为char型数据,经转换后其值变成负数(char型数据的范围为[-128,127]),导致循环无法进入。
5.控制流缺陷
正确的控制流能保证程序有序、正常地执行。对控制结构的不正确使用将导致非预期的代码执行,并且可能导致危险状况的发生。
(1)中断处理缺陷。中断服务程序是实时系统的重要组成部分。系统通过中断机制与外部环境通信并对外部事件做出响应。当中断来临时,CPU暂时停止当前程序的执行转而处理中断服务程序。
①发送/应答类型的程序在函数异常返回前未完成必要工作问题。例如:
当if条件满足时,程序直接返回而不执行后两条语句。此时,程序即使成功从外部取到数,也不对外部发数硬件进行应答,从而导致硬件死等。
②中断现场保护问题。当出现中断时,应将CPU的当前状态,即中断的入口地址保存到堆栈中,转而执行其他任务。其他任务执行完成后,从堆栈中取出中断的入口地址继续执行。中断现场保护就是保护中断前一时刻的状态不被破坏。
(2)其他缺陷。其他缺陷主要是指未对不可重入函数进行保护问题。函数重入是指由于外部因素或内部调用,在一个函数没有完成一次执行的情况下,又一次进入该函数使其重新执行。可重入函数必须保证资源互不影响地使用,以保证被多个任务调用而不会导致数据被破坏。
本书采用控制变量加以保护,使函数不可重入(不能保证资源,如全局变量、系统资源等互不影响地被使用)。例如:
2.2.3.4 可维护性缺陷
可维护性缺陷关注程序注释问题和变量、语句多余问题。在本书中,变量多余是指变量被定义后,在后续代码中未被使用(未参与计算或逻辑运算);语句多余是指不影响程序功能(或性能)的冗余代码。
2.2.3.5 实例应用
代码审查单是代码审查中使用的主要工具,是对缺陷探测经验的总结,通常包括一系列表示为问题形式的典型缺陷和实例,用于指导审查员对代码进行分析。基于上文对缺陷的分类得到代码审查单,如表2.3所示。其中,各审查项对应的缺陷实例可以参考上述内容。
表2.3 代码审查单
上述代码审查单已投入实际代码审查使用。表2.4列出了某航空型号软件测试项目代码审查阶段根据该审查单发现的各类缺陷个数和各重要度等级的缺陷个数。由表2.4可见,语义缺陷的重要度等级较高,由于它们直接影响程序的可靠性和安全性,因此是审查重点。可维护性缺陷个数较多,但重要度等级不高,因为它们不直接影响程序的可靠性和安全性,仅对代码维护产生影响。该测试项目代码审查阶段发现的缺陷类型可以被审查单中的缺陷类型覆盖。在完成全部测试项目后,统计得到代码审查阶段发现的缺陷个数占全部缺陷个数的75%。审查结果表明,该代码缺陷分类方法能有效指导实际代码审查。
表2.4 某航空型号软件代码审查结果
续表