5.1 软件工程概述

早期的软件主要指程序,程序的开发采用个体工作方式,开发工作主要依赖于开发人员的个人技能和程序设计技巧。当时的软件通常缺少与程序有关的文档,软件开发的实际成本和进度往往与预计的相差甚远,软件的质量得不到保证,开发出来的软件常常不能使用户满意。随着计算机应用需求的不断增长,软件的规模也越来越大,然而软件开发的生产率远远跟不上计算机应用的迅速增长。此外,由于软件开发时缺少好的方法指导和工具辅助,同时又缺少相关文档,使得大量已有的软件难以维护。上述这些问题严重地阻碍了软件的发展,20 世纪 60 年代中期,人们把上述软件开发和维护过程中所遇到的各种问题称为“软件危机”。

1968 年,在德国召开的 NATO(North Atlantic Treaty Organization,北大西洋公约组织)会议上首次提出了“软件工程”这个名词,希望用工程化的原则和方法来克服软件危机。在此以后,人们开展了软件开发模型、开发方法、工具与环境的研究,提出了瀑布模型、演化模型、螺旋模型和喷泉模型等开发模型,出现了面向数据流方法、面向数据结构的方法、面向对象方法等开发方法,以及一批 CASE(Computer Aided Software Engineering,计算机辅助的软件工程)工具和环境。现在,软件工程已经成为计算机软件的一个重要分支和研究方向。

软件工程是指应用计算机科学、数学及管理科学等原理(如图 5-1 所示),以工程化的原则和方法来解决软件问题的工程,其目的是提高软件生产率、提高软件质量、降低软件成本。软件工程涉及软件开发、维护、管理等多方面的原理、方法、工具与环境,限于篇幅,本章不能对软件工程做全面的介绍。根据软件设计考试大纲的要求,本章着重介绍软件开发过程中的原理,其他内容只做简单的介绍。

5.1.1 计算机软件

计算机软件是指计算机系统中的程序及其文档。程序是计算任务的处理对象和处理规则的描述。任何以计算机为处理工具的任务都是计算任务。处理对象是数据(如数字、文字、图形、图像、声音等,它们只是表示,而无含义)或信息(数据及有关的含义)。处理规则一般指处理的动作和步骤。文档是为了便于了解程序所需的阐述性资料。

按照软件的应用领域,可以将计算机软件分为十大类。

1. 系统软件

系统软件是一整套服务于其他程序的程序。某些系统软件处理复杂但是确定的信息结构。另一些系统应用程序(如操作系统构件、驱动程序、网络软件、远程通信处理器)主要处理的是不确定的数据。无论何种情况,系统软件多具有以下特点:和计算机硬件大量交互;多用户大量使用;需要调度、资源共享和复杂进程管理的同步操作;复杂的数据结构以及多种外部接口。

2. 应用软件

应用软件是解决特定业务需要的独立应用程序。这类应用软件处理商务或技术数据,以协助业务操作和管理或技术决策。除了传统数据处理的应用程序,应用软件也被用于业务功能的实时控制(例如销售点的交易处理、实时制造过程控制等)。

3. 工程/科学软件

这类软件通常带有“数值计算”算法的特征。工程/科学软件涵盖了广泛的应用领域,从天文学到火山学,从自动应力分析到航天飞机轨道动力学,从分子生物学到自动制造业。不过,当今科学工程领域的应用软件已经不仅仅局限于传统的数值算法,计算机辅助设计、系统仿真和其他的交互性应用程序已经呈现出实时性甚至具有系统软件的特性。

4. 嵌入式软件

嵌入式软件存在于某个产品或系统中,可实现和控制面向最终使用者和系统本身的特性和功能。嵌入式软件可以执行有限但难于实现的功能(例如,微波炉的按键控制)或者提供重要的功能和控制能力(例如,汽车中的燃油控制、仪表板显示、刹车系统等汽车电子功能)。

5. 产品线软件

产品为多个不同用户的使用提供特定功能。产品线软件关注有限的特定专业市场(例如库存控制产品)或大众消费品市场(例如,文字处理、多媒体、娱乐、数据库管理等)。

6. Web 应用

Web 应用(WebApp)是一类以网络为中心的软件,其概念涵盖了宽泛的应用程序产品。最简单可以是一组超文本链接文件,仅仅用文本和有限的图形表达信息。然而,随着 Web 2.0 的出现,网络应用正在发展为复杂的计算环境,不仅为最终用户提供独立的特性、计算功能和内容信息,还与企业数据库和商务应用程序相结合。绝大多数 WebApp 具备网络密集性、并发性、无法预知的负载量、性能、可用性和数据驱动属性。

7. 人工智能软件

人工智能软件利用非数值算法解决计算和直接分析无法解决的复杂问题。这个领域的应用包括机器人、专家系统、模式识别、人工神经网络、定理证明和博弈等。

8. 开放计算

无线网络的快速发展将促成真正的普适计算、分布式计算的实现。软件工程师所面临的挑战是如何开发系统和应用软件,以使移动设备、个人电脑和企业应用可以通过大量的网络设施进行通信。

9. 网络资源

现在,万维网已经快速发展为一个计算引擎和内容提供平台。软件工程师面临的新任务是构建一个简单而智能的应用程序,为全世界的最终用户市场提供服务。

10. 开源软件

开源软件就是开放系统应用程序的代码,使得很多人能够为软件开发做贡献,这种方式正在逐步成为一种趋势。软件工程师面临的挑战是开发可以自我描述的代码,更重要的是开发某种技术,以便于用户和开发人员都能够了解已经发生的改动,并且知道这些改动如何在软件中体现出来。

5.1.2 软件工程基本原理

美国著名的软件工程专家 B.W.Boehm 于 1983 年提出了软件工程的 7 条基本原理。Boehm 认为这 7 条原理是确保软件产品质量和开发效率的原理的最小集合。

1. 用分阶段的生命周期计划严格管理

有统计表明,50%以上的失败项目是由于计划不周造成的。在软件开发与维护的漫长生命周期中,需要完成许多各种各样的工作。这条基本原理意味着应该把软件生命周期划分成若干个阶段,并相应地制订出切实可行的计划,然后严格按照计划对软件的开发与维护工作进行管理。Boehm 认为,在软件的整个生存周期中应该制定并严格执行六类计划:项目概要计划、里程碑计划、项目控制计划、产品控制计划、验证计划和运行维护计划。

2. 坚持进行阶段评审

据统计结果显示,大部分错误是在编码之前造成的。根据 Boehm 等人的统计,设计错误占软件错误的 63%,编码错误仅占 37%,而且错误发现与改正得越晚,所需付出的代价越高。因此,在每个阶段都应进行严格的评审,以便尽早发现在软件开发过程中所犯的错误。

3. 实现严格的产品控制

在软件开发过程中不应随意改变需求,因为改变一项需求需要付出较高的代价。但是,在软件开发过程中改变需求又是难免的,由于外部环境的变化,相应地改变用户需求是一种客观需要,这就要采用科学的产品控制技术来顺应这种要求。在改变需求时,为了保持软件各个配置成分的一致性,必须实行严格的产品控制,其中主要是实行基准配置管理。基准配置又称为基线配置,它是经过阶段评审后的软件配置成分(各个阶段产生的文档或程序代码)。基准配置管理也称为变动控制,一切有关修改软件的建议,特别是涉及基准配置的修改建议,都必须按照严格的规程进行评审,在获得批准以后才能实施修改。

4. 采用现代程序设计技术

从 20 世纪 60 年代和 70 年代的结构化软件开发技术到面向对象技术,从第一代、第二代语言到第四代语言,人们已经充分认识到:方法大于力气。采用先进的技术既可以提高软件开发的效率,又可以降低软件维护的成本。

5. 结果应能清楚地审查

软件是一种看不见、摸不着的逻辑产品。软件开发小组的工作进展情况可见性差,难以评价和管理。为了更好地进行管理,应根据软件开发的总目标及完成期限尽量明确地规定开发小组的责任和产品标准,从而使所得到的结果能够清楚地审查。

6. 开发小组的人员应少而精

开发人员的素质和数量是影响软件质量和开发效率的重要因素,应该少而精。这一条基于两点原因:高素质开发人员的效率比低素质开发人员的效率要高几倍到几十倍,开发工作中犯的错误也要少得多;当开发小组为 $N$ 人时,可能的通信信道为 $N(N-1)/2$。可见,随着人数 $N$ 的增大,通信开销将急剧增大。

7. 承认不断改进软件工程实践的必要性

遵循上述 6 条基本原理,就能够按照当代软件工程基本原理实现软件的工程化生产。但是它们只是对现有经验的总结和归纳,并不能保证软件开发与维护的过程能赶上时代前进的步伐,能跟上技术的不断进步。因此,Boehm 提出应把“承认不断改进软件工程实践的必要性”作为软件工程的第 7 条原理。根据这条原理,用户不仅要积极采纳新的软件开发技术,还要注意不断总结经验,收集进度和消耗等数据,进行出错类型和问题报告统计。这些数据既可以用来评估新的软件技术的效果,也可以用来指明必须着重注意的问题和应该优先进行研究的工具和技术。

5.1.3 软件生存周期

与其他事物一样,一个软件产品或软件系统也要经历孕育、诞生、成长、成熟、衰亡的许多阶段,一般称为软件生存周期。把整个软件生存周期划分为若干阶段,使得每个阶段有明确的任务,使规模大、结构复杂和管理复杂的软件的开发变得容易控制和管理。通常,软件生存周期包括可行性分析与项目开发计划、需求分析、设计(概要设计和详细设计)、编码、测试、维护等活动,可以将这些活动以适当的方式分配到不同的阶段去完成。

1. 可行性分析与项目开发计划

这个阶段主要确定软件的开发目标及其可行性。必须要回答的问题是:要解决的问题是什么?该问题有可行的解决办法吗?若有解决的办法,则需要多少费用?需要多少资源?需要多少时间?要回答这些问题,就要进行问题定义、可行性分析,制订项目开发计划。

可行性分析与项目计划阶段的参加人员有用户、项目负责人和系统分析师。该阶段产生的主要文档有可行性分析报告和项目开发计划。

2. 需求分析

需求分析阶段的任务不是具体地解决问题,而是准确地确定软件系统必须做什么,确定软件系统的功能、性能、数据和界面等要求,从而确定系统的逻辑模型。该阶段的参加人员有用户、项目负责人和系统分析师。该阶段产生的主要文档有软件需求说明书。

3. 概要设计

在概要设计阶段,开发人员要把确定的各项功能需求转换成需要的体系结构。在该体系结构中,每个成分都是意义明确的模块,即每个模块都和某些功能需求相对应,因此,概要设计就是设计软件的结构,明确软件由哪些模块组成,这些模块的层次结构是怎样的,这些模块的调用关系是怎样的,每个模块的功能是什么。同时,还要设计该项目的应用系统的总体数据结构和数据库结构,即应用系统要存储什么数据,这些数据是什么样的结构,它们之间有什么关系。

概要设计阶段的参加人员有系统分析师和软件设计师。该阶段产生的主要文档有概要设计说明书。

4. 详细设计

详细设计阶段的主要任务是对每个模块完成的功能进行具体描述,要把功能描述转变为精确的、结构化的过程描述。即该模块的控制结构是怎样的,先做什么,后做什么,有什么样的条件判定,有些什么重复处理等,并用相应的表示工具把这些控制结构表示出来。

详细设计阶段的参加人员有软件设计师和程序员。该阶段产生的主要文档有详细设计文档。

5. 编码

编码阶段就是把每个模块的控制结构转换成计算机可接受的程序代码,即写成某种特定程序设计语言表示的源程序清单。

6. 测试

测试是保证软件质量的重要手段,其主要方式是在设计测试用例的基础上检查软件的各个组成部分。测试阶段的参加人员通常是另一部门(或单位)的软件设计师或系统分析师。该阶段产生的主要文档有软件测试计划、测试用例和软件测试报告。

7. 维护

软件维护是软件生存周期中时间最长的阶段。已交付的软件投入正式使用后,便进入软件维护阶段,它可以持续几年甚至几十年。在软件运行过程中可能由于各方面的原因需要对它进行修改,其原因可能是运行中发现了软件隐含的错误而需要修改;也可能是为了适应变化了的软件工作环境而需要做适当变更;也可能是因为用户业务发生变化而需要扩充和增强软件的功能;还可能是为将来的软件维护活动做预先准备等。

5.1.4 软件过程

在开发产品或构建系统时,遵循一系列可预测的步骤(即路线图)是非常重要的,它有助于及时交付高质量的产品。软件开发中所遵循的路线图称为“软件过程”。过程是活动的集合,活动是任务的集合。软件过程有 3 层含义:一个是个体含义,即指软件产品或系统在生存周期中的某一类活动的集合,如软件开发过程、软件管理过程等;二是整体含义,即指软件产品或系统在所有上述含义下的软件过程的总体;三是工程含义,即指解决软件过程的工程,应用软件的原则、方法来构造软件过程模型,并结合软件产品的具体要求进行实例化,以及在用户环境下的运作,以此进一步提高软件的生产率,降低成本。

1. 能力成熟度模型(CMM)

自从软件工程概念提出以后,出现了许多开发、维护软件的模型、方法、工具和环境,它们对提高软件的开发、维护效率和质量起到了很大的作用。尽管如此,人们开发和维护软件的能力仍然跟不上软件所涉及问题的复杂程度的增长,软件组织面临的主要问题仍然是无法开发出符合预算和进度要求的高可靠性和高可用性的软件。人们开始意识到问题的实质是缺乏管理软件过程的能力。

在美国国防部的支持下,1987 年,卡内基·梅隆大学软件工程研究所率先推出了软件工程评估项目的研究成果——软件过程能力成熟度模型(Capability Maturity Model of Software,CMM),其研究目的是提供一种评价软件承接方能力的方法,同时它可帮助软件组织改进其软件过程。

CMM 是对软件组织进化阶段的描述,随着软件组织定义、实施、测量、控制和改进其软件过程,软件组织的能力经过这些阶段逐步提高。该能力成熟度模型使软件组织能够较容易地确定其当前过程的成熟度并识别其软件过程执行中的薄弱环节,确定对软件质量和过程改进最为关键的几个问题,从而形成对其过程的改进策略。软件组织只要关注并认真实施一组有限的关键实践活动,就能稳步地改善其全组织的软件过程,使全组织的软件过程能力持续增长。

CMM 将软件过程改进分为以下 5 个成熟度级别。

1)初始级(Initial) 软件过程的特点是杂乱无章,有时甚至很混乱,几乎没有明确定义的步骤,项目的成功完全依赖个人的努力和英雄式核心人物的作用。

2)可重复级(Repeatable) 建立了基本的项目管理过程和实践来跟踪项目费用、进度和功能特性,有必要的过程准则来重复以前在同类项目中的成功。

3)已定义级(Defined) 管理和工程两方面的软件过程已经文档化、标准化,并综合成整个软件开发组织的标准软件过程。所有项目都采用根据实际情况修改后得到的标准软件过程来开发和维护软件。

4)已管理级(Managed) 制定了软件过程和产品质量的详细度量标准。软件过程的产品质量都被开发组织的成员所理解和控制。

5)优化级(Optimized) 加强了定量分析,通过来自过程质量反馈和来自新观念、新技术的反馈使过程能不断持续地改进。

CMM 模型提供了一个框架,将软件过程改进的进化步骤组织成 5 个成熟度等级,为过程不断改进奠定了循序渐进的基础。这 5 个成熟度等级定义了一个有序的尺度,用来测量一个组织的软件过程成熟度和评价其软件过程能力。成熟度等级是已得到确切定义的,也是在向成熟软件组织前进途中的平台。每一个成熟度等级为继续改进过程提供一个基础。每一个等级包含一组过程目标,通过实施相应的一组关键过程域达到这一组过程目标,当目标满足时,能使软件过程的一个重要成分稳定。每达到成熟度框架的一个等级,就建立起软件过程的一个相应成分,使得组织过程能力有一定程度的增长。

基于 CMM 模型的产品包括一些诊断工具,可应用于软件过程评价和软件能力评估小组,以确定一个机构的软件过程实力、弱点和风险。最著名的是成熟度调查表。软件过程评价及软件能力评估的方法及培训也依赖于 CMM 模型。

2. 能力成熟度模型集成(CMMI)

CMM 的成功导致了适用不同学科领域的模型的衍生,如系统工程的能力成熟度模型,适用于集成化产品开发的能力成熟度模型等。而一个工程项目又往往涉及多个交叉的学科,因此有必要将各种过程改进的工作集成起来。1998 年,由美国产业界、政府和卡内基·梅隆大学软件工程研究所共同主持 CMMI 项目。CMMI 是若干过程模型的综合和改进,是支持多个工程学科和领域的、系统的、一致的过程改进框架,能适应现代工程的特点和需要,能提高过程的质量和工作效率。2000 年发布了 CMMI-SE/SW/IPPD,集成了适用于软件开发的 SW-CMM(草案版本 2(C))、适用于系统工程的 EIA/IS731 以及适用于集成化产品和过程开发的 IPD CMM(0.98 版)。2002 年 1 月发布了 CMMI-SE/SW/IPPD 1.1 版。

CMMI 提供了两种表示方法:阶段式模型和连续式模型。

1)阶段式模型 阶段式模型的结构类似于 CMM,它关注组织的成熟度。CMMI-SE/SW/IPPD 1.1 版中有 5 个成熟度等级。

  • 初始的:过程不可预测且缺乏控制。
  • 已管理的:过程为项目服务。
  • 已定义的:过程为组织服务。
  • 定量管理的:过程已度量和控制。
  • 优化的:集中于过程改进。

2)连续式模型 连续式模型关注每个过程域的能力,一个组织对不同的过程域可以达到不同的过程域能力等级(Capability Level,CL)。CMMI 中包括 6 个过程域能力等级,等级号为 $0\sim5$。能力等级包括共性目标及相关的共性实践,这些实践在过程域内被添加到特定目标和实践中。当组织满足过程域的特定目标和共性目标时,就说该组织达到了那个过程域的能力等级。

能力等级可以独立地应用于任何单独的过程域,任何一个能力等级都必须满足比它等级低的能力等级的所有准则。对各能力等级的含义简述如下。

  • $\text{CL}_0$(未完成的):过程域未执行或未得到 $\text{CL}_1$ 中定义的所有目标。
  • $\text{CL}_1$(已执行的):其共性目标是过程将可标识的输入工作产品转换成可标识的输出工作产品,以实现支持过程域的特定目标。
  • $\text{CL}_2$(已管理的):其共性目标集中于已管理的过程的制度化。根据组织级政策规定过程的运作将使用哪个过程,项目遵循已文档化的计划和过程描述,所有正在工作的人都有权使用足够的资源,所有工作任务和工作产品都被监控、控制和评审。
  • $\text{CL}_3$(已定义级的):其共性目标集中于已定义的过程的制度化。过程是按照组织的剪裁指南从组织的标准过程集中剪裁得到的,还必须收集过程资产和过程的度量,并用于将来对过程的改进。
  • $\text{CL}_4$(定量管理的):其共性目标集中于可定量管理的过程的制度化。使用测量和质量保证来控制和改进过程域,建立和使用关于质量和过程执行的定量目标作为管理准则。
  • $\text{CL}_5$(优化的):使用量化(统计学)手段改变和优化过程域,以满足客户要求的改变和持续改进计划中的过程域的功效。

5.2 软件过程模型

软件过程模型习惯上也称为软件开发模型,它是软件开发全部过程、活动和任务的结构框架。典型的软件过程模型有瀑布模型、增量模型、演化模型(原型模型、螺旋模型)、喷泉模型、基于构件的开发模型和形式化方法模型等。

5.2.1 瀑布模型(Waterfall Model)

瀑布模型是将软件生存周期中的各个活动规定为依线性顺序连接的若干阶段的模型,包括需求分析、设计、编码、测试、运行与维护。它规定了由前至后、相互衔接的固定次序,如同瀑布流水逐级下落,如图 5-2 所示。

瀑布模型为软件的开发和维护提供了一种有效的管理模式,根据这一模式制定开发计划,进行成本预算,组织开发力量,以项目的阶段评审和文档控制为手段有效地对整个开发过程进行指导,所以它是以文档作为驱动、适合于软件需求很明确的软件项目的模型。

