软件质量经济学
上QQ阅读APP看书,第一时间看更新

3.2.8 圈复杂度度量(及相关的复杂度度量)

圈复杂度和基本复杂度都于1975年因数学家Tom McCabe而成名。两种度量都控制着软件应用程序的流程。圈复杂度度量了全部的路径,基本复杂度消除了冗余的路径。

圈复杂度是以图论为基础的,通用的公式是“边减去节点再加上未连接部分的2倍”。图3-1说明了一个简单的软件控制流图。在这个图中,没有分支,因此圈复杂度等级是1。显然,分支会增加圈复杂度的等级。

超过35年的经验数据说明,软件工程师一般认为圈复杂度低于10的软件是非常简单且很容易跟踪的。

随着圈复杂度上升到20以上,就会感知到复杂度急剧上升。潜在缺陷增多了,缺陷清除效率变低了。测试覆盖率急剧下降。将圈复杂度维持在10以下有很多实际的和经济的理由,更完美的是保持在5以下。这不总是可以实现的,但是软件界有一个常识是圈复杂度高是有害的。

复杂度仍然是一个含糊的主题,没有所有研究者一致认可的确切定义。尽管圈复杂度和基本复杂度有清楚的含义,但是还有很多其他类型的复杂度影响着软件,但是并没有被度量和作为质量预测的因素。

当我们在软件的上下文中提到复杂度时,我们讨论的是软件应用程序尝试实现的问题、应用程序所用到的代码架构或者数据项间关系的难度。换句话说,复杂度这个词语可以被用来通过一个通用的方式来讨论问题复杂度、代码复杂度和数据复杂度。圈复杂度只度量了代码复杂度。

在本书一位作者的客户之中,600家企业中的大约450家在其度量圈复杂度的软件项目中使用了某种形式的复杂度度量工具。很多执行这类计算的商业工具是可用的。

复杂度度量工具的使用在系统软件和军事软件(超过75%的项目)中是很普遍的,在管理信息系统、外包软件、Web和商业软件(少于60%)中的使用稍微少一点。

使用复杂度度量工具的有趣的一点是,表面上看来好像使用了随机的模式。我们常常能看到同一家公司里两个接近的项目中,一个可能使用了复杂度分析工具,而另一个,没有什么特别的原因,就是没有使用。

科学和工程著作介绍了复杂度不少于30种不同的特点,其中一些可能与软件应用程序是相关的。科学著作在软件的上下文中提到的复杂度种类包括下面这些。

算法复杂度关注可计算问题的算法的长度和结构。算法较长和较复杂的软件应用程序是很难设计、审查、编码、证明、调试和测试的。

代码复杂度关注开发和维护人员对所负责的代码是否复杂的主观看法。对软件人员的访谈和对其主观意见的收集是更正式的复杂度度量方法(如圈复杂度和基本复杂度)标准化中很重要的一步。除非与代码打交道的实际的软件人员断言圈复杂度20比10更难办,否则就没有什么关系。然而,代码复杂度的主观看法似乎与圈复杂度是相关联的。

组合复杂度关注可以在N个组件上构建的子集合和集合的数目。这个概念有时以软件应用程序的模块和组件的构建方式出现。

计算复杂度关注执行算法所需的机器时间和迭代数。一些问题是非常复杂的,导致它们被认为是不可计算的。其他的一些问题是可以解决的,但是需要太多的机器时间,如密码分析法或者天气模式的气象分析。

圈复杂度是从图论中派生出来的,并且是Tom McCabe博士使其变得流行的。圈复杂度用于测量软件片段的结构图的控制流程。计算控制流图圈复杂度的通用公式是“边减去节点再加上未连接部分的2倍”。圈复杂度经常被用作潜在缺陷问题的警告指示器。

数据复杂度处理与实体相关的属性数目。例如,在一个典型的医疗机构存放病人记录的数据库中,一个人相关的属性可能包括出生日期、性别、婚姻状态、孩子、兄弟姐妹、身高、体重、四肢残疾情况等。数据复杂度是处理数据质量的一个关键因素。

诊断复杂度是从医疗实践中派生出来的,在医疗实践中,诊断复杂度处理用来准确地确定疾病的症状(体温、血压、损伤等)的组合。例如,很多年来,如果表面上的症状基本相同的话,确定一个病人是患了结核病还是组织胞浆菌病还是很难的。对于软件来说,诊断复杂度会在客户报告缺陷、厂商尝试隔离相关症状并找出问题所在时出现。