瀑布模型假设,一个待开发的系统需求是完整的、简明的、一致的,而且可以先于设计和实现完成之前产生。

瀑布模型的一个变体是 V 模型,如图 5-3 所示。

V 模型描述了质量保证活动和沟通、建模相关活动以及早期构建相关的活动之间的关系。随着软件团队工作沿着 V 模型左侧步骤向下推进,基本问题需求逐步细化,形成问题及解决方案的技术描述。一旦编码结束,团队沿着 V 模型右侧的步骤向上推进工作,其实上是执行了一系列测试(质量保证活动),这些测试验证了团队沿着 V 模型左侧步骤向下推进过程中所生成的每个模型。V 模型提供了一种将验证确认活动应用于早期软件工程工作中的方法。

瀑布模型的优点是,容易理解,管理成本低;强调开发的阶段性早期计划及需求调查和产品测试。不足之处是,客户必须能够完整、正确和清晰地表达他们的需要;在开始的两个或 3 个阶段中,很难评估真正的进度状态;当接近项目结束时,出现了大量的集成和测试工作;直到项目结束之前,都不能演示系统的能力。在瀑布模型中,需求或设计中的错误往往只有到了项目后期才能够被发现,对于项目风险的控制能力较弱,从而导致项目常常延期完成,开发费用超出预算。

5.2.2 增量模型(Incremental Model)

增量模型融合了瀑布模型的基本成分和原型实现的迭代特征,它假设可以将需求分段为一系列增量产品,每一增量可以分别开发。该模型采用随着日程时间的进展而交错的线性序列,每一个线性序列产生软件的一个可发布的“增量”,如图 5-4 所示。当使用增量模型时,第 1 个增量往往是核心的产品。客户对每个增量的使用和评估都作为下一个增量发布的新特征和功能,这个过程在每一个增量发布后不断重复,直到产生了最终的完善产品。增量模型强调每一个增量均发布一个可操作的产品。

增量模型作为瀑布模型的一个变体,具有瀑布模型的所有优点。此外,它还有以下优点:第一个可交付版本所需要的成本和时间很少;开发由增量表示的小系统所承担的风险不大;由于很快发布了第一个版本,因此可以减少用户需求的变更;运行增量投资,即在项目开始时,可以仅对一个或两个增量投资。

增量模型有以下不足之处:如果没有对用户的变更要求进行规划,那么产生的初始增量可能会造成后来增量的不稳定;如果需求不像早期思考的那样稳定和完整,那么一些增量就可能需要重新开发,重新发布;管理发生的成本、进度和配置的复杂性可能会超出组织的能力。

5.2.3 演化模型(Evolutionary Model)

软件类似于其他复杂的系统,会随着时间的推移而演化。在开发过程中,常常会面临以下情形:商业和产品需求经常发生变化,直接导致最终产品难以实现;严格的交付时间使得开发团队不可能圆满地完成软件产品,但是必须交付功能有限的版本以应对竞争或商业压力;很好地理解了核心产品和系统需求,但是产品或系统扩展的细节问题却没有定义。在上述情况和类似情况下,软件开发人员需要一种专门应对不断演变的软件产品的过程模型。

演化模型是迭代的过程模型,使得软件开发人员能够逐步开发出更完整的软件版本。演化模型特别适用于对软件需求缺乏准确认识的情况。典型的演化模型有原型模型和螺旋模型等。

1. 原型模型(Prototype Model)

并非所有的需求都能够预先定义,大量的实践表明,在开发初期很难得到一个完整的、准确的需求规格说明。这主要是由于客户往往不能准确地表达对未来系统的全面要求,开发者对要解决的应用问题模糊不清,以至于形成的需求规格说明常常是不完整的、不准确的,有时甚至是有歧义的。此外,在整个开发过程中,用户可能会产生新的要求,导致需求的变更。而瀑布模型难以适应这种需求的不确定性和变化,于是出现了快速原型(Rapid Prototype)这种新的开发方法。原型方法比较适合于用户需求不清、需求经常变化的情况。当系统规模不是很大也不太复杂时,采用该方法比较好。

原型是预期系统的一个可执行版本,反映了系统性质的一个选定的子集。一个原型不必满足目标软件的所有约束,其目的是能快速、低成本地构建原型。当然,能够采用原型方法是因为开发工具的快速发展,使得能够迅速地开发出一个让用户看得见、摸得着的系统框架。这样,对于计算机不是很熟悉的用户就可以根据这个框架提出自己的需求。开发原型系统首先确定用户需求,开发初始原型,然后征求用户对初始原型的改进意见,并根据意见修改原型。原型模型如图 5-5 所示。

原型模型开始于沟通,其目的是定义软件的总体目标,标识需求,然后快速制订原型开发的计划,确定原型的目标和范围,采用快速射击的方式对其进行建模,并构建原型。被开发的原型应交付给客户使用,并收集客户的反馈意见,这些反馈意见可在下一轮中对原型进行改进。在前一个原型需要改进,或者需要扩展其范围的时候,进入下一轮原型的迭代开发。

根据使用原型的目的不同,原型可以分为探索型原型、实验型原型和演化型原型 3 种。探索型原型的目的是要弄清目标的要求,确定所希望的特性,并探讨多种方案的可行性。实验型原型的目的是验证方案或算法的合理性,是在大规模开发和实现前,用于考查方案是否合适、规格说明是否可靠等。演化型原型的目的是将原型作为目标系统的一部分,通过对原型的多次改进,逐步将原型演化成最终的目标系统。

2. 螺旋模型(Spiral Model)

对于复杂的大型软件,开发一个原型往往达不到要求。螺旋模型将瀑布模型和演化模型结合起来,加入了两种模型均忽略的风险分析,弥补了这两种模型的不足。

螺旋模型将开发过程分为几个螺旋周期,每个螺旋周期大致和瀑布模型相符合,如图 5-6 所示。每个螺旋周期分为如下 4 个工作步骤。

(1)制订计划。确定软件的目标,选定实施方案,明确项目开发的限制条件。

(2)风险分析。分析所选的方案,识别风险,消除风险。

(3)实施工程。实施软件开发,验证阶段性产品。

(4)用户评估。评价开发工作,提出修正建议,建立下一个周期的开发计划。

螺旋模型强调风险分析,使得开发人员和用户对每个演化层出现的风险有所了解,从而做出应有的反应。因此,该模型特别适用于庞大、复杂并且具有高风险的系统。

与瀑布模型相比,螺旋模型支持用户需求的动态变化,为用户参与软件开发的所有关键决策提供了方便,有助于提高软件的适应能力,并且为项目管理人员及时调整管理决策提供了便利,从而降低了软件开发的风险。在使用螺旋模型进行软件开发时,需要开发人员具有相当丰富的风险评估经验和专门知识。另外,过多的迭代次数会增加开发成本,延迟提交时间。

5.2.4 喷泉模型(Water Fountain Model)

喷泉模型是一种以用户需求为动力,以对象作为驱动的模型,适合于面向对象的开发方法。它克服了瀑布模型不支持软件重用和多项开发活动集成的局限性。

喷泉模型使开发过程具有迭代性和无间隙性,如图 5-7 所示。迭代意味着模型中的开发活动常常需要重复多次,在迭代过程中不断地完善软件系统。无间隙是指在开发活动(如分析、设计、编码)之间不存在明显的边界,也就是说,它不像瀑布模型那样,在需求分析活动结束后才开始设计活动,在设计活动结束后才开始编码活动,而是允许各开发活动交叉、迭代地进行。

喷泉模型的各个阶段没有明显的界线,开发人员可以同步进行。其优点是可以提高软件项目的开发效率,节省开发时间。由于喷泉模型在各个开发阶段是重叠的,在开发过程中需要大量的开发人员,不利于项目的管理。此外,这种模型要求严格管理文档,使得审核的难度加大。

5.2.5 基于构件的开发模型(Component-based Development Model)

基于构件的开发是指利用预先包装的构件来构造应用系统。构件可以是组织内部开发的构件,也可以是商品化成品(Commercial Off-The-Shelf,COTS)软件构件。基于构件的开发模型具有许多螺旋模型的特点,它本质上是演化模型,需要以迭代方式构建软件。其不同之处在于,基于构件的开发模型采用预先打包的软件构件开发应用系统。

一种基于构建的开发模型如图 5-8 所示,包括领域工程和应用系统工程两部分。

领域工程的目的是构建领域模型、领域基准体系结构和可复用构件库。为达到此目的,首先要进行领域分析,分析该领域中各种应用系统的公共部分或相似部分,构建领域模型和领域基准体系结构,表示领域的候选构件,对候选构件进行可变性分析,以适应多个应用系统的需要,最后构建可复用构件,经严格测试和包装后存入可复用构件库。

应用系统工程的目的是使用可复用构件组装应用系统。首先进行应用系统分析,设计应用系统的体系结构,标识应用系统所需的构件,然后在可复用构件库中查找合适的构件(也可以购买第三方构件),这些选取的构件需进行特化,必要时做适当的修改,以适应该应用系统的需要。对于那些未找到合适构件的应用部分,仍需单独开发,并将其与特化修改后的构件组装成应用系统。在此过程中,还需要对可复用构件的复用情况进行评价,以改进可复用构件,同时对新开发的部分进行评价,并向领域工程推荐候选构件。

5.2.6 形式化方法模型(Formal Methods Model)

形式化方法是建立在严格数学基础上的一种软件开发方法,其主要活动是生成计算机软件形式化的数学规格说明。

形式化方法用严格的数学语言和语义描述功能规约和设计规约,通过数学的分析和推导,易于发现需求的歧义性、不完整性和不一致性,易于对分析模型、设计模型和程序进行验证。通过数学的演算,使得从形式化功能规约到形式化设计规约,以及从形式化设计规约到程序代码的转换成为可能。这种方法的一个变形是净室软件工程。

5.2.7 统一过程(UP)模型

统一过程模型是一种“用例和风险驱动,以架构为中心,迭代并且增量”的开发过程,由 UML 方法和工具支持。迭代的意思是将整个软件开发项目划分为许多个小的“袖珍项目”,每个“袖珍项目”都包含正常软件项目的所有元素:计划、分析和设计、构造、集成和测试,以及内部和外部发布。

统一过程定义了 4 个技术阶段及其制品。

1)起始阶段(Inception Phase) 起始阶段专注于项目的初创活动,产生的主要工作产品有构想文档(Vision Document)、初始用例模型、初始项目术语表、初始业务用例、初始风险评估、项目计划(阶段及迭代)、业务模型以及一个或多个原型(需要时)。

2)精化阶段(Elaboration Phase) 精华阶段在理解了最初的领域范围之后进行需求分析和架构演进,产生的主要工作产品有用例模型、补充需求(包括非功能需求)、分析模型、软件体系结构描述、可执行的软件体系结构原型、初步的设计模型、修订的风险列表、项目计划(包括迭代计划、调整的工作流、里程碑和技术工作产品)以及初始用户手册。

3)构建阶段(Construction Phase) 构建阶段关注系统的构建,产生实现模型,产生的主要工作产品有设计模型、软件构件、集成的软件增量、测试计划及步骤、测试用例以及支持文档(用户手册、安装手册和对于并发增量的描述)。

4)移交阶段(Transition Phase) 移交阶段关注于软件提交方面的工作,产生软件增量,产生的主要工作产品有提交的软件增量、$\beta$ 测试报告和综合用户反馈。

每次迭代产生包括最终系统的部分完成的版本和任何相关的项目文档的基线,通过逐步迭代基线之间相互构建,直到完成最终系统。在每个迭代中有 5 个核心工作流:捕获系统应该做什么的需求工作流,精化和结构化需求的分析工作流,在系统构架内实现需求的设计工作流,构造软件的实现工作流,验证实现是否如期望那样工作的测试工作流。随着 UP 的阶段进展,每个核心工作流的工作量发生了变化。4 个技术阶段由主要里程碑所终止。

  • 初始阶段:生命周期目标。
  • 精化阶段:生命周期架构。
  • 构建阶段:初始运作功能。
  • 移交阶段:产品发布。

统一过程的典型代表是 RUP(Rational Unified Process)。RUP 是 UP 的商业扩展,完全兼容 UP,但比 UP 更完整、更详细。

5.2.8 敏捷方法(Agile Development)

敏捷开发的总体目标是通过“尽可能早地、持续地对有价值的软件的交付”使客户满意。通过在软件开发过程中加入灵活性,敏捷方法使用户能够在开发周期的后期增加或改变需求。

敏捷过程的典型方法有很多,每一种方法基于一套原则,这些原则实现了敏捷方法所宣称的理念(敏捷宣言)。

1. 极限编程(XP)

XP 是一种轻量级(敏捷)、高效、低风险、柔性、可预测的、科学的软件开发方式。它由价值观、原则、实践和行为 4 个部分组成,彼此相互依赖、关联,并通过行为贯穿于整个生存周期。

  • 4 大价值观:沟通、简单性、反馈和勇气。
  • 5 个原则:快速反馈、简单性假设、逐步修改、提倡更改和优质工作。
  • 12 个最佳实践:计划游戏(快速制定计划、随着细节的不断变化而完善)、小型发布(系统的设计要能够尽可能早地交付)、隐喻(找到合适的比喻传达信息)、简单设计(只处理当前的需求,使设计保持简单)、测试先行(先写测试代码,然后再编写程序)、重构(重新审视需求和设计,重新明确地描述它们以符合新的和现有的需求)、结队编程、集体代码所有制、持续集成(可以按日甚至按小时为客户提供可运行的版本)、每周工作 40 个小时、现场客户和编码标准。

2. 水晶法(Crystal)

水晶法认为每一个不同的项目都需要一套不同的策略、约定和方法论,认为人对软件质量有重要的影响,因此随着项目质量和开发人员素质的提高,项目和过程的质量也随之提高。通过更好地交流和经常性的交付,软件生产力得到提高。

3. 并列争求法(Scrum)

并列争求法使用迭代的方法,其中,把每 30 天一次的迭代称为一个“冲刺”,并按需求的优先级别来实现产品。多个自组织和自治的小组并行地递增实现产品。协调是通过简短的日常情况会议来进行,就像橄榄球中的“并列争球”。

4. 自适应软件开发(ASD)

ASD 有 6 个基本的原则:有一个使命作为指导;特征被视为客户价值的关键点;过程中的等待是很重要的,因此“重做”与“做”同样关键;变化不被视为改正,而是被视为对软件开发实际情况的调整;确定的交付时间迫使开发人员认真考虑每一个生产的版本的关键需求;风险也包含其中。

5. 敏捷统一过程(AUP)

敏捷统一过程(Agile Unified Process,AUP)采用“在大型上连续”以及在“在小型上迭代”的原理来构建软件系统。采用经典的 UP 阶段性活动(初始、精化、构建和转换),提供了一系列活动,能够使团队为软件项目构想出一个全面的过程流。在每个活动里,一个团队迭代使用敏捷,并将有意义的软件增量尽可能快地交付给最终用户。每个 AUP 迭代执行以下活动:

  • 建模。建立对商业和问题域的模型表述,这些模型“足够好”即可,以便团队继续前进。
  • 实现。将模型翻译成源代码。
  • 测试。像 XP 一样,团队设计和执行一系列的测试来发现错误以保证源代码满足需求。
  • 部署。对软件增量的交付以及获取最终用户的反馈。
  • 配置及项目管理。着眼于变更管理、风险管理以及对团队的任一制品的控制。项目管理追踪和控制开发团队的工作进展并协调团队活动。
  • 环境管理。协调标准、工具以及适用于开发团队的支持技术等过程基础设施。

5.3 需求分析

5.3.1 软件需求

在进行需求获取之前,首先要明确需要获取什么,也就是需求包含哪些内容。软件需求是指用户对目标软件系统在功能、行为、性能、设计约束等方面的期望。通常,这些需求包括功能需求、性能需求、用户或人的因素、环境需求、界面需求、文档需求、数据需求、资源使用需求、安全保密需求、可靠性需求、软件成本消耗与开发进度需求等,并预先估计以后系统可能达到的目标。此外,还需要注意其他非功能性的需求。具体内容如下。

(1)功能需求。考虑系统要做什么,在何时做,在何时以及如何修改或升级。

(2)性能需求。考虑软件开发的技术性指标。例如,存储容量限制、执行速度、响应时间及吞吐量。

(3)用户或人的因素。考虑用户的类型。例如,各种用户对使用计算机的熟练程度,需要接受的训练,用户理解、使用系统的难度,用户错误操作系统的可能性等。

(4)环境需求。考虑未来软件应用的环境,包括硬件和软件。对硬件设备的需求包括机型、外设、接口、地点、分布、湿度、磁场干扰等;对软件的需求包括操作系统、网络、数据库等。

(5)界面需求。考虑来自其他系统的输入,到其他系统的输出,对数据格式的特殊规定,对数据存储介质的规定。

(6)文档需求。考虑需要哪些文档,文档针对哪些读者。

(7)数据需求。考虑输入、输出数据的格式,接收、发送数据的频率,数据的准确性和精度,数据流量,数据需保持的时间。

(8)资源使用需求。考虑软件运行时所需要的数据、其他软件、内存空间等资源;软件开发、维护所需的人力、支撑软件、开发设备等。

(9)安全保密要求。考虑是否需要对访问系统或系统信息加以控制,隔离用户数据的方法,用户程序如何与其他程序和操作系统隔离以及系统备份要求等。

(10)可靠性要求。考虑系统的可靠性要求,系统是否必须检测和隔离错误;出错后,重启系统允许的时间等。

(11)软件成本消耗与开发进度需求。考虑开发是否有规定的时间表,软/硬件投资有无限制等。

(12)其他非功能性要求。如采用某种开发模式,确定质量控制标准、里程碑和评审、验收标准、各种质量要求的优先级等,以及可维护性方面的要求。

这些需求可以来自于用户(实际的和潜在的)、用户的规约、应用领域的专家、相关的技术标准和法规;也可以来自于原有的系统、原有系统的用户、新系统的潜在用户;甚至还可以来自于竞争对手的产品。

5.3.2 需求分析原则

需求分析过程的具体实现有不同的分析方法,这些方法有自己独特的特点。然而,这些分析方法都遵循一组操作原则。

(1)必须能够表示和理解问题的信息域。

(2)必须能够定义软件将完成的任务。

(3)必须能够表示软件的行为(作为外部事件的结束)。

(4)必须划分描述数据、功能和行为的模型,从而可以分层次地揭示细节。

(5)分析过程应该从要素信息移向细节信息。

通过应用这些原则,分析人员将能系统地处理问题。检查信息域可以更完整地理解功能,通过模型可以更简洁地交流功能和行为的特征,应用抽象与分解可减少问题的复杂度。

5.3.3 需求工程

需求工程是一个不断反复的需求定义、文档记录、需求演进的过程,并最终在验证的基础上冻结需求。需求工程可以细分为需求获取、需求分析与协商、系统建模、需求规约、需求验证以及需求管理 6 个阶段。

1. 需求获取

在需求获取阶段,系统分析人员通过与用户的交流、对现有系统的观察以及对任务进行分析确定系统或产品范围的限制性描述、与系统或产品有关的人员及特征列表、系统的技术环境的描述、系统功能的列表及应用于每个需求的领域限制、一组描述不同运行条件下系统或产品使用状况的应用场景以及为更好地定义需求而开发的原型。需求获取的工作产品为进行需求分析提供了基础。

2. 需求分析与协商

需求获取结束后,分析活动对需求进行分类组织,分析每个需求与其他需求的关系,以检查需求的一致性、重叠和遗漏的情况,并根据用户的需要对需求进行排序。在需求获取阶段,经常出现以下问题:用户提出的要求超出软件系统可以实现的范围或实现能力;不同的用户提出了相互冲突的需求;每个用户在提出自己的需求时都会说“这是至关重要的”,所以系统分析人员需要通过一个谈判过程来调解这些冲突。

3. 系统建模

建模技术可以通过合适的工具和符号系统地描述需求。建模工具的使用在用户和系统分析人员之间建立了统一的语言和理解的“桥梁”,同时系统分析人员借助建模技术对获取的需求信息进行分析,排除错误和弥补不足,确保需求文档正确地反映用户的真实意图。常用的分析和建模方法有面向数据流方法、面向数据结构方法和面向对象方法。

在观察和研究某一事物或某一系统时,常常把它抽象为一个模型。创建模型是需求分析阶段的重要活动。模型以一种简洁、准确、结构清晰的方式系统地描述了软件需求,从而帮助分析员理解系统的信息、功能和行为,使得需求分析任务更容易实现,结果更系统化,同时易于发现用户描述中的模糊性和不一致性;模型将成为软件设计的基础,为设计者提供软件要素的表示视图,这些表示可被转化到实现的语境中去;更重要的是,模型还可以在分析人员和用户之间建立更快捷的沟通方式,使两者可以用相同的工具分析和理解问题。

在软件需求分析阶段所创建的模型,要着重于描述系统要做什么,而不是如何去做,目标软件的模型不应涉及软件的实现细节。通常情况下,分析人员使用图形符号来创建模型,将信息、处理、系统行为和其他相关特征描述为各种可识别的符号,同时与符号图形相配套,并辅以文字描述,可使用自然语言或某种特殊的专门用于描述需求的语言来提供辅助的信息描述。

目前,已存在的多种需求分析方法引用了不同的分析策略,常用的分析方法有以下两种:

(1)面向数据流的结构化分析方法(SA)。

(2)面向对象的分析方法(OOA)。

4. 需求规约

软件需求规约是分析任务的最终产物,通过建立完整的信息描述、详细的功能和行为描述、性能需求和设计约束的说明、合适的验收标准,给出对目标软件的各种需求。需求规约作为用户和开发者之间的一个协议,在之后的软件工程各阶段发挥重要的作用。软件需求规约中通常包含以下内容。

(1)引言。引言陈述软件目标,在基于计算机的系统语境内进行描述。

(2)信息描述。信息描述给出软件必须解决的问题的详细描述,记录信息内容、信息流和信息结构。

(3)功能描述。功能描述用来描述解决问题所需要的每个功能。其中包括为每个功能说明一个处理过程;叙述设计约束;叙述性能特征;用一个或多个图形形象地表示软件的整体结构和软件功能与其他系统元素间的相互影响。

(4)行为描述。行为描述用于描述作为外部事件和内部产生的控制特征的软件操作。

(5)检验标准。检验标准描述检验系统成功的标志,即对系统进行什么样的测试,得到什么样的结果,就表示系统已经成功实现了。检验标准是“确认测试”的基础。

(6)参考书目。参考书目包含了对所有和该软件相关的文档的引用,其中包括其他的软件工程文档、技术参考文献、厂商文献和标准。

(7)附录。附录包含了规约的补充信息,表格数据、算法的详细描述、图表和其他资料。

5. 需求验证

需求验证作为需求开发阶段工作的复查手段,其目的是要检验需求功能的正确性、完整性和清晰性,是否能够反映用户的意愿,由于需求的变化往往使系统的设计和实现也跟着改变,所以由需求问题引起系统变更的成本比修改设计或代码错误的成本高得多。因此,为保证软件需求定义的质量,评审应指定专门的人员负责,并按规程严格进行。

需求验证需要对需求文档中定义的需求执行多种检查。开发团队要对用户需求进行“遍访”,逐条解释需求含义;评审团队应该检查需求的有效性、一致性和作为一个整体的完备性。评审人员评审时往往需要检查以下内容:

(1)系统定义的目标是否与用户的要求一致。

(2)系统需求分析阶段提供的文档资料是否齐全;文档中的描述是否完整、清晰、准确地反映了用户要求。

(3)被开发项目的数据流与数据结构是否确定且充足。

(4)主要功能是否已包括在规定的软件范围之内,是否都已充分说明。

(5)设计的约束条件或限制条件是否符合实际。

(6)开发的技术风险是什么。

(7)是否详细地制定了检验标准,它们能否对系统定义进行确认。

为了保证软件需求定义的质量,验证应该由专门的人员来负责,按照规定严格进行。除分析人员之外,还要有用户,开发部门的管理者,软件设计、实现、测试的人员参加。评审结束应有负责人的结论意见和签字。

想要判断一组需求是否符合用户的需要是很困难的。用户需要描述出系统的操作过程,构想出如何让系统加入到他们的工作中去,这种抽象对于一个普通用户来说比较困难。所以,需求验证也不可能发现所有的需求问题。在需求验证之后,对遗漏的补充以及对错误理解的更正是不可避免的,因此需要进行需求管理。

6. 需求管理

在实际的开发过程中,获取、分析、建模、编写规约和验证这些需求开发活动通常是交叉、递增和反复地进行。而且,软件系统的需求会变更,这些变更不仅会存在于项目开发过程,而且会出现在项目已经付诸应用之后。软件需求管理是一组用于帮助项目组在项目进展中的任何时候去标识、控制和跟踪需求的活动,对需求工程所有相关活动的规划和控制。换句话说,需求管理就是一种获取、组织并记录系统需求的系统化方案,以及一个使用户与项目团队对不断变更的系统需求达成并保持一致的过程。

在需求管理中,每个需求被赋予唯一的标识符,一旦标识出需求,就可以为需求建立跟踪表,每个跟踪表标识需求与其他需求或设计文档、代码、测试用例的不同版本间的关系。例如,特征跟踪表,记录需求如何与产品或系统特征相关联;来源跟踪表,记录每个需求的来源;依赖跟踪表,描述需求间如何关联等。

这些跟踪表可以用于需求跟踪。在整个开发过程中,进行需求跟踪的目的是为了建立和维护从用户需求开始到测试之间的一致性与完整性,确保所有的实现是以用户需求为基础,所有的输出符合用户需求,并且全面覆盖了用户需求。需求跟踪有两种方式:正向跟踪和逆向跟踪。其中,正向跟踪以用户需求为切入点,检查《需求规约》中的每个需求是否都能在后继工作产品中找到对应点;逆向跟踪检查设计文档、代码、测试用例等工作产品是否都能在《需求规约》中找到出处。

5.4 系统设计

在系统分析阶段,我们已经搞清楚了软件“做什么”的问题,并把这些需求通过规格说明书描述了出来,这也是目标系统的逻辑模型。进入设计阶段,要把软件“做什么”的逻辑模型转换成“怎么做”的物理模型,即着手实现软件系统的需求。

系统设计的主要目的就是为系统制定蓝图,在各种技术和实施方法中权衡利弊,精心设计,合理地使用各种资源,最终勾画出新系统的详细设计方案。

系统设计的主要内容包括新系统总体结构设计、代码设计、输出设计、输入设计、处理过程设计、数据存储设计、用户界面设计和安全控制设计等。

目前,已存在的多种系统设计方法,常用的设计方法有以下两种。

(1)面向数据流的结构化设计方法(SD)。

(2)面向对象的分析方法(OOD)。

系统设计的基本任务大体上可以分为概要设计和详细设计两个步骤。

5.4.1 概要设计

1)设计软件系统总体结构

其基本任务是采用某种设计方法,将一个复杂的系统按功能划分成模块;确定每个模块的功能;确定模块之间的调用关系;确定模块之间的接口,即模块之间传递的信息;评价模块结构的质量。

软件系统总体结构的设计是概要设计关键的一步,直接影响到下一个阶段详细设计与编码的工作。软件系统的质量及一些整体特性都在软件系统总体结构的设计中决定。

2)数据结构及数据库设计

(1)数据结构的设计。逐步细化的方法也适用于数据结构的设计。在需求分析阶段,已经通过数据字典对数据的组成、操作约束和数据之间的关系等方面进行了描述,确定了数据的结构特性,在概要设计阶段要加以细化,详细设计阶段则规定具体的实现细节。在概要设计阶段,宜使用抽象的数据类型。

(2)数据库的设计。数据库的设计是指数据存储文件的设计,主要进行以下几方面设计。

① 概念设计。在数据分析的基础上,采用自底向上的方法从用户角度进行视图设计,一般用 E-R 模型来表述数据模型。E-R 模型既是设计数据库的基础,也是设计数据结构的基础。

② 逻辑设计。E-R 模型是独立于数据库管理系统(DBMS)的,要结合具体的 DBMS 特征来建立数据库的逻辑结构。

③ 物理设计。对于不同的 DBMS,物理环境不同,提供的存储结构与存取方法各不相同。物理设计就是设计数据模式的一些物理细节,如数据项存储要求、存取方法和索引的建立等。

本节对数据库技术不做详细讨论,详细内容参见本书第 7 章。

3)编写概要设计文档

文档主要有概要设计说明书、数据库设计说明书、用户手册以及修订测试计划。

4)评审

对设计部分是否完整地实现了需求中规定的功能、性能等要求,设计方法的可行性,关键的处理及内外部接口定义的正确性、有效性、各部分之间的一致性等都一一进行评审。

5.4.2 详细设计

(1)对每个模块进行详细的算法设计,用某种图形、表格和语言等工具将每个模块处理过程的详细算法描述出来。

(2)对模块内的数据结构进行设计。

(3)对数据库进行物理设计,即确定数据库的物理结构。

(4)其他设计。根据软件系统的类型,还可能要进行以下设计。

① 代码设计。为了提高数据的输入、分类、存储和检索等操作,节约内存空间,对数据库中某些数据项的值要进行代码设计。

② 输入/输出格式设计。

③ 用户界面设计。

(5)编写详细设计说明书。

(6)评审。对处理过程的算法和数据库的物理结构都要评审。

系统设计的结果是一系列的系统设计文件,这些文件是物理实现一个信息系统(包括硬件设备和编制软件程序)的重要基础。

5.5 系统测试

5.5.1 系统测试与调试

1. 系统测试的意义、目的及原则

系统测试是为了发现错误而执行程序的过程,成功的测试是发现了至今尚未发现的错误的测试。

测试的目的就是希望能以最少的人力和时间发现潜在的各种错误和缺陷。用户应根据开发各阶段的需求、设计等文档或程序的内部结构精心设计测试实例,并利用这些实例来运行程序,以便发现错误的过程。

信息系统测试应包括软件测试、硬件测试和网络测试。硬件测试、网络测试可以根据具体的性能指标进行,此处所说的测试更多的是指软件测试。

系统测试是保证系统质量和可靠性的关键步骤,是对系统开发过程中的系统分析、系统设计和实施的最后复查。根据测试的概念和目的,在进行信息系统测试时应遵循以下基本原则。

(1)应尽早并不断地进行测试。测试不是在应用系统开发完之后才进行的。由于原始问题的复杂性、开发各阶段的多样性以及参加人员之间的协调等因素,使得在开发的各个阶段都有可能出现错误。因此,测试应贯穿在开发的各个阶段,应尽早纠正错误,消除隐患。

(2)测试工作应该避免由原开发软件的人或小组承担,一方面,开发人员往往不愿否认自己的工作,总认为自己开发的软件没有错误;另一方面,开发人员的错误很难由本人测试出来,很容易根据自己编程的思路来制定测试思路,具有局限性。测试工作应由专门人员来进行,这样会更客观、更有效。

(3)在设计测试方案时,不仅要确定输入数据,而且要根据系统功能确定预期输出结果。将实际输出结果与预期结果相比较就能发现测试对象是否正确。

(4)在设计测试用例时,不仅要设计有效、合理的输入条件,也要包含不合理、失效的输入条件。在测试的时候,人们往往习惯按照合理的、正常的情况进行测试,而忽略了对异常、不合理、意想不到的情况进行测试,而这可能就是隐患。

(5)在测试程序时,不仅要检验程序是否做了该做的事,还要检验程序是否做了不该做的事。多余的工作会带来副作用,影响程序的效率,有时会带来潜在的危害或错误。

(6)严格按照测试计划来进行,避免测试的随意性。测试计划应包括测试内容、进度安排、人员安排、测试环境、测试工具和测试资料等。严格地按照测试计划可以保证进度,使各方面都得以协调进行。

(7)妥善保存测试计划、测试用例,作为软件文档的组成部分,为维护提供方便。

(8)测试例子都是精心设计出来的,可以为重新测试或追加测试提供方便。当纠正错误、系统功能扩充后,都需要重新开始测试,而这些工作的重复性很高,可以利用以前的测试用例,或在其基础上修改,然后进行测试。

2. 测试过程

测试是开发过程中的一个独立且非常重要的阶段。测试过程基本上与开发过程平行进行。

一个规范化的测试过程通常包括以下基本的测试活动。

(1)制订测试计划。在制订测试计划时,要充分考虑整个项目的开发时间和开发进度以及一些人为因素和客观条件等,使得测试计划是可行的。测试计划的内容主要有测试的内容、进度安排、测试所需的环境和条件、测试培训安排等。

(2)编制测试大纲。测试大纲是测试的依据,它明确、详尽地规定了在测试中针对系统的每一项功能或特性所必须完成的基本测试项目和测试完成的标准。

(3)根据测试大纲设计和生成测试用例,产生测试设计说明文档。其内容主要有被测项目、输入数据、测试过程和预期输出结果等。

(4)实施测试。测试的实施阶段是由一系列的测试周期组成的。在每个测试周期中,测试人员和开发人员将依据预先编制好的测试大纲和准备好的测试用例对被测软件或设备进行完整的测试。

(5)生成测试报告。测试完成后要形成相应的测试报告,主要对测试进行概要说明,列出测试的结论,指出缺陷和错误。另外,给出一些建议,如可采用的修改方法,各项修改预计的工作量及修改的负责人员。

5.5.2 传统软件的测试策略

软件测试策略将软件测试用例的设计方法集成到一系列经过周密计划的步骤中,从而使软件构造成功地完成。测试策略提供以下方面的路径图:描述将要进行的测试步骤,这些步骤计划和执行的时机,需要多少工作量、时间和资源。因此,任何测试策略都必须包含测试计划、测试用例设计、测试执行以及结果数据的收集和评估。

软件测试策略应该具有足够的灵活性,以便促进测试方法的制定。同时,它必须足够严格,以便在项目进行过程中对项目进行合理地策划和追踪管理。

有效的软件测试实际上分为 4 步进行,即单元测试、集成测试、确认测试和系统测试。

1. 单元测试

单元测试也称为模块测试,在模块编写完成且无编译错误后就可以进行。单元测试侧重于模块中的内部处理逻辑和数据结构。如果选用机器测试,一般用白盒测试法。这类测试可以对多个模块同时进行。

1)单元测试的测试内容

单元测试主要检查模块的以下 5 个特征。

(1)模块接口。模块的接口保证了测试模块的数据流可以正确地流入、流出。在测试中应检查以下要点:

① 测试模块的输入参数和形式参数在个数、属性、单位上是否一致。

② 调用其他模块时,所给出的实际参数和被调用模块的形式参数在个数、属性、单位上是否一致。

③ 调用标准函数时,所用的参数在属性、数目和顺序上是否正确。

④ 全局变量在各模块中的定义和用法是否一致。

⑤ 输入是否仅改变了形式参数。

⑥ 开/关的语句是否正确。

⑦ 规定的 I/O 格式是否与输入/输出语句一致。

⑧ 在使用文件之前是否已经打开文件或使用文件之后是否已经关闭文件。

(2)局部数据结构。在单元测试中,局部数据结构出错是比较常见的错误,在测试时应重点考虑以下因素。

① 变量的说明是否合适。

② 是否使用了尚未赋值或尚未初始化的变量。

③ 变量的初始值或默认值是否正确。

④ 变量名是否有错(例如拼写错)。

(3)重要的执行路径。在单元测试中,对路径的测试是最基本的任务。由于不能进行穷举测试,需要精心设计测试例子来发现是否有计算、比较或控制流等方面的错误。

① 计算方面的错误。算术运算的优先次序不正确或理解错误;精度不够;运算对象的类型彼此不相容;算法错;表达式的符号表示不正确等。

② 比较和控制流的错误。本应相等的量由于精度造成不相等;不同类型进行比较;逻辑运算符不正确或优先次序错误;循环终止不正确(如多循环一次或少循环一次)、死循环;不恰当地修改循环变量;当遇到分支循环时出口错误等。

(4)出错处理。好的设计应该能预测到出错的条件并且有对出错处理的路径。虽然计算机可以显示出错信息的内容,但仍需要程序员对出错进行处理,保证其逻辑的正确性,以便于用户维护。

(5)边界条件。边界条件的测试是单元测试的最后工作,也是非常重要的工作。软件容易在边界出现错误。

2)单元测试过程

由于模块不是独立运行的程序,各模块之间存在调用与被调用的关系。在对每个模块进行测试时,需要开发两种模块。单元测试环境如图 5-9 所示。

  • 驱动模块。相当于一个主程序,接收测试例子的数据,将这些数据送到测试模块,输出测试结果。
  • 桩模块(也称为存根模块)。桩模块用来代替测试模块中所调用的子模块,其内部可进行少量的数据处理,目的是为了检验入口,输出调用和返回的信息。

提高模块的内聚度可以简化单元测试。如果每个模块只完成一种功能,对于具体模块来讲,所需的测试方案数据会显著减少,而且更容易发现和预测模块中的错误。

2. 集成测试

集成测试就是把模块按系统设计说明书的要求组合起来进行测试。即使所有的模块都通过了测试,在集成之后,仍然可能出现问题:穿过模块的数据丢失;一个模块的功能对其他模块造成有害的影响;各个模块集成起来没有达到预期的功能;全局数据结构出现问题。另外,单个模块的误差可以接受,但模块组合后,可能会出现误差累积,最后累积到不能接受的程度。集成测试是构造软件体系结构的系统化技术,同时也是进行一些旨在发现与接口相关的错误的测试,其目标是利用已通过单元测试的构件建立设计中描述的程序结构。

通常,集成测试有两种方法:一种是非增量集成,分别测试各个模块,再把这些模块组合起来进行整体测试;另一种是增量集成,即以小增量的方式逐步进行构造和测试。非增量式集成可以对模块进行并行测试,能充分利用人力,并加快工程进度。但这种方法容易混乱,出现错误不容易查找和定位。增量式测试的范围一步步扩大,错误容易定位,更易于对接口进行彻底测试,并且可以运用系统化的测试方法。下面讨论一些增量集成策略。

1)自顶向下集成测试

自顶向下集成测试是一种构造软件体系结构的增量方法。模块的集成顺序为从主控模块(主程序)开始,沿着控制层次逐步向下,以深度优先或广度优先的方式将从属于(或间接从属于)主控模块的模块集成到结构中。

如图 5-10 所示,深度优先集成是首先集成位于程序结构中主控路径上的所有构件,也可以根据特定应用系统的特征进行选择。

例如,选择最左边的路径,首先集成构件 $M_1$、$M_2$ 和 $M_5$;其次,集成 $M_8$ 或 $M_6$(若 $M_2$ 的正常运行是必需的),然后集成中间和右边控制路径上的构件。广度优先集成首先沿着水平方向,将属于同一层的构件集成起来。例如图 5-10 中,首先将构件 $M_2$、$M_3$ 和 $M_4$ 集成起来;其次是控制成 $M_5$、$M_6$、$M_7$,依此类推。集成过程可以通过下列 5 个步骤完成。

(1)主控模块用作测试驱动模块,用这些从属于主控模块的所有模块代替桩模块。

(2)依靠所选择的集成方法(即深度优先或广度优先),每次用实际模块替换一个从属桩模块。

(3)在集成每个模块后都进行测试。

(4)在完成每个测试集之后,用实际模块替换另一个桩模块。

(5)可以执行回归测试,以确保没有引入新的错误。

回到第(2)步继续执行此过程,直到完成了整个程序结构的构造。

2)自底向上集成测试

自底向上集成测试就是从原子模块(程序结构的最底层构件)开始进行构造和测试。由于构件是自底向上集成的,在处理时所需要的从属于给定层次的模块总是存在的,因此,没有必要使用桩模块。自底向上集成策略可以利用以下步骤来实现。

(1)连接低层构件以构成完成特定子功能的簇。

(2)编写驱动模块(测试的控制程序)以协调测试用例的输入和输出。

(3)测试簇。

(4)去掉驱动程序,沿着程序结构向上逐步连接簇。