熵复杂度是系统组件部分的异常状态。熵是一个重要的概念,因为随着时间的推移,所有著名的熵都会增加。也就是说,异常会逐步增加。根椐观察,这个现象会伴随着软件项目而发生,考虑很多小的变更会逐渐损害初始的架构。人们在软件项目维护模式方面进行了广泛的研究,尝试去度量熵增加的速率,以及它是否可以被代码重构等方法反转。

基本复杂度也是从图论中派生出来的,也是Tom McCabe博士使其变得流行的。软件片段的基本复杂度是从圈复杂度中派生出来的,是在应用程序的图通过清除冗余的路径而被简化后产生的。基本复杂度常常被用作潜在质量问题的警告指示器。与圈复杂度相同,一个没有任何分支的模块的基本复杂度是1。随着分支数目的增加,圈复杂度和基本复杂度都会上升。

扇复杂度是指软件模块被调用(称为“扇入”(fan in))的次数,或者软件模块调用(称为“扇出”(fan out))的模块数目。扇入数目大的模块就软件质量而言显然很关键,因为它们被很多其他模块调用。然而,扇出数目大的模块也很重要,而且很难调试,因为它们依赖于非常多的模块。扇复杂度与潜在重用的研究是相关的。

流复杂度是流体动力学和气象学研究的一个重要主题。它是处理通过渠道和跨过障碍物的流体的湍流的。数学物理学的一个称为“混沌理论”的新子领域提升了处理物理问题的流复杂度的重要性。很多概念(包括混沌理论本身)与软件是相关的,应该研究一下。例如,通过高圈复杂度的软件的控制流类似于湍流液体的流,是比较难预测的。

功能点复杂度是指计算软件项目最终调整过的功能点总数所需要的调整因子的集合。标准美国功能点,如由国际功能点用户组(IFPUG)所定义的那样,拥有14个复杂度调整因子。英国的MarkⅡ功能点使用了19个复杂度调整因子。SPR功能点和特性点度量方法使用了3个复杂度调整因子。

图复杂度是从图论中派生出来的,处理为各种目的而创建的图中的边和节点数目。这个概念对软件来说是很重要的,因为它是圈复杂度和基本复杂度分析的一部分,而且也是几个源代码重构工具的操作的一部分。

Halstead复杂度是从已故的Maurice Halstead博士和他的同事及学生们在普渡大学的“软件科学”研究中派生出来的。Halstead软件科学对复杂度的处理是以4个单元为基础的:①单一的运算符(如动词)的数目;②单一的操作数(名词)的数目;③运算符出现的实例;④操作数出现的实例。Halstead的工作与语言学的研究有所重叠,它试图枚举出如软件项目的词汇之类的概念。

信息复杂度关注在数据库、存储库或数据仓库中发现的实体数目以及这些实体之间的关系。信息复杂度也与数据质量的研究相关。

逻辑复杂度对软件和电路设计都很重要。它是以AND、OR、NOR和NAND连起来的逻辑条件的组合为基础的。这种形式的复杂度对表达算法和正确性证明是很重要的。

记忆复杂度是从认知心理学派生出来的,处理的是记忆的容易度或难度。大家都已熟知,人类的大脑有临时记忆和永久记忆。一些类型的信息(如名字或电话号码)被保存在临时记忆中,需要有意识的努力来将它们转移到永久记忆中。另外一些类型的信息(如气味或长相)会直接进入永久记忆。这个主题对于软件调试、设计和代码审查来说是很重要的。很多过程式编程语言有一些很难扫描和调试的象征性转换,因为这些过度的转换塞满了人类的临时记忆。如嵌套循环一类的使用了多层圆括号的东西,“(((……)))”,超过了人类临时记忆的能力。

组织复杂度处理公司中人们将自己分配到分层的小组或矩阵组织中的方式。这个主题可能被假定与软件只有间接的关系,除了这种情况——很多大型软件项目被分解成适合当前组织结构的组件。例如,很多大型项目被分解成可以被8人部门处理的片段(不论这个方法是否符合系统架构的需要)。