遵循这种模式的集成如图 5-11 所示。连接相应的构件形成簇 1、簇 2 和簇 3,利用驱动模块(图中的虚线框)对每个簇进行测试。簇 1 和簇 2 中的构件从属于模块 $M_a$,去掉驱动模块 $D_1$ 和 $D_2$,将这两个簇直接与 $M_a$ 相连。与之相类似,在簇 3 与 $M_b$ 连接之前去掉驱动模块 $D_3$,最后将 $M_a$ 和 $M_b$ 与构件 $M_c$ 连接在一起。

随着集成向上进行,对单独的测试驱动模块的需求减少。事实上,若程序结构的最上两层是自顶向下集成的,驱动模块的数量可以大大减少,而且簇的集成得到明显简化。

3)回归测试

每当加入一个新模块作为集成测试的一部分时,软件发生变更,建立了新的数据流路径,可能出现新的 I/O,以及调用新的控制逻辑。这些变更可能会使原来可以正常工作的功能产生问题。在集成测试策略的环境下,回归测试是重新执行已测试过的某些子集,以确保变更没有传播不期望的副作用。

回归测试有助于保证变更不引入无意识行为或额外的错误。回归测试可以手工进行,方法是重新执行所有测试用例的子集,或者利用捕捉/回放工具自动执行。捕捉/回放工具使软件工程师能够为后续的回放与比较捕捉测试用例和测试结果。回归测试要执行的测试子集包含以下 3 种测试用例。

随着集成测试的进行,回归测试的数量可能变得相当庞大,因此,应将回归测试用例设计成只包括设计每个主要程序功能的一个或多个错误类的测试。一旦发生变更,对每个软件功能重新执行所有的测试是不切实际的,而且效率很低。

4)冒烟测试

当开发软件产品时,冒烟测试是一种常用的集成测试方法,是时间关键项目的决定性机制,它让软件团队频繁地对项目进行评估。本质上,冒烟测试方法包括下列活动:

(1)将已经转换为代码的软件构件集成到构建中。一个构建包括所有的数据文件、库、可复用的模块以及实现一个或多个产品功能所需的工程化构件。

(2)设计一系列测试以暴露影响构建正确的完成其功能的错误,其目的是为了发现极有可能造成项目延迟的业务阻塞错误。

(3)每天将该构建与其他构建及整个软件产品(以其当前形势)集成起来进行冒烟测试。这种集成方法可以自顶向下,也可以自底向上。

3. 确认测试

确认测试始于集成测试的结束,那时已测试完单个构件,软件已组装成完整的软件包,且接口错误已被发现和改正。在进行确认测试或系统级测试时,传统软件、面向对象软件及WebApp之间的差别已经消失,测试集中于用户可见的动作和用户可识别的系统输出。

1)确认测试准则

软件确认是通过一系列表明与软件需求相符合的测试而获得的。测试计划列出将要执行的测试类,测试规程定义了特定的测试用例,设计的特定测试用例用于确保满足所有的功能需求,具有所有的行为特征,所有内容都准确无误且正确显示,达到所有的性能需求,文档是正确可用的,且满足其他需求(如可移植性、兼容性、错误恢复和可维护性)。

执行每个确认测试用例之后,存在下面两种可能条件之一:①功能或性能特征符合需求规格说明,可以接受;②发现了与规格说明的偏差,创建缺陷列表。在项目的这个阶段发现的错误或偏差很难在预定的交付期之前得到改正。此时往往必须与客户进行协商,确定解决缺陷的方法。

2)配置评审

确认过程的一个重要成分是配置评审,主要是检查软件(源程序、目标程序)、文档(包括面向开发和用户的文档)和数据(程序内部的数据或程序外部的数据)是否齐全以及分类是否有序。确保文档、资料的正确和完善,以便维护阶段使用。

3)$\alpha$ 测试与 $\beta$ 测试

当为客户开发软件时,执行一系列验收测试能使客户确认所有的需求。验收测试是由最终用户而不是软件工程师进行的,它的范围从非正式的”测试驱动”直到有计划地、系统地进行一系列测试。

若将软件开发为产品,由多个用户使用,让每个用户都进行正式的验收测试是不切实际的。多数软件开发者使用被称为 $\alpha$ 测试与 $\beta$ 测试的过程,以期查找到似乎只有最终用户才能发现的错误。

$\alpha$ 测试是由有代表性的最终用户在开发者的场所进行。软件在自然的环境下使用,开发者站在用户的后面观看,并记录错误和使用问题。$\alpha$ 测试在受控的环境下进行。

$\beta$ 测试在一个或多个最终用户场所执行。与 $\alpha$ 测试不同,开发者通常不在场,因此,$\beta$ 测试是在不被开发者控制的环境下软件的”现场”应用。最终用户记录测试过程中遇见的所有问题(现实存在的或想象的),并定期地报告给开发者。接到 $\beta$ 测试的问题报告之后,开发人员对软件进行修改,然后准备向最终用户发布软件产品。

$\beta$ 测试的一种变体称为客户验收测试,有时是按照合同交付给客户时进行的。客户执行一系列的特定测试,试图在从开发者那里接收软件之前发现错误。在某些情况下(例如,大公司或政府系统),验收测试可能是非常正式的,可能会测试很多天,甚至几个星期。

4. 系统测试

系统测试是将已经确认的软件、计算机硬件、外设和网络等其他因素结合在一起,进行信息系统的各种集成测试和确认测试,其目的是通过与系统的需求相比较,发现所开发的系统与用户需求不符或矛盾的地方。

1)恢复测试

多数基于计算机的系统必须从错误中恢复并在一定的时间内重新运行。在有些情况下,系统必须是容错的,也就是说,处理错误绝不能使整个系统功能都停止。而在有些情况下,系统的错误必须在特定的时间内或严重的经济危害发生之前得到改正。

恢复测试是一种系统测试,通过各种方式强制地让系统发生故障,并验证能否按照要求从故障中恢复过来,并在约定的时间内开始事务处理,而且不对系统造成任何伤害。如果系统的恢复是自动的(由系统自动完成),需要重新验证初始化、检查点和数据恢复等是否正确。如果恢复需要人工干预,就要对恢复的平均时间进行评估并判断它是否在允许的范围内。

2)安全性测试

任何管理敏感信息或能够对个人造成不正当伤害(或带来好处)的计算机系统都是非法入侵的目标。安全性测试验证建立在系统内的保护机制是否能够实际保护系统不受非法入侵。

在安全性测试过程中,测试人员模拟非法入侵者,采用各种方法冲破防线。系统安全性设计准则是使非法入侵者所花费的代价大于攻破系统之后获取信息的价值,此时非法入侵已无利可图。

3)压力测试

压力测试要求以非正常的数量、频率或容量等方式执行系统。例如:在平均每秒出现1~2次中断的情况下,可以设计每秒产生10次中断的测试用例;将输入数据的量提高一个数量级以确定输入功能将如何反应;执行需要最大内存或其他资源的测试用例;设计可能在实际的运行系统中产生惨败的测试用例;创建可能会过多查找磁盘驻留数据的测试用例。从本质上来说,压力测试者是在试图破坏程序。

压力测试的一个变体称为敏感性测试。在一些情况下(最常见的是在数学算法中),包含在有效数据界限之内的一小部分数据可能会引起极端处理情况,甚至是错误处理或性能的急剧下降。敏感性测试试图在有效输入类中发现会引发系统不稳定或错误处理的数据组合。

4)性能测试

对于实时和嵌入式系统,提供所需功能但不符合性能需求的软件是不能接受的。性能测试用来测试软件在集成环境中的运行性能。在测试过程中的任何步骤都可以进行性能测试。即使是在单元级,也可以在执行测试时评估单个模块的性能。然而,只有当整个系统的所有成分完全集成指挥时才能确定系统的真实性能。

性能测试经常与压力测试一起进行,且常需要硬件和软件工具。也就是说,以严格的方式测量资源(例如,处理器周期)的利用往往是必要的。当有运行间歇或时间发生时,外部工具可以监测到,并可定期监测采样机的状态。通过检测系统,测试人员可以发现导致效率降低或系统故障的情形。

5)部署测试

在很多情况下,软件必须在多种平台及操作系统环境中运行。有时也将部署测试称为配置测试,是在软件将要运行的每一种环境中测试软件。另外,部署测试检查客户将要使用的所有安装程序及专业安装软件,并检查用于向最终用户介绍软件的所有文档。

5.5.3 测试面向对象软件

对于面向对象软件,测试的基本目标仍然是在现实的时间范围内利用可控的工作量找出尽可能多的错误,但是其本质特征的不同使得测试策略和技术也发生了变化。

1. 单元测试

面向对象软件中单元的概念发生了变化,封装导出了类的定义。每个类和类的实例(对象)有属性(数据)和处理这些数据的操作(函数或方法)。封装的类常是单元测试的重点,然而,类中包含的操作是最小的可测试单元。由于类中可以包含一些不同的操作,且特殊的操作可以作为不同类的一部分存在,因此,面向对象软件的类测试是由封装在该类中的操作和类的状态行为驱动的。

2. 集成测试

由于面向对象软件没有明显的层次控制结构,因此面向对象环境中的集成测试有两种策略:

(1)基于线程的测试,对响应系统的一个输入或事件所需的一组类进行集成,每个线程单独地集成和测试,并应用回归测试以确保没有产生副作用。

(2)基于使用的测试,通过测试很少使用服务类的那些类开始系统的构建。

5.5.4 测试Web应用

由于WebApp位于网络上,并与很多不同的操作系统、浏览器(位于很多不同的设备上)、硬件平台、通信协议及”暗中的”应用系统进行交互作用,错误的查找是一个重大的挑战。

为了了解Web工程环境中的测试目标,必须考虑WebApp质量的多种维度。

1. 质量维度

良好的设计应该将质量集成到Web应用中,通过对设计模型中的不同元素进行一系列技术评审,对质量进行评估。评估和测试都要检查下面质量维度中的一项或多项。

(1)内容。在语法及语义层对内容进行评估。在语法层,对于文本的文档进行拼写、标点及文法方面的评估;在语义层,所表示信息的正确性、整个内容对象和相关对象的一致性及清晰性都要评估。

(2)功能。对功能进行测试,以发现与客户需求不一致的错误。对于每一项WebApp的功能,评定其正确性、不稳定性及与相应的实现标准(例如,Java或AJAX语言标准)的总体符合程度。

(3)结构。对结构进行评估,以保证它正确地表示WebApp的内容及功能,是可扩展的,并支持新内容、新功能的增加。

(4)可用性。对可用性进行测试,以保证接口支持各种类型的用户,各种用户都能够学会及使用所有需要的导航语法及语义。

(5)导航性。对导航性进行测试,以保证检查所有的导航语法及语义,发现任何导航错误(例如,死链接、不合适的链接、错误链接等)。

(6)性能。在各种不同的操作条件、配置及负载下对性能进行测试,以保证系统响应用户的交互并处理极端的负载情况,而且没有出现操作上不可接受的性能降低。

(7)兼容性。在客户端及服务器端,在各种不同的主机配置下通过运行WebApp对兼容性进行测试,目的是发现对特定主机配置的错误。

(8)安全性。对安全性进行测试,通过评定可能存在的弱点试图对每个弱点进行攻击。任何成功的突破尝试都被认为是一个安全漏洞。

2. WebApp测试策略

WebApp测试策略采用所有软件测试使用的基本原理,并建议使用面向对象系统使用的策略和战术。下面的步骤对此方法进行了总结。

(1)对WebApp的内容模型进行评审,以发现错误。

(2)对接口模型进行评审,保证适合所有的用例。

(3)评审WebApp的设计模型,发现导航错误。

(4)测试用户界面,发现表现机制和(或)导航机制中的错误。

(5)对功能构件进行单元测试。

(6)对贯穿体系结构的导航进行测试。

(7)在各种不同的环境配置下实现WebApp,并测试WebApp对于每一种配置的兼容性。

(8)进行安全性测试,试图攻击WebApp或其所处环境的弱点。

(9)进行性能测试。

(10)通过可监控的最终用户群对WebApp进行测试,对他们与系统的交互结果进行以下方面的评估,包括内容和导航错误、可用性、兼容性以及WebApp的安全性、可靠性及性能等方面的评估。

5.5.5 测试方法

在软件测试过程中,应该为定义软件测试模板,即将特定的测试方法和测试用例设计放在一系列的测试步骤中。

软件测试方法分为静态测试和动态测试。

(1)静态测试。静态测试是指被测试程序不在机器上运行,而是采用人工检测和计算机辅助静态分析的手段对程序进行检测。

① 人工检测。人工检测不依靠计算机而是依靠人工审查程序或评审软件,包括代码检查、静态结构分析和代码质量度量等。

② 计算机辅助静态分析。利用静态分析工具对被测试程序进行特性分析,从程序中提取一些信息,以便检查程序逻辑的各种缺陷和可疑的程序构造。

(2)动态测试。动态测试是指通过运行程序发现错误。在对软件产品进行动态测试时可以采用黑盒测试法和白盒测试法。

测试用例由测试输入数据和与之对应的预期输出结果组成。在设计测试用例时,应当包括合理的输入条件和不合理的输入条件。

1. 黑盒测试

黑盒测试也称为功能测试,在完全不考虑软件的内部结构和特性的情况下,测试软件的外部特性。进行黑盒测试主要是为了发现以下几类错误。

(1)是否有错误的功能或遗漏的功能?

(2)界面是否有误?输入是否正确接收?输出是否正确?

(3)是否有数据结构或外部数据库访问错误?

(4)性能是否能够接受?

(5)是否有初始化或终止性错误?

常用的黑盒测试技术有等价类划分、边界值分析、错误推测和因果图等。

(1)等价类划分。等价类划分法将程序的输入域划分为若干等价类,然后从每个等价类中选取一个代表性数据作为测试用例。每一类的代表性数据在测试中的作用等价于这一类中的其他值,这样就可以用少量代表性的测试用例取得较好的测试效果。等价类划分有两种不同的情况:有效等价类和无效等价类。在设计测试用例时,要同时考虑这两种等价类。

定义等价类的原则如下。

① 在输入条件规定了取值范围或值的个数的情况下,可以定义一个有效等价类和两个无效等价类。

② 在输入条件规定了输入值的集合或规定了”必须如何”的条件的情况下,可以定义一个有效等价类和一个无效等价类。

③ 在输入条件是一个布尔量的情况下,可以定义一个有效等价类和一个无效等价类。

④ 在规定了输入数据的一组值(假定 $n$ 个),并且程序要对每一个输入值分别处理的情况下,可以定义 $n$ 个有效等价类和一个无效等价类。

⑤ 在规定了输入数据必须遵守的规则的情况下,可以定义一个有效等价类(符合规则)和若干个无效等价类(从不同角度违反规则)。

⑥ 在确知已划分的等价类中,各元素在程序处理中的方式不同的情况下,则应将该等价类进一步划分为更小的等价类。

定义好等价类之后,建立等价类表,并为每个等价类编号。在设计一个新的测试用例时,使其尽可能多地覆盖尚未覆盖的有效等价类,不断重复,最后使得所有有效等价类均被测试用例覆盖。然后设计一个新的测试用例,使其只覆盖一个无效等价类。

(2)边界值分析。输入的边界比中间更加容易发生错误,因此用边界值分析来补充等价类划分的测试用例设计技术。边界值划分选择等价类边界的测试用例,既注重于输入条件边界,又适用于输出域测试用例。

对边界值设计测试用例应遵循的原则如下。

① 如果输入条件规定了值的范围,则应取刚达到这个范围的边界的值,以及刚刚超越这个范围边界的值作为测试输入数据。

② 如果输入条件规定了值的个数,则用最大个数、最小个数、比最小个数少1、比最大个数多1的数据作为测试数据。

③ 根据规格说明的每个输出条件使用上述两条原则。

④ 如果程序的规格说明给出的输入域或输出域是有序集合,则应选取集合的第一个元素和最后一个元素作为测试用例。

⑤ 如果程序中使用了一个内部数据结构,则应当选择这个内部数据结构边界上的值作为测试用例。

⑥ 分析规格说明,找出其他可能的边界条件。

(3)错误推测。错误推测是基于经验和直觉推测程序中所有可能存在的各种错误,从而有针对性地设计测试用例的方法。其基本思想是列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据它们选择测试用例。

(4)因果图。因果图法是从自然语言描述的程序规格说明中找出因(输入条件)和果(输出或程序状态的改变),通过因果图转换为判定表。

利用因果图导出测试用例需要经过以下几个步骤。

① 分析程序规格说明的描述中哪些是原因,哪些是结果,原因常常是输入条件或是输入条件的等价类,而结果是输出条件。

② 分析程序规格说明的描述中语义的内容,并将其表示成连接各个原因与各个结果的”因果图”。

③ 标明约束条件。由于语法或环境的限制,有些原因和结果的组合情况是不可能出现的。为表明这些特定的情况,在因果图上使用若干个标准的符号标明约束条件。

④ 把因果图转换成判定表。

⑤ 为判定表中每一列表示的情况设计测试用例。

这样生成的测试用例(局部,组合关系下的)包括了所有输入数据的取”真”和取”假”的情况,构成的测试用例数据达到最少,且测试用例数据随输入数据数目的增加而增加。

2. 白盒测试

白盒测试也称为结构测试,根据程序的内部结构和逻辑来设计测试用例,对程序的路径和过程进行测试,检查是否满足设计的需要。

白盒测试常用的技术是逻辑覆盖、循环覆盖和基本路径测试。

(1)逻辑覆盖。逻辑覆盖考察用测试数据运行被测程序时对程序逻辑的覆盖程度,主要的逻辑覆盖标准有语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、条件组合覆盖和路径覆盖6种。

① 语句覆盖。语句覆盖是指选择足够的测试数据,使被测试程序中的每条语句至少执行一次。语句覆盖对程序执行逻辑的覆盖很低,因此一般认为它是很弱的逻辑覆盖。

② 判定覆盖。判定覆盖是指设计足够的测试用例,使得被测程序中的每个判定表达式至少获得一次”真”值和”假”值,或者说是程序中的每一个取”真”分支和取”假”分支至少都通过一次,因此判定覆盖也称为分支覆盖。判定覆盖要比语句覆盖更强一些。

③ 条件覆盖。条件覆盖是指构造一组测试用例,使得每一判定语句中每个逻辑条件的各种可能的值至少满足一次。

④ 判定/条件覆盖。判定/条件覆盖是指设计足够的测试用例,使得判定中每个条件的所有可能取值(真/假)至少出现一次,并使每个判定本身的判定结果(真/假)也至少出现一次。

⑤ 条件组合覆盖。条件组合覆盖是指设计足够的测试用例,使得每个判定中条件的各种可能值的组合都至少出现一次。满足条件组合覆盖的测试用例是一定满足判定覆盖、条件覆盖和判定/条件覆盖的。

⑥ 路径覆盖。路径覆盖是指覆盖被测试程序中所有可能的路径。

(2)循环覆盖。执行足够的测试用例,使得循环中的每个条件都得到验证。

(3)基本路径测试。基本路径测试法是在程序控制流图的基础上通过分析控制流图的环路复杂性,导出基本可执行路径集合,从而设计测试用例。设计出的测试用例要保证在测试中程序的每一条独立路径都执行过,即程序中的每条可执行语句至少执行一次。此外,所有条件语句的真值状态和假值状态都测试过。路径测试的起点是程序控制流图。程序控制流图中的结点代表包含一个或多个无分支的语句序列,边代表控制流。

白盒测试的原则如下。

(1)程序模块中的所有独立路径至少执行一次。

(2)在所有的逻辑判断中,取”真”和取”假”的两种情况至少都能执行一次。

(3)每个循环都应在边界条件和一般条件下各执行一次。

(4)测试程序内部数据结构的有效性等。

5.5.6 调试

调试发生在测试之后,其任务是根据测试时所发现的错误找出原因和具体的位置,进行改正。调试工作主要由程序开发人员进行,谁开发的程序就由谁来进行调试。

1. 调试过程

调试并不是测试,且总是发生在测试之后。执行测试用例,对测试结果进行评估,当期望的表现与实际表现不一致时,调试过程就开始了。在很多情况下,这种不一致的数据是隐藏在背后的某种原因所变现出来的症状。调试试图找到隐藏在症状背后的原因,从而使错误得到修正。

调试过程通常得到以下两种结果之一:发现问题的原因并将其改正;未能找到问题的原因。在后一种情况下,调试人员可以假设一个原因,设计一个或多个测试用例来帮助验证这个假设,重复此过程直到改正错误。

调试是非常困难的,这是因为在很大程度上,人类的心理比软件技术与其有更密切的关系。软件Bug的以下特征为开发者进行调试提供了一些线索。

(1)症状与原因出现的地方可能相隔很远。也就是说,症状可能在程序的一个地方出现,而原因实际上可能在很远的另一个地方。高度耦合的构件加剧了这种情况的发生。

(2)症状可能在另一个错误被改正时(暂时)消失。

(3)症状实际上可能是由非错误因素(例如,舍入误差)引起的。

(4)症状可能是由不易追踪的人为错误引起的。

(5)症状可能是有计时问题而不是处理问题引起的。

(6)重新产生完全一样的输入条件是困难的(例如,输入顺序不确定的实时应用系统)。

(7)症状可能时有时无,这在软/硬件耦合的嵌入式系统中尤为常见。

(8)症状可能是由分布运行在不同处理器上的很多任务引起的。

2. 调试方法

目前,常用的调试方法有以下几种。

1)试探法

调试人员分析错误的症状,猜测问题所在的位置,利用在程序中设置输出语句,分析寄存器、存储器的内容等手段获得错误的线索,一步步地试探和分析出错误所在。这种方法效率很低,适合于结构比较简单的程序。

2)回溯法

调试人员从发现错误症状的位置开始,人工沿着程序的控制流程往回跟踪代码,直到找出错误根源为止。这种方法适合于小型程序,对于大规模程序,由于其需要回溯的路径太多而变得不可操作。

3)对分查找法

这种方法主要用来缩小错误的范围,如果已经知道程序中的变量在若干位置的正确取值,可以在这些位置上给这些变量以正确值,观察程序运行的输出结果,如果没有发现问题,则说明从赋予变量一个正确值开始到输出结果之间的程序没有错误,问题可能在除此之外的程序中。否则错误就在所考察的这部分程序中,对含有错误的程序段再使用这种方法,直到把故障范围缩小到比较容易诊断为止。

4)归纳法

归纳法就是从测试所暴露的问题出发,收集所有正确或不正确的数据,分析它们之间的关系,提出假想的错误原因,用这些数据来证明或反驳,从而查出错误所在。

5)演绎法

演绎法根据测试结果,列出所有可能的错误原因;分析已有的数据,排除不可能和彼此矛盾的原因;对其余的原因,选择可能性最大的,利用已有的数据完善该假设,使假设更具体;用假设来解释所有的原始测试结果,如果能解释这一切,则假设得以证实,也就找出错误,否则,要么是假设不完备或不成立,要么有多个错误同时存在,需要重新分析,提出新的假设,直到发现错误为止。

5.6 运行和维护知识

5.6.1 系统转换

在进行新旧系统转换以前,首先要进行新系统的试运行。在系统测试、调试中,使用的是系统测试数据,有些实际运行中可能出现的问题很难通过这些数据被发现。所以,一个系统开发后,让它实际运行一段时间,是对系统最好的检验和测试方法。

系统试运行阶段的主要工作如下。

(1)对系统进行初始化、输入各种原始数据记录。

(2)记录系统运行的数据和状况。

(3)核对新系统输出和旧系统(人工或计算机系统)输出的结果。

(4)对实际系统的输入方式进行考察(是否方便、效率如何、安全可靠性、误操作保护等)。

(5)对系统实际运行、响应速度(包括运算速度、传递速度、查询速度和输出速度等)进行实际测试。

新系统试运行成功之后,就可以在新系统和旧系统之间互相转换。新旧系统之间的转换方式有直接转换、并行转换和分段转换。

(1)直接转换。直接转换就是在确定新系统运行无误时立刻启用新系统,终止旧系统运行。这种方式很节省人员、设备费用。这种方式一般适用于一些处理过程不太复杂、数据不太重要的场合。

(2)并行转换。这种转换方式是新旧系统并行工作一段时间,经过一段时间的考验以后,新系统正式替代旧系统。对于较复杂的大型系统,它提供了一个与旧系统运行结果进行比较的机会,可以对新旧两个系统的时间要求、出错次数和工作效率给予公正的评价。当然,由于与旧系统并行工作,消除了尚未认识新系统之前的紧张和不安。在银行、财务和一些企业的核心系统中,这是一种经常使用的转换方式。它的主要特点是安全、可靠,但费用和工作量都很大,因为在相当长的时间内系统要新、旧两套并行工作。

(3)分段转换。分段转换又称逐步转换、向导转换、试点过渡法等。这种转换方式实际上是以上两种转换方式的结合。在新系统全部正式运行前,一部分一部分地代替旧系统。那些在转换过程中还没有正式运行的部分,可以在一个模拟环境中继续试运行。这种方式既保证了可靠性,又不至于费用太大。但是,这种分段转换要求子系统之间有一定的独立性,对系统的设计和实现都有一定的要求,否则无法实现这种分段转换的设想。

在实际工作中,转换方法较为灵活。一个信息系统从使用到成熟再到提高,是一个比较长的过程。只有遵循数据处理的阶段性,信息系统才能健康发展。现以一个连锁企业开始实施新系统为例进行介绍。

(1)初始阶段。企业首先为应用系统做基本资料的准备,进行总部、”配送”核心系统的实施。

(2)推广阶段。总部、”配送”系统稳定后,先从1~2家门店试点开始,以门店核心模块为主,完成门店与总部的信息交换、物流过程。然后再逐步推广门店系统,直到完成所有门店的联网工作。

(3)控制阶段。所有门店联网完成后,进行准确、及时的基本数据采集和调整工作。

(4)集成阶段。考虑自动补货、自动配货、财务接口等高级模块应用的工作。

(5)管理阶段。进入数据的全面启用和介入管理决策。最后,系统步入成熟阶段。

实际上,每个企业在不同阶段的发展过程中对具体问题的解决方法是不同的,随时都会进行以上阶段的周期重复,随着每次重复时起点的不断升高,整个企业的数据应用水平也就随之逐步提高了。

5.6.2 系统维护概述

软件维护是软件生命周期中的最后一个阶段,处于系统投入生产性运行以后的时期中,因此不属于系统开发过程。软件维护是在软件已经交付使用之后为了改正错误或满足新的需求而修改软件的过程,即软件在交付使用后对软件所做的一切改动。

1. 系统可维护性概念

系统的可维护性可以定义为维护人员理解、改正、改动和改进这个软件的难易程度。提高可维护性是开发软件系统所有步骤的关键目的,系统是否能被很好地维护,可以用系统的可维护性这一指标来衡量。

1)系统可维护性的评价指标

(1)可理解性。指别人能理解系统的结构、界面、功能和内部过程的难易程度。模块化、详细设计文档、结构化设计和良好的高级程序设计语言等都有助于提高可理解性。

(2)可测试性。诊断和测试的容易程度取决于易理解的程度。好的文档资料有利于诊断和测试,同时,程序的结构、高性能的测试工具以及周密计划的测试工序也是至关重要的。为此,开发人员在系统设计和编程阶段就应尽力把程序设计成易诊断和测试的。此外,在进行系统维护时,应该充分利用在系统测试阶段保存下来的测试用例。

(3)可修改性。诊断和测试的容易程度与系统设计所制定的设计原则有直接关系。模块的耦合、内聚、作用范围与控制范围的关系等都对可修改性有影响。

2)维护与软件文档

文档是软件可维护性的决定因素。由于长期使用的大型软件系统在使用过程中必然会经受多次修改,所以文档显得非常重要。

软件系统的文档可以分为用户文档和系统文档两类。用户文档主要描述系统功能和使用方法,并不关心这些功能是怎样实现的;系统文档描述系统设计、实现和测试等各方面的内容。

可维护性是所有软件都应具有的基本特点,必须在开发阶段保证软件具有可维护的特点。在软件工程的每一个阶段都应考虑并提高软件的可维护性,在每个阶段结束前的技术审查和管理复查中应该着重对可维护性进行复审。

在系统分析阶段的复审过程中,应该对将来要改进的部分和可能会修改的部分加以注解并指明,并且指出软件的可移植性问题以及可能影响软件维护的系统界面;在系统设计阶段的复审期间,应该从容易修改、模块化和功能独立的目的出发,评价软件的结构和过程;在系统实施阶段的复审期间,代码复审应该强调编码风格和内部说明文档这两个影响可维护性的因素。在完成了每项维护工作之后,都应该对软件维护本身进行认真的复审。

3)软件文档的修改

维护应该针对整个软件配置,不应该只修改源程序代码。如果对源程序代码的修改没有反映在设计文档或用户手册中,可能会产生严重的后果。每当对数据、软件结构、模块过程或任何其他有关的软件特点做了改动时,必须立即修改相应的技术文档。不能准确反映软件当前状态的设计文档可能比完全没有文档更坏。在以后的维护工作中,用户很可能因文档不完全符合实际而不能正确地理解软件,从而在维护中引入过多的错误。

2. 系统维护的内容及类型

系统维护主要包括硬件维护、软件维护和数据维护。

1)硬件维护

硬件维护应由专职的硬件维护人员来负责,主要有两种类型的维护活动:一种是定期的设备保养性维护,保养周期可以是一周或一个月不等,维护的主要内容是进行例行的设备检查与保养,易耗品的更换与安装等;另一种是突发性的故障维护,即当设备出现突发性故障时,由专职的维修人员或请厂方的技术人员来排除故障,这种维修活动所花的时间不能过长,以免影响系统的正常运行。

2)软件维护

软件维护主要是指根据需求变化或硬件环境的变化对应用程序进行部分或全部修改。修改时应充分利用源程序,修改后要填写程序修改登记表,并在程序变更通知书上写明新旧程序的不同之处。

软件维护的内容一般有以下几个方面。

(1)正确性维护。正确性维护是指改正在系统开发阶段已发生而系统测试阶段尚未发现的错误。这方面的维护工作量要占整个维护工作量的17%~21%。所发现的错误有的不太重要,不影响系统的正常运行,其维护工作可随时进行;而有的错误非常重要,甚至会影响整个系统的正常运行,其维护工作必须制定计划,进行修改,并且要进行复查和控制。

(2)适应性维护。适应性维护是指使应用软件适应信息技术变化和管理需求变化而进行的修改。这方面的维护工作量占整个维护工作量的18%~25%。由于目前计算机硬件价格不断下降,各类系统软件层出不穷,人们常常为改善系统硬件环境和运行环境而产生系统更新换代的需求;企业的外部市场环境和管理需求的不断变化也使得各级管理人员不断提出新的信息需求。这些因素都将导致适应性维护工作的产生。进行这方面的维护工作也要像系统开发一样,有计划、有步骤地进行。

(3)完善性维护。这是为扩充功能和改善性能而进行的修改,主要是指对已有的软件系统增加一些在系统分析和设计阶段中没有规定的功能与性能特征。这些功能对完善系统功能是非常必要的。另外,它还包括对处理效率和编写程序的改进,这方面的维护占整个维护工作的50%~60%,比重较大,也是关系到系统开发质量的重要方面。这方面的维护除了要有计划、有步骤地完成外,还要注意将相关的文档资料加入到前面相应的文档中。

(4)预防性维护。为了改进应用软件的可靠性和可维护性,为了适应未来的软/硬件环境的变化,应主动增加预防性的新的功能,以使应用系统适应各类变化而不被淘汰。例如将专用报表功能改成通用报表生成功能,以适应将来报表格式的变化。这方面的维护工作量占整个维护工作量的4%左右。

3)数据维护

数据维护工作主要是由数据库管理员来负责,主要负责数据库的安全性和完整性以及进行并发性控制。数据库管理员还要负责维护数据库中的数据,当数据库中的数据类型、长度等发生变化时,或者需要添加某个数据项、数据库时,要负责修改相关的数据库、数据字典,并通知有关人员。另外,数据库管理员还要负责定期出版数据字典文件及一些其他数据管理文件,以保留系统运行和修改的轨迹。当系统出现硬件故障并得到排除后,要负责数据库的恢复工作。

数据维护中还有一项很重要的内容,那就是代码维护。不过代码维护发生的频率相对较小。代码的维护应由代码管理小组进行。变更代码应经过详细讨论,确定之后要用书面形式贯彻。代码维护的困难往往不在于代码本身的变更,而在于新代码的贯彻。为此,除了成立专门的代码管理小组外,各业务部门要指定专人进行代码管理,通过他们贯彻使用新代码。这样做的目的是要明确管理职责,有助于防止和更正错误。

3. 系统维护的管理和步骤

需要强调的是,系统的修改往往会”牵一发而动全身”。程序、文件、代码的局部修改都可能影响系统的其他部分。因此,系统的维护工作应有计划、有步骤地统筹安排,按照维护任务的工作范围、严重程度等诸多因素确定优先顺序,制定出合理的维护计划,然后通过一定的批准手续实施对系统的修改和维护。

通常,对系统的维护应执行以下步骤。

(1)提出维护或修改要求。操作人员或业务领导用书面形式向系统维护工作的主管人员提出对某项工作的修改要求。这种修改要求一般不能直接向程序员提出。

(2)领导审查并做出答复,如同意修改则列入维护计划。系统主管人员进行一定的调查后,根据系统的情况和工作人员的情况,考虑这种修改是否必要、是否可行,做出是否修改、何时修改的答复。如果需要修改,则根据优先程度的不同列入系统维护计划。计划的内容应包括维护工作的范围、所需资源、确认的需求、维护费用、维护进度安排以及验收标准等。

(3)领导分配任务,维护人员执行修改。系统主管人员按照计划向有关的维护人员下达任务,说明修改的内容、要求和期限。维护人员在仔细了解原系统的设计和开发思路的情况下对系统进行修改。

(4)验收维护成果并登记修改信息。系统主管人员组织技术人员对修改部分进行测试和验收。验收通过后,将修改的部分嵌入系统,取代旧的部分。维护人员登记所做的修改,更新相关的文档,并将新系统作为新的版本通报用户和操作人员,指明新的功能和修改的地方。

在进行系统维护过程中,还要注意维护的副作用。维护的副作用包括两个方面:一是修改程序代码有时会发生灾难性的错误,造成原来运行比较正常的系统变得不能正常运行。为了避免这类错误,要在修改工作完成后进行测试,直到确认和复查无错为止。二是修改数据库中数据的副作用,当一些数据库中的数据发生变化时可能导致某些应用软件不再适应这些已经变化了的数据而产生错误。为了避免这类错误,一是要有严格的数据描述文件,即数据字典系统;二是要严格记录这些修改并进行修改后的测试工作。

总之,系统维护工作是信息系统运行阶段的重要工作内容,必须予以充分的重视。维护工作做得好,信息系统的作用才能够得以充分发挥,信息系统的寿命也就越长。

5.6.3 系统评价

1. 系统评价概述

信息系统的评价分为广义和狭义两种。广义的信息系统评价是指从系统开发的一开始到结束的每一阶段都需要进行评价。狭义的信息系统评价则是指在系统建成并投入运行之后所进行的全面、综合的评价。

按评价的时间与信息系统所处的阶段的关系又可从总体上把广义的信息系统评价分成立项评价、中期评价和结项评价。

(1)立项评价。立项评价指信息系统方案在系统开发前的预评价,即系统规划阶段中的可行性研究。评价的目的是决定是否立项进行开发,评价的内容是分析当前开发新系统的条件是否具备,明确新系统目标实现的重要性和可能性,主要包括技术上的可行性、经济上的可行性、管理上的可行性和开发环境的可行性等方面。由于事前评价所用的参数大多是不确定的,所以评价的结论具有一定的风险性。

(2)中期评价。项目中期评价包含两种含义,一是指项目方案在实施过程中因外部环境出现重大变化(例如市场需求变化、竞争性技术或更完美的替代系统出现,或者发现原先的设计有重大失误等)需要对项目的方案进行重新评估,以决定是继续执行还是终止该方案;另一种含义也可称为阶段评估,是指在信息系统开发正常的情况下,对系统设计、系统分析、系统实施阶段的阶段性成果进行评估。由于一般都将阶段性成果的提交视为信息系统建设的里程碑,所以,阶段评估又可称为里程碑式评价。

(3)结项评价。信息系统的建设是一个项目,是项目就需要有终结时间。结项评价是指项目准备结束时对系统的评价,一般是指在信息系统投入正式运行以后,为了了解系统是否达到预期的目的和要求而对系统运行的实际效果进行的综合评价。所以,结项评价又是狭义的信息系统评价。信息系统项目的鉴定是结项评价的一种正规的形式。结项评价的主要内容包括系统性能评价、系统的经济效益评价以及企业管理效率提高、管理水平改善、管理人员劳动强度减轻等间接效果。通过结项评价,用户可以了解系统的质量和效果,检查系统是否符合预期的目的和要求;开发人员可以总结开发工作的经验、教训,这对今后的工作十分有益。

在对信息系统进行评价考核时,应该注意以下几个问题。

(1)信息系统通过基本资料输入、进货、订货、盘点和零售等各个环节采集进来,其中任何一个环节的数据输入出现问题,都将导致最终报表的不准确,而报表不准确就意味着企业决策者无法根据报表决定企业的运作,更谈不上数据分析和决策支持了。这也是目前大部分使用了信息系统的企业普遍存在的问题。究竟是什么原因导致了数据采集的不准确呢?一些企业错误地将数据准确性作为考核信息部的一个指标,其实数据不准确更多的源于管理上存在的问题,正因为数据源头非常多,所造成的数据不一致的问题不是信息部都能解决的,最终数据采集的成败将由最高管理层对数据采集各环节的管理力度所决定,而数据采集的成败又将最终决定整个信息系统应用的成败。

(2)信息系统并不是万能系统。在系统应用的过程中,有些问题是信息系统擅长解决的,如大量的、重复的、规范性的事务处理;而有些问题是信息系统不擅长解决的,如特殊的、偶然的、不规范的经营管理内容。让信息系统做不擅长的工作,势必在应用的过程中投入的管理成本远远大于它所产生的效益。对于这种灵活、多变的情况,不妨采用人工处理或通过制度的限制,尽量避免不规范的行为频繁发生,从而真正实现企业简单复制、快速扩张、规模效益的目的。

2. 系统评价的指标

从以下几方面综合考虑,建立起一套指标体系理论框架。

(1)从信息系统的组成部分出发,信息系统是一个由人机共同组成的系统,所以可以按照运行效果和用户需求(人)、系统质量和技术条件(机)这两条线索构造指标。

(2)从信息系统的评价对象出发,对于开发方来说,他们所关心的是系统质量和技术水平;对于用户方而言,关心的是用户需求和运行质量;系统外部环境则主要通过社会效益指标来反映。

(3)从经济学角度出发,分别按系统成本、系统效益和财务指标3条线索建立指标。

5.7 软件项目管理

在经历了软件危机和大量的软件项目失败以后,人们对软件工程产业的现状进行了多次的分析,得出了普遍性的结论:软件项目成功率非常低的原因可能就是项目管理能力太弱。由于软件本身的特殊性及复杂性,将项目管理思想引入软件工程领域,就形成了软件项目管理。软件项目管理是指软件生存周期中软件管理者所进行的一系列活动,其目的是在一定的时间和预设范围内有效地利用人力、资源、技术和工具,使软件系统或软件产品按原定计划和质量要求如期完成。

5.7.1 软件项目管理涉及的范围

有效的软件项目管理集中在4个P上,即人员(Person)、产品(Product)、过程(Procedure)和项目(Project)。

1. 人员

人员是软件工程项目的基本要素和关键因素,在对人员进行组织时,有必要考虑参与软件过程(及每一个软件项目)的人员类型。一般来说,可以分为以下5类。

1)项目管理人员

项目管理人员负责软件项目的管理工作,其负责人通常称为项目经理。项目经理除了要求掌握相应的软件开发技术外,更多的应具备管理人员应有的技能。项目经理的任务就是要对项目进行全面的管理,具体表现在对项目目标要有一个全局的观点,制订项目计划,监控项目进展,控制反馈,组建团队,在不确定环境下对不确定问题进行决策,在必要的时候进行谈判并解决冲突。

2)高级管理人员