认知复杂度是从认知心理学派生出来的,处理的是边的排列和人们观察起来容易或复杂的表象。例如,规则的模式就是容易的,而随机的排列就是复杂的。这个主题对于可视化、软件设计方法以及屏幕可读性评估的研究是很重要的。举一个例子,在国际象棋对弈中,经验丰富的棋手很容易记住每颗棋子的位置,而如果将棋子随机地放在一个棋盘上,即使是经验丰富的棋手要记忆棋子的位置也是有困难的。

问题复杂度关注人们对他们要解决的各种问题的难度的主观看法。心理学家知道增加变量的数目和演绎推理链的长度会增强认为问题比较复杂的主观看法。归纳推理也增强了复杂度高的观念。在软件的上下文中,问题复杂度涉及将成为程序或系统组成部分的算法。确定真实的人的主观看法是标准化更多客观复杂度度量的必要步骤。

过程复杂度在数学上与流复杂度是相关的,但在日常的软件工作中,它是与贯穿一个软件开发周期的材料流相关的。这方面的复杂度经常被项目管理工具以特别的方式处理,这些项目管理工具可以计算软件开发过程中的关键路径和PERT图(方案评估和评审技术)。

语义复杂度是从语言学的研究中派生出来的,关注的是术语定义中的歧义。本书引用过的含糊的术语有“质量”、“数据”和“复杂度”。由于一个奇怪的原因,这个主题与软件是相关的:软件开发者与他们的客户之间的很多法律诉讼可以追溯到合同的语义复杂度,两方对同样的条款有不同的解释。

句法复杂度也是从语言学派生出来的,处理的是语法的结构和散文片段(prose section)(如句子和段落)的长度。很多商业软件工具可以使用如FOG索引一类的度量方法来度量句法复杂度(遗憾的是,这些工具几乎没有被用在软件规格说明书中,尽管它们看上去对这个目的很有价值)。

拓扑复杂度处理的是旋转和折叠模式。这个主题经常被数学家研究,但是它也与软件相关。举例来说,拓扑复杂度在一些商业来源的代码重构工具中是一个因子。

从以上包含了“复杂度”这个词汇的各个主题可以看出,这并不是一个容易处理的主题。从软件质量的观点看,6种复杂度的重要性特别突出:

1)主观代码复杂度(由访谈决定);

2)圈复杂度;

3)数据复杂度;

4)信息复杂度;

5)过程复杂度;

6)问题复杂度(由访谈决定)。

基于构建软件的技术人员的主观看法和客观度量,如果这6个方面的复杂度比较高,那么质量、工期和成本都可能会给讨论中的项目带来麻烦。相反地,如果这6个方面的复杂度比较低,那么软件项目就不会引起什么麻烦。

高复杂度会增加潜在缺陷,低复杂度会降低潜在缺陷。但是影响软件项目的各种形式的复杂度大部分仍然没有被研究过,因此也就是不明确的。

遗憾的是,复杂度和规模对于软件通常是紧密相关的,所以低复杂度的项目往往是规模小于100个功能点的项目,而许多规模大于1000个功能点的软件项目,以及几乎所有规模大于10000个功能点的软件项目,在这6个复杂度因素方面等级是比较高的,这些方面对软件是非常麻烦的。

对于为什么大多数形式的复杂度会发生在软件项目中,有两个独立的根本原因:

1)软件开发是非常困难的,对智力活动是种挑战,而且不论你考虑的是哪一种复杂度,有些问题都是很困难的;

2)软件开发实践是不严格的,以至于有时候以较差的代码结构或者有疑问的设计实践的形式出现的复杂度好像是较差的培训、缺乏经验或者过度赶工的偶然的副产品。

尽管这两个根本原因是独立的变量,它们可以而且经常一起发生。当非常困难的问题与不严格、粗心或者没受过良好培训的开发人员联系在一起时,项目几乎就不会有一个大团圆的结局,而且可能会被中止。

圈复杂度增长的另外一个方面是需要越来越多的测试用例来覆盖所有路径和路径序列。确实,全覆盖测试对于高于10的圈复杂度是比较困难的,而对于高于20的圈复杂度大概是不可能的。

软件复杂度的著作很多只专注在代码上,有时候只专注在控制流或者分支序列(branching sequence)上。尽管代码复杂度是一个重要的主题且非常值得研究,但是绝不是需要研究的唯一主题。

关于所有形式的软件复杂度,尤其是与算法、可视化、软件需求、规格说明、测试用例以及数据相关的复杂度,还有很多研究需要做。