高级管理人员可以是领域专家,负责提出项目的目标并对业务问题进行定义,这类业务问题经常会对项目产生较大的影响。

3)开发人员

这类人员常常掌握了开发一个产品或应用所需的专门技术,可胜任需求分析、设计、编码、测试、发布等各种相关的开发岗位。

4)客户

客户是一组可说明待开发软件的需求的人,也包括与项目目标有关的其他风险承担者。

5)最终用户

产品或应用提交后,那些与产品/应用进行交互的人称为最终用户。

软件项目的组织称为软件项目组,每一个软件项目组都有上述的人员参与,项目组的组织必须最大限度地发挥每个人的技术和能力。

2. 产品

在进行项目计划之前,应该首先进行项目定义,也就是定义项目范围,其中包括建立产品的目的和范围、可选的解决方案、技术或管理的约束等。

软件开发者和客户必须一起定义产品的目的和范围。一般情况下,该活动作为系统工程或业务过程工程的一部分,持续到软件需求分析阶段的前期。其目的是从客户的角度定义该产品的总体目标,但不必考虑这些目标如何实现。软件范围定义了与软件产品相关的数据、功能和行为及相关的约束。

软件范围是通过回答下列问题来定义的。

1)项目环境

要开发的软件如何适应于大型的系统、产品或业务环境,该环境下要施加什么约束?

2)信息目标

软件要产生哪些客户可见的数据对象作为输出?需要什么数据对象作为输入?

3)功能和性能

软件要执行什么功能才能将输入数据变换成输出数据?软件需要满足什么特殊的性能要求?

软件项目范围必须是无二义的和可理解的,为控制其复杂性,必要时还需对问题进行分解。

3. 过程

传统的项目管理有大项目、项目、活动、工作包、工作单元等多种分解层次,对于软件项目来说,强调的是对其进行过程控制,通常将项目分解为任务、子任务等,其分解准则是基于软件工程的过程。

软件过程提供了一个项目团队要选择一个适合于待开发软件的过程模型(软件过程模型详见4.2节)。项目团队必须决定哪种过程模型最适合于:需要该产品的客户和从事开发工作的人员;产品本身的特性;软件团队所处的项目工作环境。在选定过程模型后,项目团队可以基于这组过程框架活动来制订一个初步的项目计划。一旦确定了初步计划,过程分解就开始了,也就是说,必须制订一个完整的计划来反映框架活动中所需完成的工作任务。

4. 项目

进行有计划和可控制的软件项目是管理复杂性的一种方式。Reel提出了包含如下5个部分常识的软件项目方法。

1)明确目标及过程

充分理解待解决的问题,明确定义项目目标及软件范围,为项目小组及活动设置明确、现实的目标,并充分发挥相关小组的自主性。

2)保持动力

为了维持动力,项目管理者必须提供激励措施以保持人员变动为绝对最小量。小组应该强调所完成的每个任务的质量,而高层的管理应该尽量不干涉项目小组的工作方式。

3)跟踪进展

针对每个软件项目,当每个任务的工作制品(如规约、源代码、测试用例集合等)作为质量保证活动的一部分而被批准(通过正式的技术评审)时,对其进展进行跟踪,并对软件过程和项目进行测量。

4)做出明智的决策

在本质上,项目管理者和软件小组的决策应该”保持其简单”。只要有可能,就使用商用成品软件或现有的软件构件或模式,可以采用标准方法时避免定制接口,识别并避免显而易见的风险,以及分配比你认为的时间更多的时间来完成复杂或有风险的任务。

5)进行事后分析

建立统一的机制,从每个项目中获取可学习的经验。评估计划的进度和实际的进度,收集和分析软件项目度量数据,从团队成员和客户处获取反馈,并记录所有的发现。

5.7.2 软件项目估算

软件项目估算涉及人、技术、环境等多种因素,因此很难在项目完成前准确地估算出开发软件所需的成本、持续时间和工作量。因此,需要一些方法和技术来支持项目的估算,常用的估算方法有下列3种。

(1)基于已经完成的类似项目进行估算。这是一种常用的也是有效的估算方法。

(2)基于分解技术进行估算。分解技术包括问题分解和过程分解。问题分解是将一个复杂问题分解成若干个小问题,通过对小问题的估算得到复杂问题的估算。过程分解是指先根据软件开发过程中的活动(分析、设计、编码、测试等)进行估算,然后得到整个项目的估算值。

(3)基于经验估算模型的估算。典型的经验估算模型有IBM估算模型、CoCoMo模型和Putnam模型。

上述方法可以组合使用,以提高估算的精度。

1. 成本估算方法

1)自顶向下估算方法

估算人员参照以前完成的项目所耗费的总成本(或总工作量)来推算将要开发的软件的总成本(或总工作量),然后把它们按阶段、步骤和工作单元进行分配,这种方法称为自顶向下估算方法。

自顶向下估算方法的主要优点是对系统级工作的重视,所以估算中不会遗漏诸如集成、配置管理之类的系统级事务的成本估算,且估算工作量小、速度快。它的缺点是往往不清楚低级别上的技术性困难问题,而这些困难将会使成本上升。

2)自底向上估算方法

自底向上估算方法是将待开发的软件细分,分别估算每一个子任务所需要的开发工作量,然后将它们加起来,得到软件的总开发量。这种方法的优点是将每一部分的估算工作交给负责该部分工作的人来做,所以估算较为准确。其缺点是估算往往缺少各项子任务之间相互联系所需要的工作量和与软件开发有关的系统级工作量,所以估算往往偏低。

3)差别估算方法

差别估算方法的思想是将待开发项目与一个或多个已完成的类似项目进行比较,找出与某个相似项目的若干不同之处,并估算每个不同之处对成本的影响,导出待开发项目的总成本。该方法的优点是可以提高估算的准确度,缺点是不容易明确”差别”的界限。

4)其他估算方法

除了以上方法之外,还有专家估算法、类推估算法和算式估算法等。

(1)专家估算法。该方法依靠一个或多个专家对要求的项目做出估算,其精确性取决于专家对估算项目的定性参数的了解和他们的经验。

(2)类推估算法。在自顶向下的方法中,它是将估算项目的总体参数与类似项目进行直接比较得到结果;在自底向上方法中,类推是在两个具有相似条件的工作单元之间进行。

(3)算式估算法。专家估算法和类推估算法的缺点在于它们依靠带有一定盲目性和主观性的猜测对项目进行估算。算式估算法则是企图避免主观因素的影响,用于估算的方法有两种基本类型:由理论导出和由经验导出。

2. COCOMO估算模型

COCOMO模型是一种精确的、易于使用的成本估算模型。COCOMO模型按其详细程度分为基本COCOMO模型、中级COCOMO模型和详细COCOMO模型。

1)基本COCOMO模型

基本COCOMO模型是一个静态单变量模型,用于对整个软件系统进行估算。其公式如下:

$$ E = a(L)^b $$

$$ D = cE^d $$

其中,$E$ 表示工作量,单位是人月;$D$ 表示开发时间,单位是月;$L$ 是项目的源代码行估计值,不包括程序中的注释及文档,其单位是千行代码;$a$、$b$、$c$、$d$ 是常数。

基本COCOMO模型可通过估算代码行的值 $L$,然后计算开发工作量和开发时间的估算值。

2)中级COCOMO模型

中级COCOMO模型是一个静态多变量模型,它将软件系统模型分为系统和部件两个层次,系统由部件构成,它把软件开发所需的人力(成本)看作是程序大小和一系列”成本驱动属性”的函数。

中级COCOMO模型以基本COCOMO模型为基础,并考虑了15种影响软件工作量的因素,通过工作量调节因子(EAF)修正对工作量的估算,从而使估算更合理。其公式如下:

$$ E = a(L)^b \text{EAF} $$

其中,$L$ 是软件产品的目标代码行数,单位是千行代码数;$a$、$b$ 是常数。

3)详细COCOMO模型

它将软件系统模型分为系统、子系统和模块3个层次,除包括中级模型所考虑的因素外,还考虑了在需求分析、软件设计等每一步的成本驱动属性的影响。

3. COCOMOII模型

最初的COCOMO模型是得到产业界最广泛应用和讨论的软件成本估算模型之一,现在它已经演化成更全面的估算模型,称为COCOMOII。和其前身一样,COCOMOII也是一种层次结构的估算模型,被分为3个阶段性模型。

(1)应用组装模型。在软件工程的前期阶段使用,这时用户界面的原型开发、对软件和系统交互的考虑、性能的评估以及技术成熟度的评价是最重要的。

(2)早期设计阶段模型。在需求已经稳定并且基本的软件体系结构已经建立时使用。

(3)体系结构阶段模型。在软件的构造过程中使用。

和所有的软件估算模型一样,COCOMOII模型也需要使用规模估算信息,在模型层次结构中有3种不同的规模估算选择:对象点、功能点和代码行。应用组装模型使用的是对象点;早期设计阶段模型使用的是功能点,功能点可以转换为代码行。

对象点也是一种间接的软件测量。计算对象点时使用如下的计数值:(用户界面的)屏幕书;报表数;构造应用系统可能需要的构件数。

4. Putnam估算模型

Putnam模型是一种动态多变量模型,它是假设在软件开发的整个生存周期中工作量有特定的分布。

根据一些大型软件项目(30人年以上)的工作量分布情况,推导出软件项目在软件生存周期各阶段的工作量分布。根据该曲线给出代码行数、工作量和开发时间之间的关系,如下所示:

$$ L = C_k E^{1/3} t_d^{4/3} $$

其中,$L$ 表示源程序代码行数(LOC);

$t_d$ 表示开发持续时间(年);

$E$ 是包括软件开发和维护在整个生存期所花费的工作量(人年);

$C_k$ 表示技术状态常数,其值依赖于开发环境,如表5-1所示。

表5-1 技术状态常数 $C_k$ 的取值

$C_k$ 的典型值 开发环境 开发环境举例
2000 差 没有软件开发方法学的支持,缺少文档和评审,采用批处理方式
8000 一般 有软件开发方法学的支持,有适宜的文档和评审,采用交互式处理方式
11000 较好 采用CASE工具和集成化CASE环境

5.7.3 进度管理

软件项目进度管理的目的是确保软件项目在规定的时间内按期完成。一个软件项目通常可以分成多个子项目和任务,这些任务之间存在一定的关系。有些任务可并行开发,有些任务必须在另一些任务完成后才能进行。完成每个任务都需要一定的资源,包括人、时间等,项目管理者的任务就是定义所有的项目任务以及它们之间的依赖关系,制订项目的进度安排,规划每个任务所需的工作量和持续时间,并在项目开发过程中不断跟踪项目的执行情况,发现那些未按计划进度完成的任务对整个项目工期的影响,并及时进行调整。

软件开发项目的进度安排有如下两种方式:系统最终交付日期已经确定,软件开发部门必须在规定期限内完成;系统最终交付日期只确定了大致的年限,最后交付日期由软件开发部门确定。

1. 进度管理的基本原则

指导软件进度安排的基本原则如下。

(1)划分。项目必须被划分成若干可以管理的活动和任务。为了实现项目的划分,对于产品和过程都需要进行分解。

(2)相互依赖性。划分后的各个活动或任务之间的相互依赖关系必须是明确的。有些任务必须按顺序出现,而有些任务则可以并发进行。有些活动只有在其他活动产生的工作产品完成后才能够开始,而有些则可以独立进行。

(3)时间分配。必须为每个被调度的任务分配一定数量的工作单位(如若干人天的工作量)。此外,必须为每个任务制定开始和结束日期。任务的开始日期和结束日期取决于任务之间的相互依赖性以及工作方式。

(4)工作量确认。每个项目都有预定数量的人员参与。在进行时间分配时,项目管理者必须确保在任意时段中分配的人员数量不会超过项目团队中的总人数。

(5)确定责任。安排了进度计划的每个任务都应该指定特定的团队成员来负责。

(6)明确输出结果。安排了进度计划的每个任务都应该有一个明确的输出结果。对于软件项目而言,输出结果通常是一个工作产品(例如一个模块的设计)或某个工作产品的一部分。通常,可以将多个工作产品组合成可交付产品。

(7)确定里程碑。每个任务或任务组都应该与一个项目里程碑相关联。当一个或多个工作产品经过质量评审并且得到认可时,标志着一个里程碑的完成。

2. 进度安排

为监控软件项目的进度计划和工作的实际进展情况,表示各项任务之间进度的相互依赖关系,需要采用图示的方法。在图中明确标明如下内容。

(1)各个任务的计划开始时间和完成时间。

(2)各个任务的完成标志。

(3)各个任务与参与工作的人数,各个任务与工作量之间的衔接情况。

(4)完成各个任务所需的物理资源和数据资源。

进度安排的常用图形描述方法有Gantt图(甘特图)和项目计划评审技术(Program Evaluation & Review Technique,PERT)图。

1)Gantt图

Gantt图是一种简单的水平条形图,它以日历为基准描述项目任务。水平轴表示日历时间线(如时、天、周、月和年等),每个条形表示一个任务,任务名称垂直地列在左边的列中,图中水平条的起点和终点对应水平轴上的时间,分别表示该任务的开始时间和结束时间,水平条的长度表示完成该任务所持续的时间。当日历中同一时段存在多个水平条时,表示任务之间的并发。

Gantt图能清晰地描述每个任务从何时开始,到何时结束,任务的进展情况以及各个任务之间的并行性。但是它不能清晰地反映出各任务之间的依赖关系,难以确定整个项目的关键所在,也不能反映计划中有潜力的部分。

2)PERT图

PERT图是一个有向图,图中的箭头表示任务,它可以标上完成该任务所需的时间。图中的结点表示流入结点的任务的结束,并开始流出结点的任务,这里把结点称为事件。只有当流入该结点的所有任务都结束时,结点所表示的事件才出现,流出结点的任务才可以开始。事件本身不消耗时间和资源,它仅表示某个时间点。一个事件有一个事件号和出现该事件的最早时刻和最迟时刻。最早时刻表示在此时刻之前从该事件出发的任务不可能开始;最迟时刻表示从该事件出发的任务必须在此时刻之前开始,否则整个工程就不能如期完成。每个任务还可以有一个松弛时间(Slack Time),表示在不影响整个工期的前提下完成该任务有多少机动余地。为了表示任务间的关系,在图中还可以加入一些空任务(用虚线箭头表示),完成空任务的时间为0。

PERT图不仅给出了每个任务的开始时间、结束时间和完成该任务所需的时间,还给出了任务之间的关系,即哪些任务完成后才能开始另外一些任务,以及如期完成整个工程的关键路径。图中的松弛时间则反映了完成某些任务时可以推迟其开始时间或延长其所需完成的时间。但是,PERT图不能反映任务之间的并行关系。

5.7.4 软件项目的组织

开发组织采用什么形式组织,不仅要考虑软件项目的特点,还需要考虑参与人员的素质。在软件项目组织中,其组织原则有以下3条。

(1)尽早落实责任。在软件项目开始组织时,要尽早指定专人负责,使他有权进行管理,并对任务的完成负全责。

(2)减少交流接口。一个组织的生产率随着完成任务时存在的通信路径数目的增加而降低。要有合理的人员分工、好的组织结构、有效的通信,减少不必要的生产率的损失。

(3)责权均衡。软件管理人员承担的责任不应比赋予他的权利还大。

1. 组织结构的模式

根据项目的分解和过程的分解,软件项目可以有以下多种组织形式。

1)按项目划分的模式

按项目将开发人员组织成项目组,项目组的成员共同完成该项目的所有开发任务,包括项目的定义、需求分析、设计、编码、测试、评审以及所有的文档编制,甚至包括该项目的维护。

2)按职能划分的模式

按软件过程中所反映的各种职能将项目的参与者组织成相应的专业组,如开发组(可进一步分为需求分析组、设计组、编码组)、测试组、质量保证组、维护组等。

3)矩阵模式

这种模式是上述两种模式的组合,它既按职能组织相应的专业组,又按项目组织项目组。每个软件人员既属于某个专业组,又属于某个项目组。每个软件项目指定一个项目经理,项目中的成员根据其所属的专业组的职能承担项目的相应任务。

2. 程序设计小组的组织方式

这里的程序设计小组主要是指从事软件开发活动的小组,有以下3种不同的组织形式。

1)主程序员制小组

主程序员制小组由一名主程序员、若干名程序员、一名后援工程师和一名资料员组成。主程序员通常由高级工程师担任,负责小组的全部技术活动,进行任务的分配,协调技术问题,组织评审,必要时也设计和实现项目中的关键部分。程序员负责完成主程序员指派的任务,包括相关文档的编写。后援工程师协助主程序员工作,必要时能替代主程序员,也做部分开发工作。资料员负责小组中所有文档资料的管理,收集与过程度量相关的数据,为评审准备资料。一个资料员可以同时服务于多个小组。

主程序员制小组突出了主程序员的领导作用,小组内的通信主要体现在主程序员与程序员之间。

2)民主制小组

民主制小组成员之间地位平等,虽然形式上有一位组长,但小组的工作目标和决策都是由全体成员决定的,互相合作,形成一个良好的工作氛围。另外,这种形式的组内通信路径较多。

3)层次式小组

层次式小组的组织形式是一名组长领导若干名高级程序员,每名高级程序员领导若干名程序员。组长通常就是项目负责人,负责全组的技术工作,进行任务分配,组织评审。高级程序员负责项目的一个部分或一个子系统,负责该部分的分析、设计,并将子任务分配给程序员。这种组织形式适合于具有层次结构特征的项目的开发。其组内的通信路径数介于主程序员制小组和民主制小组之间。

5.7.5 软件配置管理

在软件开发过程中变更是不可避免的,而变更时由于没有进行变更控制,可能加剧了项目中的混乱,为了协调软件开发使得混乱减到最小,使用配置管理技术,使变更所产生的错误达到最小并最有效地提高生产率。

软件配置管理(Software Configure Management,SCM)用于整个软件工程过程。其主要目标是标识变更;控制变更;确保变更正确地实现;报告有关变更。SCM是一组管理整个软件生存周期中各阶段变更的活动。

1. 基线

基线是软件生存周期中各开发阶段的一个特定点,它的作用是使各开发阶段的工作划分更加明确,使本来连续的工作在这些点上断开,以便于检查与肯定阶段成果。因此,基线可以作为一个检查点,在开发过程中,当采用的基线发生错误时可以知道所处的位置,返回到最近和最恰当的基线上。

2. 软件配置项

软件配置项(Software Configure Item,SCI)是软件工程中产生的信息项,它是配置管理的基本单位,对于已经成为基线的SCI,虽然可以修改,但必须按照一个特殊的、正式的过程进行评估,确认每一处修改。以下的SCI是SCM的对象,并可形成基线。

(1)系统规格说明书。

(2)软件项目实施计划。

(3)软件需求规格说明书。

(4)设计规格说明书(数据设计、体系结构设计、模块设计、接口设计、对象描述(使用面向对象技术时))。

(5)源代码清单。

(6)测试计划和过程、测试用例和测试结果记录。

(7)操作和安装手册。

(8)可执行程序(可执行程序模块、连接模块)。

(9)数据库描述(模式和文件结果、初始内容)。

(10)用户手册。

(11)维护文档(软件问题报告、维护请求、工程变更次序)。

(12)软件工程标准。

(13)项目开发小结。

此外,许多软件工程组织把配置控制之下的软件工具,即编辑程序、编译程序、其他CASE工具的特定版本都作为软件配置的一部分列入其中。

3. 版本控制

软件配置实际上是一个动态的概念,它一方面随着软件生存周期向前推进,SCI的数量在不断增多,一些文档经过转换生成另一些文档,并产生一些信息;另一方面又随时会有新的变更出现,形成新的版本。

可以采用演变图来表达系统的不同版本,在图中各个结点是一个完全的软件版本。软件的每一个版本都是SCI(源代码、文档、数据)的一个汇集,而且各个版本都可能由不同的变种组成。

4. 变更控制

软件工程过程中某一阶段的变更均要引起软件配置的变更,这种变更必须严格地加以控制和管理,保持修改信息,并把精确、清晰的信息传递到软件工程过程的下一步骤。

对于一个大型软件来说,不加控制的变更很快就会引起混乱。因此,变更控制是一项最重要的软件配置任务。为了有效地实现变更控制,需借助于配置数据库和基线的概念。

配置数据库可以分为以下三类。

(1)开发库。专供开发人员使用,其中的信息可能做频繁修改,对其控制相当宽松。

(2)受控库。在生存期某一阶段工作结束时发布的阶段产品,这些是与软件开发工作相关的计算机可读信息和人工可读信息。软件配置管理正是对受控库中的各个软件项进行管理,受控库也称为软件配置库。

(3)产品库。在开发的软件产品完成系统测试后,作为最终产品存入产品库,等待交付用户或现场安装。

5.7.6 风险管理

一般认为软件风险包含两个特性:不确定性和损失。不确定性是指风险可能发生也可能不发生;损失是指如果风险发生,就会产生恶性后果。在进行风险分析时,重要的是量化每个风险的不确定程度和损失程度。为了实现这一点,必须考虑不同类型的风险。

项目风险威胁到项目计划。也就是说,如果项目风险发生,就有可能拖延项目的进度和增加项目的成本。项目风险是指预算、进度、人员(聘用职员及组织)、资源、利益相关者、需求等方面的潜在问题以及它们对软件项目的影响。项目复杂度、规模及结构不确定性也属于项目风险因素。

技术风险威胁到要开发软件的质量及交付时间。如果技术风险发生,开发工作就可能变得很困难或根本不可能。技术风险是指设计、实现、接口、验证和维护等方面的潜在问题。此外,规格说明的歧义性、技术的不确定性、技术陈旧以及”前沿”技术也是技术风险因素。技术风险的发生是因为问题比我们所设想的更加难以解决。

商业风险威胁到要开发软件的生存能力,且常常会危害到项目或产品。5个主要的商业风险如下。

(1)市场风险。开发了一个没有人真正需要的优良产品或系统。

(2)策略风险。开发的产品不再符合公司的整体商业策略。

(3)销售风险。开发了一个销售部门不知道如何去销售的产品。

(4)管理风险。由于重点的转移或人员的变动而失去了高级管理层的支持。

(5)预算风险。没有得到预算或人员的保证。

另一种常用的风险分类方式是由Charette提出的。已知风险是通过仔细评估项目计划、开发项目的商业和技术环境以及其他可靠的信息来源(如不现实的交付时间、没有文档化需求或文档化软件范围、恶劣的开发环境)之后可以发现的那些风险。可预测风险能够从过去项目的经验中推断出来(如人员变动、与客户缺乏沟通、由于正在进行维护而使开发人员精力分散)。不可预测风险可能会真的出现,但很难事先识别。

1. 风险识别

风险识别试图系统化地指出对项目计划(估算、进度、资源分配等)的威胁。识别出已知风险和可预测风险后,项目管理者首先要做的是在可能时回避这些风险,在必要时控制这些风险。

识别风险的一种方法是建立风险条目检查表。该检查表可用于风险识别,并且主要用来识别下列几种类型中的一些已知风险和可预测风险。

(1)产品规模。与要开发或要修改的软件的总休规模相关的风险。

(2)商业影响。与管理者或市场所施加的约束相关的风险。

(3)客户特性。与客户的素质以及开发者和客户定期沟通的能力相关的风险。

(4)过程定义。与软件过程定义的程度以及该过程被开发组织遵守的程度相关的风险。

(5)开发环境。与用来开发产品的工具的可得性及质量相关的风险。

(6)开发技术。与待开发软件的复杂性及系统所包含技术的”新奇性”相关的风险。

(7)人员才干及经验。与软件工程师的总体技术水平及项目经验相关的风险。

风险条目检查表可以采用不同的方式来组织。与上述每个主题相关的问题可以针对每一个软件项目来回答。根据这些问题的答案,项目管理者就可以估计风险产生的影响。

当然,也可以采用另一种风险条目检查表格式,即仅仅列出与每一种类型有关的特性,最终给出一组风险因素和驱动因子以及它们发生的概率。风险因素包括性能、成本、支持和进度。风险因素是以如下方式定义的。

(1)性能风险。产品能够满足需求且符合其使用目的的不确定程度。

(2)成本风险。能够维持项目预算的不确定程度。

(3)支持风险。开发出的软件易于纠错、修改及升级的不确定程度。

(4)进度风险。能够维持项目进度且按时交付产品的不确定程度。

2. 风险预测

风险预测又称风险估计,它试图从两个方面评估一个风险:风险发生的可能性或概率;如果风险发生了所产生的后果。

1)风险预测活动

通常,项目计划人员与管理人员、技术人员一起进行以下4步风险预测活动。

(1)建立一个尺度或标准,以反映风险发生的可能性。

(2)描述风险产生的后果。

(3)估算风险对项目和产品的影响。

(4)标注风险预测的整体精确度,以免产生误解。

一种简单的风险预测技术是建立风险表。风险表的第1列列出所有的风险(由风险识别活动得到),第2~4列列出每个风险的种类、发生的概率以及所产生的影响。风险所产生的影响可用一个数字来表示:”1”表示灾难性的;”2”表示严重的;”3”表示轻微的;”4”表示可忽略的。

2)评估风险影响

如果风险真的发生,有3个因素可能会影响风险所产生的后果,即风险的本质、范围和时间。风险的本质是指当风险发生时可能带来的问题。例如,一个定义很差的与客户硬件的外部接口(技术风险)会妨碍早期的设计和测试,也有可能导致项目后期阶段的系统集成问题。风险的范围包括风险的严重性(即风险有多严重)及风险的整体分布情况(即项目中有多少部分受到影响或有多少客户受到损害)。风险的时间是指何时能够感受到风险的影响及风险的影响会持续多长时间。在大多数情况下,项目管理者希望”坏消息”越早出现越好,但在某些情况下则是越迟越好。

整体的风险显露度(Risk Exposure,RE)可由下面的关系确定:

$$ RE = P \times C $$

其中,$P$ 是风险发生的概率,$C$ 是风险发生时带来的项目成本。

3. 风险评估

在进行风险评估时,建立了如下形式的三元组:

$$ (r_i, l_i, x_i) $$

其中,$r_i$ 表示风险,$l_i$ 表示风险发生的概率,$x_i$ 表示风险产生的影响。

一种对风险评估很有用的技术就是定义风险参照水准。对于大多数软件项目来说,成本、进度和性能就是3种典型的风险参照水准。也就是说,对于成本超支、进度延期、性能降低(或它们的某种组合),有一个表明导致项目终止的水准。

在风险评估过程中,需要执行以下4个步骤。

(1)定义项目的风险参考水平值。

(2)建立每一组 $(r_i, l_i, x_i)$ 与每一个参考水平值之间的关系。

(3)预测一组临界点以定义项目终止区域,该区域由一条曲线或不确定区域所界定。

(4)预测什么样的风险组合会影响参考水平值。

4. 风险控制

风险控制的目的是辅助项目组建立处理风险的策略。一个有效的策略必须考虑以下3个问题。

1)风险避免

应对风险的最好办法是主动地避免风险,即在风险发生前分析引起风险的原因,然后采取措施,以避免风险的发生。

例如项目风险 $r_i$ 表示”频繁的人员流动”,根据历史经验可知,该风险发生的概率 $l_i$ 大约为70%,该风险产生的影响 $x_i$ 是第2级(严重的)。为了避免该风险,可以采取以下策略。

(1)与现有人员一起探讨人员流动原因(如恶劣的工作条件、低报酬、竞争激烈的劳动力市场等)。

(2)在项目开始之前采取行动,设法缓解那些能够控制的起因。

(3)项目启动之后,假设会发生人员流动,当有人员离开时,找到能够保证工作连续性的方法。

(4)组织项目团队,使得每一个开发活动的信息都能被广泛传播和交流。

(5)制定工作产品标准,并建立相应机制以确保能够及时创建所有的模型和文档。

(6)同等对待所有工作的评审。

(7)给每一个关键的技术人员都指定一个后备人员。

2)风险监控

项目管理者应监控某些因素,这些因素可以提供风险是否正在变高或变低的指示。在频繁的人员流动的例子中,应该监测团队成员对项目压力的普遍态度、团队的凝聚力、团队成员彼此之间的关系、与报酬和利益相关的潜在问题、在公司内及公司外工作的可能性。

3)RMMM计划

风险管理策略可以包含在软件项目计划中,或者风险管理步骤也可以组织成一个独立的风险缓解、监控和管理计划(RMMM计划)。RMMM计划将所有风险分析工作文档化,并由项目管理者作为整个项目计划中的一部分来使用。

建立了RMMM计划,而且项目已经启动之后,风险缓解及监测步骤也就开始了。风险缓解是一种问题规避活动,而风险监测是一种项目跟踪活动,这种监测活动有3个主要目的:评估所预测的风险是否真的发生了;保证正确地实施了各风险的缓解步骤;收集能够用于今后风险缝隙的信息。在很多情况下,项目中发生的问题可以追溯到不止一个风险,所以风险监测的另一个任务就是试图找到”起源”(在整个项目中是哪些风险引起了哪些问题)。

5.8 软件质量

软件质量是指反映软件系统或软件产品满足规定或隐含需求的能力的特征和特性全体。软件质量管理是指对软件开发过程进行独立的检查活动,由质量保证、质量规划和质量控制3个主要活动构成。软件质量保证是指为保证软件系统或软件产品充分满足用户要求的质量而进行的有计划、有组织的活动,其目的是生产高质量的软件。

5.8.1 软件质量特性

讨论软件质量首先要了解软件的质量特性,目前已经有多种软件质量模型来描述软件质量特性,例如ISO/IEC 9126软件质量模型和Mc Call软件质量模型。

1)ISO/IEC 9126软件质量模型

ISO/IEC 9126软件质量模型由3个层次组成:第一层是质量特性,第二层是质量子特性,第三层是度量指标。该模型的质量特性和质量子特性如下。

质量特性与质量子特性对照表

质量特性 质量子特性
功能性 适合性、准确性、互用性、依从性、安全性
可靠性 成熟性、容错性、易恢复性
易使用性 易理解性、易学性、易操作性
效率 时间特性、资源特性
可维护性 易分析性、易改变性、稳定性、易测试性
可移植性 适应性、易安装性、一致性、易替换性

其中,各质量特性和质量子特性的含义如下。

(1)功能性(Functionality)。与一组功能及其指定的性质的存在有关的一组属性,功能是指满足规定或隐含需求的那些功能。

  • 适应性(Suitability)。与对规定任务能否提供一组功能以及这组功能是否适合有关的软件属性。
  • 准确性(Accurateness)。与能够得到正确或相符的结果或效果有关的软件属性。
  • 互用性(Interoperability)。与其他指定系统进行交互操作的能力相关的软件属性。
  • 依从性(Compliance)。使软件服从有关的标准、约定、法规及类似规定的软件属性。
  • 安全性(Security)。与避免对程序及数据的非授权故意或意外访问的能力有关的软件属性。

(2)可靠性(Reliability)。与在规定的一段时间内和规定的条件下软件维持在其性能水平有关的能力。

  • 成熟性(Maturity)。与由软件故障引起失效的频度有关的软件属性。
  • 容错性(Fault tolerance)。与在软件错误或违反指定接口的情况下维持指定的性能水平的能力有关的软件属性。
  • 易恢复性(Recoverability)。与在故障发生后,重新建立其性能水平并恢复直接受影响数据的能力,以及为达到此目的所需的时间和努力有关的软件属性。

(3)易使用性(Usability)。与为使用所需的努力和由一组规定或隐含的用户对这样使用所做的个别评价有关的一组属性。

  • 易理解性(Understandability)。与用户为理解逻辑概念及其应用所付出的劳动有关的软件属性。
  • 易学性(Learnability)。与用户为学习其应用(例如操作控制、输入、输出)所付出的努力相关的软件属性。
  • 易操作性(Operability)。与用户为进行操作和操作控制所付出的努力有关的软件属性。

(4)效率(Efficiency)。在规定条件下,与软件的性能水平与所用资源量之间的关系有关的软件属性。

  • 时间特性(Time behavior)。与响应和处理时间以及软件执行其功能时的吞吐量有关的软件属性。
  • 资源特性(Resource behavior)。与软件执行其功能时,所使用的资源量以及使用资源的持续时间有关的软件属性。

(5)可维护性(Maintainability)。与进行规定的修改所需要的努力有关的一组属性。

  • 易分析性(Analyzability)。与为诊断缺陷或失效原因,或为判定待修改的部分所需努力有关的软件属性。
  • 易改变性(Changeability)。与进行修改、排错或适应环境变换所需努力有关的软件属性。
  • 稳定性(Stability)。与修改造成未预料效果的风险有关的软件属性。
  • 易测试性(Testability)。为确认经修改软件所需努力有关的软件属性。

(6)可移植性(Portability)。与软件可从某一环境转移到另一环境的能力有关的一组属性。

  • 适应性(Adaptability)。与软件转移到不同环境时的处理或手段有关的软件属性。
  • 易安装性(Installability)。与在指定环境下安装软件所需努力有关的软件属性。
  • 一致性(Conformance)。使软件服从与可移植性有关的标准或约定的软件属性。
  • 易替换性(Replaceability)。与一软件在该软件环境中用来替代指定的其他软件的可能和努力有关的软件属性。

2)Mc Call软件质量模型

Mc Call软件质量模型从软件产品的运行、修正和转移3个方面确定了11个质量特性。Mc Call也给出了一个三层模型框架,第一层是质量特性,第二层是评价准则,第三层是度量指标。

Mc Call软件质量模型质量特性

方面 质量特性
产品运行 正确性(correctness)、可靠性、效率(efficiency)、完整性(integrity)、易使用性
产品修正 可维护性、灵活性(flexibility)、可测试性
产品转移 可移植性、复用性(reusability)、互用性

5.8.2 软件质量保证

软件质量保证是指为保证软件系统或软件产品充分满足用户要求的质量而进行的有计划、有组织的活动,其目的是生产高质量的软件。在软件质量方面强调以下3个要点。

(1)软件必须满足用户规定的需求,与用户需求不一致的软件无质量可言。

(2)软件应遵循规定标准所定义的一系列开发准则,不遵循这些准则的软件,其质量难以得到保证。

(3)软件还应满足某些隐含的需求,例如希望有好的可理解性、可维护性等,而这些隐含的需求可能未被明确地写在用户规定的需求中,如果软件只满足它的显式需求而不满足其隐含需求,那么该软件的质量是令人质疑的。

软件质量保证包括了与以下7个主要活动相关的各种任务。

(1)应用技术方法。应该把软件质量设计到软件产品或软件系统中,而不是在事后再施加质量保证。由于这个原因,软件质量保证首先从选择一组技术方法和工具开始,这些方法和工具帮助分析人员形成高质量的规格说明和高质量的设计。

(2)进行正式的技术评审。一旦形成了规格说明和设计,就要对它们进行质量评估。完成质量评估的中心活动是正式的技术评审。正式的技术评审是一种由技术人员实施的程式化会议,其唯一的目的是揭露质量问题。在多数情况下,评审能像测试一样有效地揭露软件中的缺陷。

(3)测试软件。软件测试组合了多种测试策略,这些测试策略带有一系列有助于有效地检测错误的测试用例设计方法。许多软件开发人员把软件测试用作质量保证的”安全网”,也就是说,开发人员以为通过测试能揭露最多的错误,借此减轻对其他软件质量保证活动的需要。遗憾的是,即使是完成得很好的测试也不会像我们所期望的那样揭露所有的错误种类。

(4)标准的实施。对软件工程过程应用正式的开发标准和过程的程度在各公司中是不同的。多数情况下,标准由客户或者某些章程确定。某些场合下,标准是自己确定的。如果存在正式的标准,软件质量保证活动必须保证遵循这些标准。与标准是否一致的评估可以被软件开发者作为正式技术评审的一部分来进行。

(5)控制变更。对软件质量的主要威胁来自”变更(Change)”。对软件的每次变更都有可能引入错误或者引起传播错误的副作用。变更控制过程是通过对变更的正式申请、评价变更的特性和控制变更的影响等直接提高软件的质量。变更控制应用在软件开发期间和较后的软件维护阶段。

(6)度量(Metrics)。度量是任何工程科学必备的活动。软件质量保证的重要目标是跟踪软件质量和评价方法学及程序上的变更对软件质量的影响程度。如果要达到这个目标,应该收集软件度量。软件度量包括某些技术上的和面向管理的度量。

(7)记录保存和报告。记录保存和报告为软件质量保证提供收集和传播软件质量保证信息的过程。评审、检查、变更控制、测试和其他软件质量保证活动的结果必须变成项目历史记录的一部分,并且应该把它传播给需要知道这些结果的开发人员。例如,记录过程设计的每次正式技术评审结果,并把记录放置在文件夹中,该文件夹包含了有关模块的所有技术信息和软件质量保证信息。

5.8.3 软件评审

通常,把”质量”理解为”用户满意程度”。为了使得用户满意,有以下两个必要条件。

(1)设计的规格说明书符合用户的要求,这称为设计质量。

(2)程序按照设计规格说明所规定的情况正确执行,这称为程序质量。

软件的规格说明分为外部规格说明和内部规格说明。外部规格说明是从用户角度来看的规格,包括硬件/软件系统设计、功能设计;内部规格说明是为了实现外部规格的更详细的规格,即软件模块结构与模块处理过程的设计。因此,内部规格说明是从开发者角度来看的规格说明。设计质量是由外部规格说明决定的,程序是由内部规格说明决定的。

1)设计质量的评审内容

设计质量评审的对象是在需求分析阶段产生的软件需求规格说明、数据需求规格说明,以及在软件概要设计阶段产生的软件概要设计说明书等。通常从以下几个方面进行评审。

(1)评价软件的规格说明是否合乎用户的要求,即总体设计思想和设计方针是否明确;需求规格说明是否得到了用户或单位上级机关的批准;需求规格说明与软件的概要设计规格说明是否一致等。

(2)评审可靠性,即是否能避免输入异常(错误或超载等)、硬件失效及软件失效所产生的失效,一旦发生应能及时采取代替手段或恢复手段。

(3)评审保密措施实现情况,即是否对系统使用资格进行检查;是否对特定数据、特定功能的使用资格进行检查;在检查出有违反使用资格的情况后,能否向系统管理人员报告有关信息;是否提供对系统内重要数据加密的功能等。

(4)评审操作特性实施情况,即操作命令和操作信息的恰当性;输入数据与输入控制语句的恰当性;输出数据的恰当性;应答时间的恰当性等。

(5)评审性能实现情况,即是否达到所规定性能的目标值。

(6)评审软件是否具有可修改性、可扩充性、可互换性和可移植性。

(7)评审软件是否具有可测试性。

(8)评审软件是否具有复用性。

2)程序质量的评审内容

程序质量评审通常是从开发者的角度进行评审,与开发技术直接相关。它是着眼于软件本身的结构、与运行环境的接口以及变更带来的影响而进行的评审活动。

软件的结构如下。

(1)功能结构。在软件的各种结构中,功能结构是用户唯一能见到的结构。因此,功能结构是联系用户与开发者的规格说明,它在软件的设计中占有极其重要的地位。在评审软件的功能结构时,必须明确软件的数据结构。需要检查的项目如下。

  • 数据结构。包括数据名和定义;构成该数据的数据项;数据与数据之间的关系。
  • 功能结构。包括功能名和定义;构成该功能的子功能;功能与子功能之间的关系。
  • 数据结构和功能结构之间的对应关系。包括数据元素与功能元素之间的对应关系;数据结构与功能结构的一致性。

(2)功能的通用性。在软件的功能结构中,某些功能有时可以作为通用功能反复出现多次。从功能便于理解、增强软件的通用性及降低开发的工作量等观点出发,希望尽可能多地使功能通用化。另外,需检查的项目包括抽象数据结构(抽象数据的名称和定义、抽象数据组成元素的定义)、抽象功能结构。

(3)模块的层次。模块的层次是指程序模块结构。由于模块是功能的具体体现,所以模块层次应当根据功能层次来设计。

(4)模块结构。上述的模块层次结构是模块的静态结构,现在要检查模块的动态结构。模块分为处理模块和数据模块两类,模块间的动态结构也与这些模块分类有关。对这样的模块结构进行检查的项目如下。

  • 控制流结构。规定了处理模块与处理模块之间的流程关系,检查处理模块之间的控制转移关系与控制转移形式(调用方式)。
  • 数据流结构。规定了数据模块是如何被处理模块进行加工的流程关系,检查处理模块与数据模块之间的对应关系;处理模块与数据模块之间的存取关系。
  • 模块结构与功能结构之间的对应关系。包括功能结构与控制流结构的对应关系;功能结构与数据流结构的对应关系;每个模块的定义(功能、输入/输出数据)。

(5)处理过程的结构。处理过程是最基本的加工逻辑过程。对它的检查项目有模块的功能结构与实现这些功能的处理过程的结构应明确对应;控制流应是结构化的;数据的结构与控制流之间的对应关系应是明确的,并且可根据这种对应关系来明确数据流程的关系;用于描述的术语标准化。

3)与运行环境的接口

运行环境包括硬件、其他软件和用户,主要的检查项目如下。

(1)与硬件的接口。包括与硬件的接口约定,即根据硬件的使用说明等所做出的规定;硬件故障时的处理和超载时的处理。

(2)与用户的接口。包括与用户的接口约定,即输入数据的结构;输出数据的结构;异常输入时的处理,超载输入时的处理;用户存取资格的检查等。

5.8.4 软件容错技术

提高软件质量和可靠性的技术大致可分为两类,一类是避开错误,即在开发的过程中不让差错潜入软件的技术;另一类是容错技术,即对某些无法避开的差错,使其影响减至最小的技术。

1)容错软件的定义

归纳容错软件的定义,有以下4种。

(1)规定功能的软件,在一定程度上对自身错误的作用(软件错误)具有屏蔽能力,则称该软件为具有容错功能的软件,即容错软件。

(2)规定功能的软件,在一定程度上能从错误状态自动恢复到正常状态,则称该软件为容错软件。

(3)规定功能的软件,在因错误发生错误时仍然能在一定程度上完成预期的功能,则称该软件为容错软件。

(4)规定功能的软件,在一定程度上具有容错能力,则称该软件为容错软件。

2)容错的一般方法

实现容错的主要手段是冗余。冗余是指对于实现系统规定功能是多余的那部分资源,包括硬件、软件、信息和时间。由于加入了这些资源,有可能使系统的可靠性得到较大的提高。通常,冗余技术分为4类。

(1)结构冗余。结构冗余是通常采用的冗余技术,按其工作方法可以分为静态、动态和混合冗余3种。

① 静态冗余。常用的有三模冗余(Triple Module Redundancy,TMR)和多模冗余。静态冗余通过表决和比较来屏蔽系统中出现的错误。如三模冗余是对3个功能相同但由不同的人采用不同的方法开发出来的模块的运行结果通过表决,以多数结果作为系统的最终结果。即如果模块中有一个出错,这个错误能够被其他模块的正确结果”屏蔽”。由于无须对错误进行特别的测试,也不必进行模块的切换就能实现容错,故称之为静态容错。

② 动态冗余。动态冗余的主要方式是多重模块待机储备。当系统测试到某工作模块出现错误时,就用一个备用模块来顶替它并重新运行。这里包括检测、切换和恢复过程,故称其为动态冗余。每当一个出错模块被其他备用模块顶替后,冗余系统相当于进行了一次重构。各备用模块在其待机时可与主模块一同工作,也可不工作。前者称为热备份系统,后者称为冷备份系统。在热备份系统中,备用模块在待机过程中的失效率为0。

③ 混合冗余。它兼有静态冗余和动态冗余的长处。

(2)信息冗余。为检测或纠正信息在运算或传输中的错误需外加一部分信息,这种现象称为信息冗余。在通信和计算机系统中,信息常以编码的形式出现。采用奇偶码、循环码等冗余码制式就可以发现甚至纠正这些错误。为了达到此目的,这些码(统称为误差校验码)的码长远远大于不考虑误差校正时的码长,增加了计算量和信道占用的时间。

(3)时间冗余。时间冗余是指以重复执行指令或程序来消除瞬时错误带来的影响。对于重复执行不成功的情况,通常的处理办法是发出中断,转入错误处理程序,或对程序进行复算,或重新组合系统,或放弃程序处理。在程序复算中比较常用的方法是程序滚回(Program Rollback)技术。

(4)冗余附加技术。冗余附加技术是指为实现上述冗余技术所需的资源和技术,包括程序、指令、数据、存放和调动它们的空间和通道等。

在屏蔽硬件错误的容错技术中,冗余附加技术包括:

① 关键程序和数据的冗余存储及调用。

② 检测、表决、切换、重构、纠错和复算的实现。

在屏蔽软件错误的容错系统中,冗余附加技术的构成包括:

① 冗余备份程序的存储及调用。

② 实现错误检测和错误恢复的程序。

③ 实现容错软件所需的固化程序。

5.9 软件度量

软件度量用于对产品及开发产品的过程进行度量。软件产品、软件过程、资源都具有外部属性和内部属性。外部属性是指面向管理者和用户的属性,体现了软件产品/软件过程与相关资源和环境的关系,如成本、效益、开发人员的生产率,经常可采用直接测量的方法进行。而软件的内部属性是指软件产品或软件过程本身的属性,如可靠性、可维护性等,只能用间接测量的方法度量,间接测量就需要一定的测量方法或模型。

5.9.1 软件度量分类

软件度量有两种分类方法,第一种分类是将软件度量分为面向规模的度量、面向功能的度量和面向人的度量;第二种分类是将软件度量分为生产率度量、质量度量和技术度量。

软件生产率度量主要关注于软件工程活动的制品。软件质量度量可指明软件满足明确的和隐含的用户需求的程度。技术度量主要集中在软件产品的某些特征(如逻辑复杂性、模块化程度等)上,而不是软件开发的全过程。

面向规模的度量用于收集与软件规模相关的软件工程输出信息和质量信息,面向功能的度量则集中在程序的”功能性”和”实用性”。面向人的度量收集有关人们开发软件所用方式的信息和人员理解有关工具的方法和效率的信息。还有基于问题、基于过程、基于用例等成本估算方法。

1. 面向规模的度量

面向规模的度量是通过对质量和(或)生产率的测量进行规范化得到的,而这些量都是根据开发过的软件的规模得到的。

软件规模通常用程序的代码行(Line of Code,LOC)或千行代码KLOC来衡量。由于代码行自然、直观地反映了软件项目的规模,也容易直接测量,因此面向规模的度量是一种常用的度量方法。计算出软件项目的代码行后,可方便地度量其他的软件属性,如软件开发的生产率、每行代码的平均开发成本、文档数量(页数)与代码量(KLOC)的比例关系、每千行代码中包含的软件错误数等。表5-2给出了面向规模的常用度量公式。其中,工作量和成本不仅仅是编码活动的工作量和成本,而是指整个软件工程活动(包括分析、设计、编码和测试)的工作量成本。

表5-2 面向规模的度量公式

度量 表示及含义
LOC或KLOC 代码行数或千行代码数
生产率 $P$ $P = \text{LOC}/E$,$E$ 为开发的工作量(常用人月数表示)
每行代码平均成本 $C$ $C = S/\text{LOC}$,$S$ 为总成本
文档代码比 $D$ $D = P_e/\text{KLOC}$,$P_e$ 为文档页数
代码错误率 EQR $\text{EQR} = N/\text{KLOC}$,$N$ 为代码中的错误数

虽然面向规模的度量方便、直观,但代码行数依赖于程序设计语言,对于同一个软件,采用不同程序设计语言编写的程序,代码行数是不同的。同时,对于一些因良好的设计而导致代码量小的软件来说,这种度量显得不够客观。

2. 面向功能的度量

面向功能的度量以功能(由应用程序提供)测量数据作为规范化值。应用最广泛的面向功能的度量是功能点(Function Point,FP)。功能点是根据软件信息域的特性及复杂性来计算的。

信息域的值用下列方式定义。

(1)外部输入数(EI)。每个外部输入源于一个用户,或从另一个应用系统中传送过来,它提供了面向不同应用系统的数据或控制信息,输入常用于更新内部逻辑文件(ILF),输入应该与独立计数的查询区分开来。

(2)外部输出数(EO)。每个外部输出从应用系统中导出,并为用户提供信息。在这种情况下,外部输出指的是报告、屏幕、错误消息等,不对报告中的单独数据项进行分开计数。

(3)外部查询数(EQ)。一个外部查询定义为一个在线输入。其结果是以在线输出(经常从 ILF 中得到)的方式产生某个即时软件响应。

(4)内部逻辑文件数(ILF)。每个内部逻辑文件是驻留在应用系统边界之内的数据逻辑分组,它通过外部输入来维护。

(5)外部接口文件数(EIF)。每个外部接口文件是驻留在应用系统外部的数据逻辑分组,它通过应用系统提供有用的信息。

利用下面的关系式计算功能点:

FP=总计×[0.65+0.01×∑(Fi)]F_P = \text{总计} \times [0.65 + 0.01 \times \sum(F_i)]

其中,”总计”是所有 $F_P$ 项的总数。$F_i(i=1\sim14)$ 是值调整因子。

5.9.2 软件复杂性度量

软件复杂性是指理解和处理软件的难易程度。软件复杂性度量的参数很多,主要有以下几个。

  • 规模。规模即总共的指令数,或源程序行数。
  • 难度。通常由程序中出现的操作数的数目所决定的量来表示。
  • 结构。通常用与程序结构有关的度量来表示。
  • 智能度。智能度即算法的难易程度。

软件复杂性包括程序复杂性和文档复杂性,软件复杂性主要体现在程序的复杂性中。

1. 程序复杂性度量原则

程序复杂性度量是软件度量的重要组成部分。开发规模相同、复杂性不同的程序花费的时间和成本会有很大的差异。K. Magel 从以下5个方面描述程序的复杂性。

  • 程序理解的难度。
  • 纠错、维护程序的难度。
  • 向他人解释程序的难度。
  • 根据设计文件编写程序的工作量。
  • 执行程序时需要资源的程度。

普遍认为,程序复杂性度量模型应遵循以下基本原则。

  • 程序复杂性与程序大小的关系不是线性的。
  • 控制结构复杂的程序较复杂。
  • 数据结构复杂的程序较复杂。
  • 转向语句使用不当的程序较复杂。
  • 循环结构比选择结构复杂,选择结构又比顺序结构复杂。
  • 语句、数据、子程序和模块在程序中的次序对复杂性有影响。
  • 全局变量、非局部变量较多时程序较复杂。
  • 函数的隐式副作用相对于显式参数传递而言更加难以理解。
  • 具有不同作用的变量共用一个名字时较难理解。
  • 模块间、过程间联系密切的程序比较复杂。
  • 嵌套程度越深,程序越复杂。

典型的程序复杂性度量有 McCabe 环路复杂性度量和 Halstead 的复杂性度量,本节主要介绍 McCabe 度量法。

2. McCabe 度量法

McCabe 度量法是由 Thomas McCabe 提出的一种基于程序控制流的复杂性度量方法。McCabe 复杂性度量又称为环路度量,它认为程序的复杂性在很大程度上取决于控制的复杂性。单一的顺序程序结构最为简单,循环和选择构成的环路越多,程序就越复杂。这种方法以图论为工具,先画出程序图,然后用该图的环路数作为程序复杂性的度量值。程序图是退化的程序流程图,也就是说,把程序流程图中的每个处理符号都退化成一个结点,原来连接不同处理符号的流线变成连接不同点的有向弧,这样得到的有向图称为程序图。程序图仅描述程序内部的控制流程,完全不表现对数据的具体操作以及分支和循环的具体条件。

根据图论,在一个强连通的有向图 $G$ 中,环的个数 $V(G)$ 由以下公式给出:

V(G)=m−n+2pV(G) = m - n + 2p

其中,$V(G)$ 是有向图 $G$ 中的环路数,$m$ 是图 $G$ 中弧的个数,$n$ 是图 $G$ 中的结点数,$p$ 是 $G$ 中的强连通分量个数。在一个程序中,从程序图的入口点总能到达图中的任何一个结点,因此,程序总是连通的,但不是强连通的。为了使程序图成为强连通图,从图的入口点到出口点加一条用虚线表示的有向边,使图成为强连通图。这样就可以使用上式计算环路复杂性了。

以图5-17为例,其中结点数 $n=6$,弧数 $m=9$,$p=1$,则有:

V(G)=m−n+2p=9−6+2=5V(G) = m - n + 2p = 9 - 6 + 2 = 5

即 McCabe 环路复杂的度量值为 5。

环路复杂性度量反映了程序(或模块)的控制结构的复杂性。McCabe 发现 $V(G)=10$ 是一个实际模块的上限。当模块的环路复杂度超过10时,要充分测试这个模块变得特别难。

5.10 软件工具与软件开发环境

自从提出了软件工程的概念以后,人们一方面着重于开发模型和开发方法的研究,以指导软件开发工作的顺利进行;另一方面着重于软件工具和环境的研究,以低成本、高效率的方式辅助软件的开发。于是出现了对计算机辅助软件工程(Computer Aided Software Engineering,CASE)的研究和实现。

计算机辅助软件工程是指使用计算机及相关的软件工具辅助软件开发、维护、管理等过程中各项活动的实施,以确保这些活动能高效率、高质量地进行。

5.10.1 软件工具

用来辅助软件开发、运行、维护、管理和支持等过程中的活动的软件称为软件工具。早期的软件工具主要用来辅助程序员编程,如编辑程序、编译程序和排错程序等。在提出了软件工程的概念以后,又出现了软件生存周期的概念,出现了许多开发模型和开发方法,并且软件管理也开始受到人们的重视。与此同时,出现了一批软件工具来辅助软件工程的实施,这些软件工具涉及软件开发、维护、管理过程中的各项活动,并辅助这些活动高效、高质量地进行。

1. 软件开发工具

对应于软件开发过程的各种活动,软件开发工具通常有需求分析工具、设计工具、编码与排错工具、测试工具等。

(1)需求分析工具。用于辅助软件需求分析活动的软件称为需求分析工具,它辅助系统分析员从需求定义出发,生成完整的、清晰的、一致的功能规范(Functional Specification)。功能规范是软件所要完成功能的准确而完整的陈述,它描述该软件要做什么以及只做什么。

按照需求定义的方法可将需求分析工具分为基于自然语言或图形描述的工具和基于形式化需求定义语言的工具。

(2)设计工具。用于辅助软件设计活动的软件称为设计工具,它辅助设计人员从软件功能规范出发,得到相应的设计规范(Design Specification)。对应于概要设计活动和详细设计活动,设计工具通常可分为概要设计工具和详细设计工具。

概要设计工具用于辅助设计人员设计目标软件的体系结构、控制结构和数据结构,详细设计工具用于辅助设计人员设计模块的算法和内部实现细节。除此之外,还有基于形式化描述的设计工具和面向对象的分析与设计工具。

(3)编码与排错工具。辅助程序员进行编码活动的工具有编码工具和排错工具。编码工具辅助程序员用某种程序设计语言编写源程序,并对源程序进行翻译,最终转换成可执行的代码。因此,编码工具通常与编码所使用的程序语言密切相关。排错工具用来辅助程序员寻找源程序中错误的性质和原因,并确定其出错的位置。

(4)测试工具。用于支持软件测试的工具称为测试工具,它分为数据获取工具、静态分析工具、动态分析工具、模拟工具以及测试管理工具。其中,静态分析工具通过对源程序的程序结构、数据流和控制流进行分析,得出程序中函数(过程)的调用与被调用关系、分支和路径、变量定义和引用等情况,发现语义错误。动态分析工具通过执行程序检查语句、分支和路径覆盖,测试有关变量值的断点,即对程序的执行流进行探测。

2. 软件维护工具

辅助软件维护过程中活动的软件称为软件维护工具,它辅助维护人员对软件代码及其文档进行各种维护活动。软件维护工具主要有版本控制工具、文档分析工具、开发信息库工具、逆向工程工具和再工程工具。

(1)版本控制工具。在软件开发和维护过程中,一个软件往往有多个版本,版本控制工具用来存储、更新、恢复和管理一个软件的多个版本。

(2)文档分析工具。文档分析工具用来对软件开发过程中形成的文档进行分析,给出软件维护活动所需的维护信息。例如,基于数据流图的需求文档分析工具可以给出对数据流图的某个成分(如加工)进行维护时的影响范围及被影响范围,以便在修改该成分的同时考虑其影响范围内的其他成分是否也要修改。除此之外,利用文档分析工具还可以得到被分析文档的有关信息,例如文档各种成分的个数、定义及引用情况等。

(3)开发信息库工具。开发信息库工具用来维护软件项目的开发信息,包括对象、模块等。它记录每个对象的修改信息(已确定的错误及重要改动)和其他变形(如抽象数据结构的多种实现),还必须维护对象和与之有关信息之间的关系。

(4)逆向工程工具。逆向工程工具辅助软件人员将某种形式表示的软件(源程序)转换成更高抽象形式表示的软件。这种工具力求恢复源程序的设计信息,使软件变得更容易理解。逆向工程工具分为静态的和动态的两种。

(5)再工程工具。再工程工具用来支持重构一个功能和性能更为完善的软件系统。目前的再工程工具主要集中在代码重构、程序结构重构和数据结构重构等方面。

3. 软件管理和软件支持工具

软件管理和软件支持工具用来辅助管理人员和软件支持人员的管理活动和支持活动,以确保软件高质量地完成。辅助软件管理和软件支持的工具很多,其中常用的工具有项目管理工具、配置管理工具和软件评价工具。

(1)项目管理工具。项目管理工具用来辅助软件的项目管理活动。通常,项目管理活动包括项目的计划、调度、通信、成本估算、资源分配及质量控制等。一个项目管理工具通常把重点放在某一个或某几个特定的管理环节上,而不提供对管理活动包罗万象的支持。

(2)配置管理工具。配置管理工具用来辅助完成软件配置项的标识、版本控制、变化控制、审计和状态统计等基本任务,使得各配置项的存取、修改和系统生成易于实现,从而简化审计过程,改进状态统计,减少错误,提高系统的质量。

(3)软件评价工具。软件评价工具用来辅助管理人员进行软件质量保证的有关活动。它通常可以按照某个软件质量模型(如 McCall 软件质量模型、ISO 软件质量度量模型等)对被评价的软件进行度量,然后得到相关的软件评价报告。软件评价工具有助于软件质量控制,对确保软件质量有重要的作用。

软件工具的种类非常多,上面只列举了一些主要的也是常用的工具。软件工具可以从不同的角度进行分类,上述分类只是其中较流行的一种,而且这种分类并非是严格的,有些工具可以属于这一类,也可以属于另一类。

5.10.2 软件开发环境

软件开发环境(Software Development Environment)指支持软件产品开发的软件系统,它由软件工具集和环境集成机制构成。工具集用于支持软件开发的相关过程、活动和任务;环境集成机制为工具集成和软件开发、维护和管理提供统一的支持。

环境集成机制主要有数据集成、界面集成和控制集成,还有其他方面的集成,例如平台集成、方法与过程集成等。

数据集成为各种相互协作的工具提供统一的数据模式和数据接口规范,以实现不同工具之间的数据交换。

界面集成指环境中的工具的界面使用统一的风格,采用相同的交互方法,提供一种相似的视感效果,这样可以减少用户学习不同工具的开销。

控制集成用于支持环境中各个工具或开发活动之间的通信、切换、调度和协同工作,并支持软件开发过程的描述、执行与转换。

方法与过程集成指把多种开发方法、过程模型及其相关工具集成在一起。

平台集成是指在不同的硬件和系统软件之上构造用户界面一致的开发平台,并集成到统一的环境中。

软件开发环境的特征如下。

(1)环境的服务是集成的。软件开发环境应支持多种集成机制,如平台集成、数据集成、界面集成、控制集成和过程集成等。

(2)环境应支持小组工作方式,并为其提供配置管理。

(3)环境的服务可用于支持各种软件开发活动,包括分析、设计、编程、测试、调试和文档等。

集成型开发环境是一种把支持多种软件开发方法和开发模型的软件工具集成在一起的软件开发环境。这种环境应该具有开放性和可剪裁性。开放性为环境外的工具集成到环境中来提供了方便,可剪裁性可根据不同的应用和不同的用户需求进行剪裁,以形成特定的开发环境。