软件工程
1、概述
软件
软件的定义和特点
1. 软件的定义
软件是计算机系统中与硬件相互依存的另一部分,是程序、数据及其相关文档的完整集合。一种公认的传统的软件定义为
软件=程序+数据+文档
其中,程序是按事先设计的功能和性能要求执行的指令序列;数据是使程序能够正确地处理信息的数据结构;文档是与程序开发、维护和使用有关的图文资料。
为了更全面、正确地理解计算机和软件,必须了解软件的特点。软件是整个计算机系统中的一个逻辑部件,而硬件是一个物理部件。因此,软件具有与硬件完全不同的特点。
2. 软件的特点
1)形态特性
软件是一种逻辑实体,不具有具体的物理实体形态特性。软件具有抽象性,可以存储在介质中,但是无法看到软件本身的形态,必须经过观察、分析、思考和判断去了解它的功能、性能及其他的特性。
2)生产特性
软件与硬件的生产方式不同。与硬件或传统的制造产品的生产不同,软件一旦设计开发出来,如果需要提供给多个用户,它的复制十分简单,其成本也极为有限,正因为如此,软件产品的生产成本主要是设计开发的成本,同时也不能采用管理制造业生产的办法来解决软件开发的管理问题。
3)维护特性
软件与硬件的维护不同。硬件是有耗损的,其产生的磨损和老化会导致故障率增加甚至使得硬件损坏。软件不存在磨损和老化的问题,但是却存在退化的问题。在软件的生存期中,为了使它能够克服以前没有发现故障、适应软件和软件环境的变化及用户新的要求,必须要对其进行多次修改,而每次的修改都有可能引入新的错误,导致软件失效率升高,从而使软件退化,软硬件失效率曲线大致如下图所示。
(4)复杂特性
软件的复杂性一方面来自它所反映的实际问题复杂性,另一方面也来自程序结构的复杂性。软件技术的发展明显落后于复杂的软件需求,这个差距日益加大。软件复杂性与时间曲线如下图所示。
5)智能特性
软件是复杂的智力产品,它的开发凝聚了人们大量的脑力劳动,它本身也体现了知识、实践经验和人类的智慧,具有一定的智能。它可以帮助我们解决复杂的计算、分析、判断和决策问题。
6)质量特性
软件产品的质量控制存在着一些实际困难,难以克服,表现在以下方面。
(1)软件产品的需求在软件开发之初常常是不确切的,也不容易确切地给出,并且需求还会在开发过程中变更,这就使软件质量控制失去了重要的可参照物。
(2)软件测试技术存在不可克服的局限性。任何测试都只能在极大数量的应用实例数据中选取极为有限的数据,致使我们无法检验大多数实例,也使我们无法得到完全没有缺陷的软件产品。没有在已经长期使用或反复使用的软件中发现问题,并不意味着今后的使用也不会出现问题。
这一特性提醒我们:一定要警惕软件的质量风险,特别是在某些重要的应用场合需要提前准备好应对策略。
7)环境特性
软件的开发和运行都离不开相关的计算机系统环境,包括支持它的开发和运行的相关硬件和软件。软件对计算机系统的环境有着不可摆脱的依赖性。
8)软件的管理特性
上述的几个特点,使得软件的开发管理显得更为重要,也更为独特。这种管理可归结为对大规模知识型工作者的智力劳动管理,其中包括必要的培训、指导、激励、制度化规程的推行、过程的量化分析与监督,以及沟通、协调,甚至是软件文化的建立和实施。
9)软件的废弃特性
等到软件的运行环境变化过大,或是用户提出了更大、更多的需求变更,再对软件实施适应性维护已经不划算,说明该软件已走到它的生存期终点而将废弃(或称为退役),此时用户应考虑采用新的软件代替。因此,与硬件不同,软件并不是由于“用坏”而被废弃的。
10)应用特性
软件的应用极为广泛,如今它已渗透到国民经济和国防的各个领域,现已成为信息产业、先进制造业和现代服务业的核心,占据了无可取代的地位。
11)软件成本比较高
软件的研制工作需要投入大量、复杂、高强度的脑力劳动,研制成本比较高。在20世纪50年代末,软件的开销大约占总开销的百分之十几,大部分成本花在硬件上。但今天,这个比例完全颠倒过来,软件的开销远远超过硬件的开销。软、硬件成本随时间变化而变化的比例如下图所示。
软件的发展
软件工程是在克服20世纪60年代出现的“软件危机”过程中逐渐形成和发展的。在过去的50年时间里,软件工程在理论和实践方面都取得了长足的进步。它的发展已经经历了四个重要阶段。
1.第一代软件技术
20世纪60年代末,软件生产主要采用“生产作坊”方式。随着软件需求量及规模的迅速扩大,生产作坊方式已不能适应软件生产的需要,出现了所谓的“软件危机”,其主要表现为软件生产效率低下、软件产品质量低劣,在大量劣质的软件涌入市场后不久就在开发过程中夭折。由于“软件危机”的不断扩大,国际软件界面临着巨大的灾难,软件产业濒临崩溃。
为了克服“软件危机”,1968年在北大西洋公约组织(NATO)举行的软件可靠性学术会议上第一次提出了“软件工程”的概念,其核心是将软件开发纳入工程化的轨道,以保证软件开发的效率和质量。
此后,逐渐形成了软件工程的基本概念、框架、技术和方法,以结构化开发方法、Jackson方法等为代表的软件开发方法成为这一阶段的主要开发方法。这一阶段又称为传统的软件工程阶段。
2.第二代软件技术
从20世纪80年代中期开始,相继推出以Smalltalk为代表的面向对象的程序设计语言,面向对象的方法与技术得到迅速发展;从20世纪90年代起,软件工程研究的重点从程序设计语言逐渐转移到面向对象的分析与设计,演化为一种完整的软件开发方法和系统的技术体系。
20世纪90年代以来,形成了以Booch方法、OOSE、OMT等许多面向对象开发方法的流派,面向对象的方法逐渐成为软件开发的主流。尤其是1997年1月,综合了各种面向对象方法优点的统一建模语言UML1.0的正式推出,使面向对象的方法得到了进一步发展,所以这一阶段又称为对象工程。
3.第三代软件技术
随着软件工程规模和复杂度的不断增大,开发人员也随之增多,开发周期相应延长,加之软件是知识密集型的逻辑思维产品,这些都增加了软件工程管理的难度。人们在软件开发的实践过程中逐渐认识到:提高软件生产效率、保证软件质量的关键是对“软件过程”的控制和管理,是软件开发和维护的管理和支持能力。因此提出了对软件项目管理的计划、组织、成本估算、质量保证、软件配置管理等技术与策略,逐步形成了软件过程工程。
4.第四代软件技术
20世纪90年代起,软件复用和基于构件(Component)的开发方法取得重要进展,软件系统的开发可通过使用现成的可复用软件组装完成,而无须从头开始构造,以此达到提高效率和质量、降低成本的目的,软件复用技术及构件技术的发展,为克服软件危机提供了一条有效途径,这已成为当前软件工程的重要研究方向,这一阶段就称为构件阶段。
软件危机
软件危机的主要特征
软件危机的主要特征体现在以下几个方面。
1)软件开发进度难以预测
软件开发过程中的拖延工期现象并不罕见,这种现象降低了软件开发组织的信誉。
2)软件开发成本难以控制
软件开发中投资一再追加,往往实际成本要比预算成本高出一个数量级。而为了赶进度和节约成本所采取的一些权宜之计往往又损害了软件产品的质量,从而不可避免地引起用户的不满。
3)产品功能难以满足用户需求
开发人员和用户之间很难沟通,矛盾很难统一,往往软件开发人员不能真正了解用户的需求,而用户又不了解计算机求解问题的模式和能力,双方无法用共同熟悉的语言进行交流和描述。在双方不充分了解的情况下,就仓促上阵设计系统,匆忙着手编写程序,这种“闭门造车”的开发方式必然导致最终的产品不符合用户的实际需要。
4)软件产品质量无法保证
系统中的错误很难消除。软件是逻辑产品,质量问题很难以统一的标准来度量,因而造成质量控制困难。软件产品并不是没有错误,而是盲目检测很难发现错误,而隐藏下来的错误往往是造成重大事故的隐患。
5)软件产品难以维护
软件产品本质上是开发人员的代码化的逻辑思维活动的产物,他人难以替代,除非是开发者本人,否则很难及时检测、排除系统故障。为使系统适应新的硬件环境,或根据用户的需要,要在原系统中增加一些新的功能,这又有可能增加系统中的错误。
6)软件缺少适当的文档资料
文档资料是软件必不可少的组成部分。缺乏必要的文档资料或文档资料不合格,将给软件开发和维护带来许多严重的困难和问题。
软件危机的具体表现
20世纪60年代末期所发生的软件危机,反映在软件可靠性没有保障、软件维护工作量大、费用不断上升、进度无法预测、成本增长无法控制、程序设计人员无限度增加等各个方面,以致形成人们难以控制软件开发的局面。
软件危机主要表现在如下两个方面:
(1)软件产品质量低劣,甚至在开发过程中就夭折;
(2)软件生产率低,不能满足要求。
软件危机所造成的严重后果已使世界各国的软件产业危机四伏,面临崩溃,因此克服软件危机刻不容缓。自从NATO会议以来,世界各国的软件工作者为克服软件危机进行了许多开创性的工作,在软件工程的理论研究和工程实践两个方面都取得了长足的进步,缓解了软件危机。但距离彻底地克服软件危机这个软件工程的最终目标还任重道远,需要软件工作者付出长期艰苦的努力。
软件危机产生的原因
20世纪60年代,计算机已经应用在很多行业,解决问题的规模及难度逐渐增加,由于软件本身的特点及软件开发方法等多方面问题,软件的发展速度远远滞后于硬件的发展速度,不能满足社会日益增长的软件需求。软件开发周期长、成本高、质量差、维护困难,导致20世纪60年代末软件危机的爆发。导致软件危机爆发的原因主要可以概括为以下几点。
1)用户需求不明确
在软件被开发出来之前,用户自己也不清楚软件开发的具体需求;用户对软件开发需求的描述不精确,可能有遗漏、有二义性甚至有错误;在软件开发过程中,用户还会提出修改软件开发功能、界面、支撑环境等方面的要求;软件开发人员对用户需求的理解与用户的本来愿望有差异。
2)缺乏正确的理论指导
由于软件开发不同于大多数其他工业产品,其开发过程是复杂的逻辑思维过程,其产品很大程度上依赖于开发人员高度的智力投入。过分地依靠程序设计人员在软件开发过程中的技巧和创造性,加剧了软件开发产品的个性化,这也是发生软件危机的一个重要原因。
3)软件开发规模越来越大
随着软件开发应用范围的扩大,软件开发规模越来越大。大型软件开发项目需要组织一定的人力共同完成,然而多数管理人员缺乏开发大型软件系统的经验,多数软件开发人员又缺乏管理方面的经验。各类人员的信息交流不及时、不准确,有时还会产生误解。软件开发人员不能有效地、独立自主地处理大型软件开发的全部关系和各个分支,因此容易产生疏漏和错误。
4)软件开发复杂度越来越高
软件开发不仅仅是在规模上快速地发展扩大,而且其复杂性也急剧地增加。软件开发产品的特殊性和人类智力的局限性,导致人们无力处理“复杂问题”。所谓“复杂问题”的概念是相对的,一旦人们采用的先进组织形式、开发方法和工具提高了软件开发效率和能力,新的、更大的、更复杂的问题又会摆在人们面前。
软件危机的解决途径
1968年,计算机科学家在德国第一次讨论软件危机问题,并正式提出“软件工程”一词,从此一门新兴的工程学科为研究和克服软件危机应运而生。作为一个新兴的工程学科,软件工程学主要研究软件产生的客观规律性,建立与系统化软件生产有关的概念、原则、方法、技术和工具,指导和支持软件系统的生产活动,以期达到降低软件生产成本、改进软件产品质量、提高软件生产率水平的目标。软件工程学从硬件工程和其他人类工程中吸收了许多成功的经验,明确提出了软件生命周期的模型,发展了许多软件开发与维护阶段适用的技术和方法,并应用于软件工程实践,取得了良好的效果。
在软件开发过程中,人们开始研制和使用软件工具,用于辅助进行软件项目管理与技术生产,人们还将软件生命周期各阶段使用的软件工具有机地集合成一个整体,形成能够连续支持软件开发与维护全过程的集成化软件支撑环境,以期从管理和技术两方面解决软件危机问题。
软件工程
软件工程的定义
软件工程是一门指导计算机软件开发和维护的工程学科,是一门边缘学科,涉及计算机科学、工程科学、管理科学、数学等多学科,研究的范围广,主要研究如何应用软件开发的科学理论和工程技术来指导大型软件系统的开发。
虽然对软件工程有着众多的定义,但是其基本思想都是强调在软件开发过程中应用工程化的重要性。
例如,1983年,IEEE(电气和电子工程师协会)所下的定义是:软件工程是开发、运行、维护和修复软件的系统方法。1990年,IEEE又将定义更改为:对软件开发、运行、维护的系统化的、有规范的、可定量的方法之应用,即是对软件的工程化应用。
2004年,IEEE/ACM联合发布的CCSE2004报告强调了对软件工程的新定义,即软件工程是“以系统的、科学的、定量的途径,把工程应用于软件的开发、运行和维护;同时,开展对上述过程中各种方法和途径的研究”。这也是目前一种比较广泛认可的定义。
从软件工程的定义可见,软件工程是一门指导软件系统开发的工程学科,它以计算机理论及其他相关学科的理论为指导,采用工程化的概念、原理、技术和方法进行软件的开发和维护,把实践证明的、科学的管理措施与最先进的技术方法结合起来。软件工程研究的目标是“以较少的投资获取高质量的软件”。它包括3个要素:方法、工具和过程。
(1)软件工程方法为软件开发提供了“如何做”的技术,是指导开发软件的某种标准规范。它包括多方面的任务,如项目计划与估算、软件系统需求分析、数据结构、系统总体结构的设计、算法的设计、编码、测试及维护等。软件工程方法常采用某种特殊的语言或图形的表达方法及一套质量保证标准。
(2)软件工具是指软件开发、维护和分析中使用的程序系统,为软件工程方法提供自动的或半自动的软件支撑环境。
(3)软件工程的过程则是将软件工程的方法和工具综合起来,以达到合理、及时地进行计算机软件开发的目的。过程定义了方法使用的顺序、要求交付的文档资料、为保证质量和协调变化所需的管理及软件开发各个阶段完成的“里程碑”。
软件工程的背景
为了克服软件危机,1968年10月,NATO召开的计算机科学会议上,Fritz Bauer首次提出“软件工程”的概念,企图将工程化方法应用于软件开发上。
许多计算机和软件科学家尝试把其他工程领域中行之有效的工程学知识运用到软件开发工作中来。经过不断实践和总结,最后得出一个结论:按工程化的原则和方法组织软件开发工作是有效的,是摆脱软件危机的一条主要道路。
虽然软件工程的概念提出已有40多年,但到目前为止,软件工程概念的定义并没有得到认可。在NATO会议上,Fritz Bauer对软件工程的定义是:“为了经济地获得可靠的和能在实际机器上高效运行的软件,而建立和使用的健全的工作原则。”除了这个定义,还有几种比较有代表性的定义。
B.W.Boehm给出的定义是:“运用现代科学技术知识来设计并构造计算机程序及为开发、运行和维护这些程序所必需的相关文件资料。”此处,“设计”一词广义上应理解为包括软件的需求分析和对软件进行修改时所进行的再设计活动。
1983年,IEEE给出的定义是:“软件工程是开发、运行、维护和修复软件的系统方法。”其中,“软件”的定义为计算机程序、方法、规则、相关的文档资料以及在计算机上运行时所必需的数据。
后来尽管又有一些人提出了许多更为完善的定义,但主要思想都是强调在软件开发过程中应用工程化原则的重要性。
我国2006年的国家标准GB/T 11457—2006《软件工程术语》中对软件工程的定义为:“应用计算机科学理论和技术以及工程管理原则和方法,按预算和进度,实现满足用户需求的软件产品的定义、开发、发布和维护的工程或进行研究的学科。”
概括地讲,软件工程是指导软件开发和维护的工程性学科,它以计算机科学理论和其他相关学科的理论为指导,采用工程化的概念、原理、技术和方法进行软件的开发和维护,把经过时间考验而证明是正确的管理技术和当前能够得到的最好的技术方法结合起来,以较少的代价获得高质量的软件并维护它。
软件工程的基本原理
著名软件工程专家B.W.Boehm于1983年综合了软件工程专家、学者们的意见,并总结了开发软件的经验,提出了软件工程的7条基本原理。这7条原理被认为是确保软件产品质量和开发效率的原理的最小集合,又是相互独立、缺一不可、相当完备的最小集合。
下面简要介绍软件工程的7条基本原理。
1)用分阶段的生命周期计划严格管理
在软件开发与维护的漫长的生命周期中,需要完成许多性质各异的工作。这条基本原理意味着,应该把软件生命周期划分为若干个阶段,并相应地提出切实可行的计划,然后严格按照计划对软件的开发和维护工作进行管理。Boehm认为,在软件的整个生命周期中,应该制订并严格地执行6类计划,它们是项目概要计划、里程碑计划、项目控制计划、产品控制计划、验证计划及运行维护计划。
不同层次的管理人员都必须严格按照计划各尽其责地管理软件开发与维护工作,绝不能受客户和上级人员的影响而擅自背离预定计划。
2)坚持进行阶段评审
软件的质量保证工作不能等到编码工作结束之后再进行。这样说至少有两个理由:第一,大部分错误是在编码之前造成的,有统计表明,设计错误占软件错误的63%,编码错误仅占37%;第二,错误发现与改正的时间越晚,所付出的代价就越高。因此,每个阶段都要对软件的质量进行严格的评审,以便尽早地发现在软件开发工作中所犯的错误,这是一条必须遵循的重要准则。
3)实行严格的产品控制
在软件开发过程中不应随意改变需求,因为改变一项需求往往需要付出较高的代价。但是,在软件开发过程中改变需求又是不可避免的,由于外部环境的变化,相应地改变用户需求是一种客观需要,显然不能硬性禁止客户提出改变需求的要求,而只能依靠科学的产品控制技术来顺应这种要求。
4)采用现代程序设计技术
从提出软件的概念开始,人们一直把主要精力用于研究各种新的程序设计技术。20世纪60年代末提出的结构程序设计技术,已经成为公认的、先进的程序设计技术,后来又进一步发展出各种结构分析(SA)与结构设计(SD)技术。实践表明,采用先进的技术既可以提高软件开发的效率,又可以提高软件维护的效率。
5)结果应能清楚地审查
软件产品不同于一般的物理产品,它是看不见摸不着的逻辑产品。软件开发人员(或开发小组)的工作进展情况可见性差,难以准确度量,从而使得软件产品的开发过程比一般产品的开发过程更难以评价和管理。为了提高软件产品开发过程的可见性,更好地进行管理,应该根据软件开发项目的总目标及完成期限,规定开发组织的责任和产品标准,从而使所得到的结果能够清楚地审查。
6)开发小组的人员应该少而精
这条基本原理的含义是,软件开发小组组成人员的素质要好,而人数不宜过多,开发小组素质和数量是影响软件产品质量和开发效率的重要因素。素质高的人员的开发效率比素质低的人员的开发效率可能高几倍至几十倍,而且素质高的人员所开发的软件中的错误数量明显少于素质低的人员所开发的软件中的错误数量。此外,随着开发小组人员数目的增加,交流情况、讨论问题而造成的通信开销也急剧增加。当开发小组人数为N时,可能的通信路径有N(N-1)/2条,可见随着人数N的增大,通信开销将急剧增加。因此,人员少而精的开发小组是软件工程的一项基本原则。
7)承认不断改进软件工程实践的必要性
遵循上述1)~6)条基本原理,就能够按照当代软件工程基本原理实现软件的工程化生产,但是仅有上述1)~6)条原理并不能保证软件开发与维护的过程能赶上时代前进的步伐,也不能保证能跟上技术的不断进步。因此,Boehm提出应把承认不断改进软件工程实践的必要性作为软件工程的第7条基本原理。按照这条原理,不仅要积极主动地采纳新的软件技术,而且要不断地总结经验,例如,收集进度和资源耗费数据,收集出错类型和问题报告数据,等等。这些数据不仅可以用于评价新的软件技术的效果,而且可以用于指明必须着重开发的软件工具和应该优先研究的技术。
软件工程工具
1.需求分析工具
需求分析工具的功能与所采用的系统开发方法密不可分。按所采用的系统开发方法,需求分析工具可分为结构化图形工具箱、面向对象模型化工具及分析工具。
1)结构化图形工具箱
这类工具需要通过数据流图(DFD)进行功能分析、包括DFD图形工具、实体-关系(ER)图形工具、Jackson图形工具、Warnier/Orr图形工具。
2)面向对象模型化工具及分析工具
这类工具需要通过对象建立构造系统的抽象模型,一般包括图形工具、对象浏览器及类库管理系统。
(1)图形工具。UML已经成为业界标准,支持面向对象的建模。因此,分析工具应该支持UML建模,如建立用例图、类图、顺序图、协作图、状态图、构件图及配置图等。
(2)对象浏览器。对象浏览器是一个允许开发者驾驭类继承的多窗口程序,它通常通过直接存取类的源代码来进行编辑。
(3)类库管理系统。类库管理系统的一个重要作用是增加可重用类的数量。随着类数量的增加,需要一些查找类的方法,类库管理系统就是一种允许选择和编辑类,并按照所需标准对类进行描述的工具。
有代表性的商品化工具如下。
①Rational Rose,由Rational Corporation开发。
②PowerDesigner,由Sybase设计。
③Visio,由Microsoft开发。
④ArgoUML,开源工具。
⑤Control Center,由TogetherSoft开发。
⑥Enterprise Architect,由Sparx Systems开发。
⑦Object Technology Workbench(OTW),由OTW Software开发。
⑧System Architect,由Popkin Software开发。
⑨UML Studio,由Pragsoft Corporation开发。
⑩Visual UML,由Visual Ob ject Modelers开发。
2.设计工具
设计阶段分为概要设计和详细设计。概要设计的主要任务是进行系统总体结构设计;详细设计的主要任务是设计软件算法和内部实现细节。
对于概要设计活动和详细设计活动,设计工具通常可分为概要设计工具和详细设计工具等两类。
1)概要设计工具
概要设计工具用于辅佐设计人员设计目标软件的体系结构、控制结构和数据结构。软件的体系结构通常用模块结构图来描述,它指明软件系统的模块组成及其调用关系、模块的接口定义等。模块的数据结构通常用实体-关系图来描述。
有代表性的商品化工具如下。
(1)Rational Rose,由Rational Corporation开发,是基于UML的设计工具,它支持体系结构设计中的所有方面。
(2)Adalon,由Synthis公司开发,是用于设计和构建专门基于Web构件体系结构的特定设计工具。
(3)Objectif,由Micro TOOL GmbH开发,是一个基于UML的设计工具,它用于设计符合基于构建的软件工程的各种体系结构(如Coldfusion、J2EE和Fusebox等)。
2)详细设计工具
详细设计工具用于辅佐设计人员设计模块的算法和内部实现细节。详细设计规范的图形描述方法通常有输入-处理-输出(Input-Process-Output,PO)图、问题分析图(Problem Analysis Diagram,PAD)、盒图(也称为N-S图)、流程图(Flow Chart,FC)等。详细设计规范的语言描述方法通常有程序设计语言(Program Design Language,PDL)、结构化语言等,其表格描述方法通常有判定表和判定树。
3.编码工具和排错工具
辅助程序员进行编码活动的工具有编码工具和排错工具。编码工具辅助程序员用某种程序设计语言编写源程序,并对源程序进行翻译,最终转换成可执行的代码。因此,编码工具通常与编码所使用的程序设计语言密切相关。排错工具用于辅助程序员寻找程序中错误的性质和原因,并确定其出错的位置。
由于源程序一般以正文的形式出现,因此必须用编辑器将它输入并进行浏览、编辑和修改。又由于源程序的编写往往不可能一次成功,需要不断寻找其中的错误,并加以纠正。因此,编码工具和排错工具是编程活动中的重要辅助工具,也是最早出现的软件工具。
4.测试工具
测试工具分为程序单元测试工具、组装测试工具和系统测试工具。
1)程序单元测试工具
早期的程序单元测试工具有静态分析工具、动态分析工具和自动测试支持工具三类。
目前最流行的单元测试工具是xUnit系列框架,根据语言不同而分为JUnit(Java)、CppUnit(C++)、DUnit(Delphi)、NUnit(.NET)、PhpUnit(PHP)等。该测试框架的第一个和最杰出的应用就是有Erich Gamma(《设计模式》的作者)和Kent Beck(XP(极限编程)的创始人)提供的开放源代码的JUnit。
2)组装测试工具
组装测试也称为集成测试或联合测试。在单元测试的基础上,将所有模块按照设计要求组装成子系统或系统,再进行组装测试。实践表明,一些模块虽然能够单独工作,但并不能保证连接起来也能正常工作。程序在某些局部反映不出来的问题,很可能在全局上暴露出来,影响功能的实现。
有代表性的组装测试工具如下。
(1)WinRunner,由Mercury Interactive公司开发,是一种企业级的功能测试工具,用于检测应用程序是否能够达到预期的功能及正常运行。
(2)IBM Rational Robot,是业界最顶尖的功能测试工具。它集成在测试人员的桌面IBM Rational TestManager上,测试人员可以计划、组织、执行和报告所有测试活动。
(3)Borland SilkTest 2006,属于软件功能测试工具,是Borland公司提出的软件质量管理解决方案的套件之一。这个工具采用精灵设定与自动化执行测试,无论是程序设计新手还是资深的专家都能快速建立功能测试,并分析功能错误。
(4)TestDirector,是业界第一个基于Web的测试管理系统,它可以在公司内部或外部进行全球范围内的测试管理。通过在一个整体的应用系统中集成测试管理的各个部分,包括需求管理、测试计划、测试执行及错误跟踪等功能,极大地加速了测试过程。
3)系统测试工具
系统测试是对整个基于计算机的系统进行一系列不同的考验,它通常是耗费测试资源最多的测试。除了功能测试之外,负载测试、性能测试、可靠性测试和其他一些测试一般也都是在系统测试期间进行的。
有代表性的系统测试工具如下。
(1)LoadRunner,是一种预测系统行为和性能的负载测试工具。通过模拟上千万用户,实施并发布负载及实时性能监测的方式来确认和查找问题,LoadRunner能够对整个企业架构进行测试。
(2)OTF(Object Testing Framework),由MCG软件公司开发,为Smalltalk对象的测试提供管理框架。
(3)QADirector,由Compuware Corp.开发,为管理测试过程的各个阶段提供简单的控制。
(4)TestWorks,由Software Research Inc.开发,包含一个完整的测试工具集,包括测试管理与测试报告。
软件过程
软件生存周期
软件生存周期(SDLC,又称为软件生命周期)是指软件的产生直到报废的生命周期。目前划分软件生存周期的方法有许多种,软件规模、种类、开发方式、开发环境及开发时使用的方法论等都会影响软件生存周期。
在划分软件生存周期的阶段时应遵循一条基本原则:各阶段的任务彼此间相互独立,同一阶段的各项任务的性质尽可能相同,从而降低每个阶段任务的复杂程度,简化不同阶段之间的联系。
一般来说,软件生存周期由软件定义、软件开发和软件维护三个时期组成,每个时期又划分为若干个阶段。以时间分程可将软件生存周期划分为计划、需求分析、总体设计、详细设计、程序编码、软件测试及运行维护7个阶段。每个阶段有明确的任务,这样使规模大、结构复杂和管理复杂的软件开发变得容易控制和管理。
1.计划
此阶段由软件开发方与需求方共同讨论,软件计划包括问题定义和可行性研究,主要任务是确定要开发软件的总目标,给出它的功能、性能、可靠性及接口等方面的设想。此阶段主要研究完成该软件任务的可行性,探讨解决问题的方案,并对可供使用的资源、成本、可取得的效益和开发进度作出估计,制定完成开发任务的实施计划。
2.需求分析
需求分析的主要任务是确定目标系统必须具备哪些功能。软件设计员在该阶段必须与用户密切配合,充分交流信息,以得出经过用户确认的系统逻辑模型,并写出软件需求说明书或功能说明书及初步的系统用户手册,提交管理机构评审。
3.总体设计
在总体设计阶段,设计人员要把已确定了的各项需求转换成一个相应的体系结构,结构中每个组成部分都是意义明确的模块,每个模块都和某些需求相对应。另外,设计人员还应该使用系统流程图或其他工具描述实际系统的可能解决方案,并估算每种方案的成本和效益,还应该在充分权衡各种方案利弊的基础上,给用户推荐一个最佳方案。如果用户接受设计人员的推荐方案,则可以开始着手详细设计。
4.详细设计
在总体设计阶段,设计人员用抽象概括的方式提出系统的体系结构和功能模块。而详细设计阶段的任务就是将实现系统的步骤具体化。这种具体化还不是编写代码,而是对系统的每个模块要完成的工作进行具体的描述,并确定输入、输出,以便在编码之前可以评价软件质量,并为编码打下基础。
通常用HIPO图(层次图加输入/处理/输出图)或PDL语言(过程设计语言)描述详细设计的结果。
5.程序编码
这个阶段程序员的关键任务是根据目标系统的性质和实际环境,选取一种高级程序设计语言,将详细设计的结果翻译成用选定的语言书写的程序,并仔细地测试每个模块的功能。
6.软件测试
软件测试阶段的任务是通过各种类型的测试,使软件符合预定的要求。其主要方式是在设计测试用例的基础上检验软件的各个组成部分。首先进行单元测试以发现模块在功能和结构方面的问题,其次将已测试过的模块组装起来进行集成测试,再次进行验收测试,验收测试时按照需求规格说明的规定,由用户对目标系统进行验收。
必要时还可以通过现场测试或平行运行等方法对目标系统进行进一步的测试。通过对软件测试结果的分析可以预测软件的可靠性;反之,根据对软件可靠性的要求,也可以决定测试和调试过程的结束时间。
用证书的文档资料将测试计划、详细测试方案及实际测试结果保存下来,作为软件配置的一个组成部分。
7.运行维护
运行维护的主要任务是进行系统的日常运行管理,根据一定的规格对系统进行必要的修改,评价系统的运行效率、工作质量和经济效益,对运行费用和效果进行监理审计。软件交付用户后,便进入运行阶段。在运行阶段中,可能由于多方面的原因,需要对它进行修改。例如,为适应外部环境的变化和用户要求而添加新的功能;或是随着制作工艺的提高,将原来的工作流程做相应的改动;等等。运行维护时在软件生存周期的各个阶段去调整现有系统,而不是开发一个新的项目。
软件过程模型
所谓软件过程模型就是一种开发策略,这种策略针对软件工程的各个阶段提供了一套规范,使工程的进展能达到预期的目的。对一个软件的开发无论其大小,我们都需要选择一种合适的软件过程模型,这种选择基于项目和应用的性质、采用的方法、需要的控制,以及要交付的产品的特点。
目前,常见的软件开发模型大致可分为如下3类:
(1)以需求完全确定为前提的开发模型,如瀑布模型;
(2)在软件开发初始阶段只能提供基本需求时采用的渐进式开发模型,如原型模型、螺旋模型、协同模型等;
(3)以形式化开发方法为基础的专用过程模型。
瀑布模型
瀑布模型即生存模型,其核心思想是按工序将问题简化,将功能设计与设计分离。瀑布模型将软件生存周期划分为计划、需求分析、设计、编码、测试和运行维护等6个基本活动,并且规定了它们自上而下、相互衔接的固定次序,如同瀑布流水,逐级下落,如下图所示。
按照传统的瀑布模型来开发软件,有如下几个特点。
1.阶段间具有顺序性和依赖性
(1)必须等前一个阶段工作完成后,才能开始后一阶段的工作;
(2)前一阶段的输出文档就是后一阶段的输入文档,因此只有前一阶段的输出文档正确,后一阶段的工作才能获得正确结果。
2.推迟实现的观点
瀑布模型在编码之前设置了计划、需求分析、设计3个阶段。需求分析与设计阶段的基本任务规定,在这两个阶段主要考虑目标系统的逻辑模型,不涉及软件的物理实现。清楚地区分逻辑设计与物理设计,尽可能推迟程序的物理实现,是瀑布模型的一条重要指导思想,可以避免项目中不必要的大量返工。
3.质量保证的观点
1)瀑布模型的每个阶段都应坚持两个重要做法
(1)每个阶段都必须完成规定的文档,没有完成合格的文档就是没有完成该阶段的任务。
(2)每个阶段结束前都要对所完成的文档进行评审,以便尽早的发现和改正问题。
2)瀑布模型的缺点
以上是隐含在软件生存周期各个阶段后面的指导思想,是比具体任务更重要的内容。但是,这种模型的线性过程太理想化,已不再适合现代的软件开发模式,几乎被业界抛弃,其主要问题在于以下几个方面:
(1)各个阶段的划分完全固定,阶段之间产生大量的文档,极大地增加了工作量;
(2)由于开发模型是线性的,用户只有等到整个过程的末期才能见到开发成果,从而增加了开发的风险;
(3)早期的错误可能要等到开发后期的测试阶段才能发现,进而带来严重的后果。
我们应该认识到,“线性”是人们最容易掌握并能熟练应用的思维方法。当人们碰到一个复杂的“非线性”问题时,总是千方百计地将其分解或转化为一系列简单的线性问题,然后逐个解决。一个软件系统的整体可能是复杂的,而单个子程序总是简单的,可以用线性的方式来实现。线性是一种简洁,简洁就是美。当我们领会了线性的精神,就不要再呆板地套用线性模型的外表,而应该灵活使用它。例如,增量模型实质上就是分段的线性模型,螺旋模型则是连接的弯曲了的线性模型,在其他模型中也能够找到线性模型的影子。
增量模型
增量模型融合了线性顺序模型的基本成分和原型模型的迭代特征。这种模型采用随着日程时间的进展而交错的线性序列。每一个线性序列产生软件的一个可发布的“增量”。当使用增量模型时,第一个增量往往是核心的产品,也就是说第一个增量实现了基本的需求,但很多补充的特征还没有发布。客户对每一个增量的使用和评估,都作为下一个增量发布的新特征和功能。在每一个增量发布后不断重复这个过程,直到产生了最终完善的产品为止。增量模型强调每一个增量均发布一个可操作的产品。采用增量模型的软件过程如下图所示。
增量模型像原型模型一样具有迭代的特征。但与原型模型不一样,增量模型强调每一个增量均发布一个可操作产品。早期的增量是最终产品的“可拆卸”版本,但它们确实给用户提供了服务的功能,并且给用户提供了评估的平台。
增量开发是很有用的,尤其是当配备的人员不能在为该项目设定的市场期限之前实现一个完全的版本时,早期的增量可以由较少的人员实现。如果核心产品很受欢迎,那么可以增加新的人手实现下一个增量。此外,增量能够有计划地管理技术风险,例如,系统的一个重要部分需要使用正在开发的并且发布时间尚未确定的新硬件,有可能计划在早期的增量中避免使用该硬件,这样就可以首先发布部分功能给用户,以免过分地拖延系统的问世时间。
演化过程模型
演化过程模型是一种全局的软件生存周期模型,属于迭代开发的模型。该模型的基本思想为:根据用户的基本需求,通过快速分析构造出该软件的原型,然后根据用户在使用原型过程中提出的意见和建议对原型进行改进,获得原型的新版本。重复这一过程,最终可以得到令用户满意的软件产品。
该模型可以表示为第一次迭代(需求→设计→实现→测试→集成)→反馈→第二次迭代→反馈→……
本小节主要介绍二种演化过程模型:原型模型和螺旋模型。
1.原型模型
原型就是可以逐步改进成运行系统的模型。开发者在初步了解用户需求的基础上,凭借自己对用户需求的理解,通过强有力的软件环境支持,利用软件快速开发工具,构成﹑设计和开发一个实在的软件初始模型(原型,一个可以实现的软件应用模型)。利用原型模型进行软件开发的流程如下图所示。相对瀑布模型,原型模型更符合人们开发软件的习惯,是目前较流行的一种实用软件生存模型。
1)原型模型的优点
(1)开发人员和用户在“原型”上达成一致。这样一来,可以减少设计中的错误和开发中的风险,也减少了对用户培训的时间,从而提高了系统的实用性﹑正确性及用户满意度。
(2)原型模型采用逐步求精的方法完善原型,使得原型能够快速开发,避免了像瀑布模型一样冗长的开发过程中难以对用户的反馈作出快速响应。
(3)原型模型通过“样品”不断改进,降低了成本。
(4)原型模型的应用使人们对需求有了渐进的认识,从而使软件开发更有针对性。另外,原型模型的应用充分利用了最新的软件工具,使软件开发效率大为提高。
2)原型模型的缺点
虽然用户和开发者都非常喜欢原型模型,因为它使用户能够感受到实际的软件系统,开发人员能很快建造出一些内容。但该模型仍然存在着一些问题,其原因如下。
(1)用户看到的是一个可运行的软件版本,但不知道这个原型是临时搭建起来的,也不知道软件开发者为了使原型尽快运行,并没有考虑软件的整体质量或以后的可维护性问题。当被告知该产品必须重建才能使其达到高质量时,用户往往叫苦连天。
(2)开发人员常常需要在实现上采取折中的办法,以使原型能够尽快工作。开发人员很可能采用一个不合适的操作系统或程序设计语言,仅仅因为它通用或有名,也可能使用一个效率低的算法,仅仅为了实现演示功能。经过一段时间之后,开发人员可能对这些选择已经习以为常了,忘记了它们不合适的原因。于是,这些不理想的选择就成了软件的组成部分。
虽然会出现问题,但原型模型仍是软件工程的一个有效典范。使用原型模型开发系统时,用户和开发者必须达成一致:原型被建造仅仅是用户用于定义需求,不宜利用它来作为最终产品,之后被部分或全部抛弃,最终的软件是要充分考虑了质量和可维护性等方面之后才被开发。
2.螺旋模型
对于复杂的大型软件,开发一个原型往往达不到要求。螺旋模型将瀑布模型和原型模型结合起来,这不仅体现了两个模型的优点,而且还增加了两个模型都忽略了的风险分析,弥补了两者的不足。
1)螺旋模型的结构
螺旋模型的结构如下图所示,它由4部分组成:制订计划、风险分析、实施开发和客户评估。在笛卡儿坐标的4个象限中分别表达了4个方面的活动。
沿螺旋线自内向外每旋转一圈便开发出一个更为完善的新的软件版本。例如,第一圈时,在制订计划阶段,确定了初步的目标、方案和限制条件以后,转入风险分析阶段,对项目的风险进行识别和分析。如果风险分析表明需求有不确定性,但是可以承受风险,那么在实施开发阶段,所建的原型会帮助开发人员和用户对需求做进一步的修改。软件开发完成后,客户会对工程成果做出评价,给出修正建议。在此基础上进入第二圈螺旋,再次进行制订计划、风险分析、实施开发和客户评估等工作。假如风险过大,开发者和用户无法承受,那么有可能终止项目。多数情况下,软件开发过程是沿螺旋线的路径连续进行的,自内向外,逐步延伸,最终总能得到一个用户满意的软件版本。
2)螺旋模型的优点
螺旋模型的优点在于:设计上的灵活性,可以在项目的各个阶段进行变更;以小的分段来构建大型系统,使成本计算变得简单容易;客户始终参与每个阶段的开发,保证了项目不偏离正确方向及项目的可控性;随着项目推进,客户始终掌握项目的最新信息,从而能够与管理层有效地交互;客户认可这种公司内部的开发方式带来的、良好的沟通和高质量的产品。
3)螺旋模型的缺点
螺旋模型的缺点在于:很难让用户确信这种演化方法的结果是可以控制的;建设周期长,而软件技术发展比较快,所以经常出现软件开发完毕后,与当前的技术水平有较大的差距,无法满足当前用户的需求。
螺旋模型不仅保留了瀑布模型中系统地、按阶段逐步地进行软件开发和“边开发、边评审”的风格,而且还引入了风险分析,并把制作原型作为风险分析的主要措施。用户始终关心、参与软件开发,并对阶段性的软件产品提出评审意见,这对保证软件产品的质量是十分有利的。但是,螺旋模型的使用需要具有相当丰富的风险评估经验和专门知识,而且开发费用昂贵,所以只适合大型软件的开发。
2、软件分析
可行性研究
可行性研究的任务
我们知道并不是所有问题都有简单明显的解决办法的,事实上,许多问题不可能在预定的系统规模之内解决。如果问题没有可行的解,那么花费在这项开发工程上的时间、资源、人力和费用都是无谓的浪费。
可行性研究的目的就是用最小的代价在尽可能短的时间内确定问题是否能够解决。必须记住,可行性研究的目的不是解决问题,而是确定问题是否值得去解。怎样达到这个目的呢?当然不能靠主观猜想,而只能靠客观分析。必须分析几种主要的可能解法的利弊,从而判断原定的系统目标和规模是否现实,系统完成后所能带来的效益是否大到值得投资开发这个系统的程度。因此,可行性研究实质上是要进行一次大大压缩简化了的系统分析和设计的过程,也就是在较高层次上以较抽象的方式进行系统分析和设计的过程。
首先需要进一步分析和澄清问题定义。在问题定义阶段要初步确定规模和目标,如果是正确的就进一步加以肯定,如果是错误的就应该及时改正;如果对目标系统有任何约束和限制,就必须清楚地把它们列举出来。
在澄清了问题定义之后,分析员应该导出系统的逻辑模型,然后从系统逻辑模型出发,探索若干种可供选择的主要解法(系统实现方案)。对每种解法都应该仔细研究它的可行性,一般说来,至少应该从下述三方面研究每种解法的可行性。
(1)技术可行性,使用现有的技术能实现这个系统吗?
(2)经济可行性,这个系统的经济效益能超过它的开发成本吗?
(3)操作可行性,系统的操作方式在这个用户组织内行得通吗?
分析员应该为每个可行的解法制定一个粗略的实现进度。
当然,可行性研究最根本的任务是对以后的行动方针提出建议。如果问题没有可行的解,分析员应该建议停止这项开发工程,以避免时间、资源、人力和费用的浪费;如果问题值得求解,分析员应该推荐一个较好的解决方案,并且为工程制订一个初步的计划。
可行性研究需要的时间长短取决于工程的规模,一般说来,可行性研究的成本只是预期工程总成本的5%~10%。
可行性研究的步骤
典型的可行性研究过程有下述一些步骤。
1.复查系统规模和目标
分析员访问关键人员,仔细阅读和分析有关的材料,以便对问题定义阶段书写的关于规模和目标的报告书进一步复查确认,改正含糊或不确切的叙述,清晰地描述对目标系统的一切限制和约束。这个步骤的工作,实质上是为了确保分析员正在解决的问题确实是要求解决的问题。
2.研究目前正在使用的系统
现有的系统是信息的重要来源。显然,如果目前有一个系统正被人使用,那么这个系统必定能完成某些有用的工作,因此,新的目标系统必须也能完成它的基本功能;另一方面,如果现有的系统是完美无缺的,用户自然不会提出开发新系统的要求,因此,现有的系统必然有某些缺点,新系统必须能解决旧系统中存在的问题。此外,运行使用旧系统所需的费用是一个重要的经济指标,如果新系统不能增加收入或减少使用费用,那么从经济角度来看新系统就不如旧系统。
应该仔细阅读分析现有系统的文档资料和使用手册,也要实地考察现有的系统。应该注意了解这个系统可以做什么,为什么这样做,还要了解使用这个系统的代价。在了解上述这些信息时必须访问有关的人员。在调查访问时分析员和用户之间的关系有点类似于医生和病人的关系,用户叙述的往往是“症状”而不是实际问题,分析员必须分析所得到的信息。
常见的错误做法是花费过多时间去分析现有的系统。这个步骤的目的是了解现有系统能做什么,而不是了解它怎样做这些工作。分析员应该画出现有系统的高层系统流程图,并请有关人员检验他对现有系统的认识是否正确。千万不要花费太多时间去了解和描绘现有系统的实现细节,除非是为了阐明一个特别关键的算法,否则不需要根据程序代码画出程序流程图。
没有一个系统是在“真空”中运行的,绝大多数系统都和其他系统有联系。应该注意了解并记录现有系统和其他系统之间的接口情况,这是设计新系统时的重要约束条件。
3.导出新系统的高层逻辑模型
优秀的设计过程通常总是从现有的物理系统出发,导出现有系统的逻辑模型,再参考现有系统的逻辑模型,设想目标系统的逻辑模型,最后根据目标系统的逻辑模型建造新的物理系统。
通过前一步的工作,分析员对目标系统应该具有的基本功能和所受的约束已有一定了解,能够使用数据流图,描绘数据在系统中流动和处理的情况,从而概括地表达出其对新系统的设想。通常为了把新系统描绘得更加清晰、准确,还应该有一个初步的数据字典来定义系统中使用的数据。数据流图和数据字典共同定义了新系统的逻辑模型,以后可以从这个逻辑模型出发设计新系统。
4.重新定义问题
新系统的逻辑模型实质上表达了分析员对新系统必须做什么的看法。用户是否也有同样的看法呢?分析员应该和用户一起再次复查问题定义、工程规模和目标,这次复查应该把数据流图和数据字典作为讨论的基础。如果分析员对问题有误解或用户曾经遗漏了某些要求,那么现在是发现和改正这些错误的时候了。
可行性研究的前四个步骤实质上构成一个循环。分析员定义问题,分析这个问题,导出一个试探性的解;在此基础上再次定义问题,再一次分析这个问题,修改这个解;继续这个循环过程,直到提出的逻辑模型完全符合系统目标为止。
5.导出和评价供选择的解法
分析员应该从其建议的系统逻辑模型出发,导出若干个较高层次的(较抽象的)物理解法进行比较和选择。导出供选择解法的最简单的途径,是从技术角度出发考虑解决问题的不同方案。分析员可以确定几组不同的自动化边界,然后针对每一组边界考虑如何实现要求的系统;还可以使用组合的方法导出若干种可能的物理系统,例如,在每一类计算机上可能有几种不同类型的系统,如微处理机上的批处理系统、微处理机上的交互式系统、小型机上的批处理系统等,此外还应该把现有系统和人工系统作为两种可能的方案一起考虑。
在从技术角度提出了一些可能的物理系统之后,应该根据技术可行性初步排除一些不现实的系统。例如,如果要求系统的响应时间不超过几秒钟,显然应该排除任何批处理方案。在把技术上行不通的解法去掉之后,就剩下一组技术上可行的方案。
其次可以考虑操作方面的可行性。分析员应该根据使用部门处理事务的原则和习惯检查技术上可行的方案,去掉其中从操作方式或操作过程的角度看用户不能接受的方案。
接下来应该考虑经济方面的可行性。分析员应该估计余下的每种可能的系统开发成本和运行费用,并且估计相对于现有的系统而言该系统可以节省的费用或可以增加的收入。在这些估计数字的基础上,对每个可能的系统进行成本、效益分析。
一般说来,只有投资预计能带来利润的系统才值得进一步考虑。
最后为每个在技术、操作和经济等方面都可行的系统制定实现进度表,这个进度表不需要(也不可能)制定得很详细,通常只需估计生存周期每个阶段的工作量。
6.推荐行动方针
根据可行性研究结果做出的一个关键性决策是:是否继续进行这项开发工程。分析员必须清楚地表明他对这个关键性决策的建议。如果分析员认为值得继续进行这项开发工程,那么就应该选择一种最好的解法,并且说明选择这个解决方案的理由。通常,使用部门的负责人主要根据经济上是否划算决定是否投资一项开发工程,因此对于所推荐的系统,分析员必须进行比较仔细的成本、效益分析。
7.草拟开发计划
分析员应该进一步为推荐的系统草拟一份开发计划,除了工程进度表之外还应该估计对各种开发人员(系统分析员、程序员、资料员等)和各种资源(计算机硬件、软件工具等)的需要情况,应该指明使用时间及使用时间的长短。此外,还应该估计系统生存周期中每个阶段的成本。最后,应该给出下一个阶段(需求分析)的详细进度表和成本估计。
8.书写文档,提交审查
应该把上述可行性研究中各个步骤的结果写成清晰的文档,请用户和使用部门的负责人仔细审查,以决定是否继续这项工程及是否接受分析员推荐的方案。
可行性研究报告
可行性分析的结果要用可行性分析报告的形式编写出来,内容包括引言、系统开发的必要性和意义、现行系统调查与分析、新系统的几种方案介绍、新旧系统方案比较、结论。
可行性分析结论应明确指出以下内容:系统具备立即开发的可行性,可进入软件开发的下一个阶段;若可行性分析结果完全不可行,则软件开发工作必须放弃;不具备某些条件,可以创造条件,增加资源或改变新系统的目标后,再重新进行可行性论证。
以库存管理系统为例子,其可行性报告的结构如下。
(1)摘要。
系统名称:存管理系统。
目标:创建一个高效、准确、操作方便,并且具有查询、更新及统计功能的信息系统。
功能:系统管理、出入库处理、库存报警、库存查询、库存计划。
(2)背景。
系统开发的组织单位:××软件公司。
系统的服务对象:系统管理员、计划员、采购员、库管员、车间、供应商、其他相关人员本系统和其他系统或机构的关系和联系。
(3)参考和引用的资料(略)。
(4)专门术语和缩写词(略)。
(5)系统开发的必要性和意义(略)。
(6)现行系统调查与分析(略)。
(7)组织机构。
供应部:主要负责计划、采购、库存管理和供应。
车间:物资消耗部门。
(8)业务流程。
(9)费用(略)。
(10)计算机应用(略)。
(11)现行系统存在的问题。
(12)新系统的几种方案介绍(略)。
(13)新系统方案比较(略)。
(14)结论。
系统具备立即开发的可行性,可进入软件开发的下一个阶段。
系统流程图
系统流程图又叫事务流程图,是在系统事务处理应用进行系统分析时常用的一种描述方法,它描述了系统事务处理中从数据输入开始到获得输出为止,各个处理工序的逻辑过程。
(1)系统流程图的符号
系统流程图是描绘物理系统的传统工具。它的基本思想是用图形符号以黑盒子形式描绘系统里面的每个部件(程序、文件、数据库、表格、人工过程等)。
值得注意的是,系统流程图表达的是部件的信息流程,而不是表示对信息进行加工处理的控制过程。
系统流程图不仅仅是计算机系统事物处理流程的表示工具,而是任何系统事物处理流程的表示工具。
系统流程图的基本符号能够以概括的方式描述一个物理系统,如下图所示。
除此之外,当需要更具体地描绘一个物理系统时,可能还需要下图中列出的其他符号。
(2)系统流程图的基本处理工序
1.变换
把输入单据变换成磁盘文件,或把磁盘文件变换成输出单据,或把某一磁盘文件的内容由一个介质文件传送到另一介质文件。
一般在进行输入变换的同时,还可进行形式性的逻辑检查,如输入单据的数据范围、录入错误等等。
2.合并
把多个文件合并为一个文件
3.划分
是合并的逆操作,将合并工序的输入文件与输出文件对调即可。
4.分类(排序)
按指定的键(关键字)以升序或降序改变原文件的记录排列顺序。
5.更新
将多个文件作为输入根据关键项目进行对照,对文件进行内容修正、删除、增加等改写工作,一般更新的内容先要写入一个临时文件。
(3)使用系统流程图需要注意的问题
1.尽量缩短处理时间
2.尽量减少空闲时间
为了减少操作人员的工作量,如果具有多重处理能力,要尽量利用它。
如何对发生的错误采取措施的做法进行系统化,则对处理时间和空闲时间有很大影响。
3.要考虑便于完成程序的调试
工序数和系统的类型,由于存储容量和中间介质的使用如何,使用几台机器问题,也要受到影响。因此在这个阶段,可对存储容量和机器结构进行预测。
需求分析
需求分析的任务
软件需求分析阶段的研究对象是软件项目的用户需求,如何准确表达用户的需求,怎样与用户共同明确将要开发的是一个什么样的系统,是需求分析要解决的主要问题。也就是说,需求分析阶段的任务并不是确定系统怎样完成工作,而仅仅是确定系统必须完成哪些工作,即对目标系统提出完整、准确、清晰、具体的要求。需求分析阶段所要完成的任务是在可行性研究成果的基础上,通过分析归纳建立模型,编写软件需求规格说明书。
1.认清问题,分析资料,建立分析模型
通过调查研究,分析员根据软件工作范围,充分理解用户提出的每项功能与要求。同时从软件系统的特征、软件开发的全过程,以及可行性分析报告中给出的资源和时间约束来确定软件开发的总策略。只有用户才知道自己需要什么,但是他们并不知道怎样利用软件来实现自己的需求,所以用户必须把他们对软件的需求尽量准确、具体地描述出来。虽然分析员知道怎样用软件实现人们的需求,但是在需求分析时他们对用户的需求并不十分清楚,必须通过与用户沟通才能获取用户对软件的需求。
一般来说,系统是杂乱无章的,并且用户群体中的各个用户会从不同角度提出对原始问题的理解及对用计算机要实现管理的软件系统的需求,但并非所有的用户提出的要求都是合理的,所以必须全面理解用户的各项要求,不可能接受所有的要求。在需求分析阶段,分析员要对收集到的大量资料和数据进行分析归纳,透过现象看本质,看到事物的内在联系及矛盾所在;同时,对于那些非本质的东西,找出解决矛盾的办法;最后,通过“抽象”建立起描述软件需求的一组模型,即分析模型。分析模型应该包含系统的界面要求、功能要求、性能(如响应时间、吞吐量、处理时间、对内外存的限制等)要求、安全性要求、保密性要求、可靠性要求、运行要求(如对硬件、支撑软件、数据通信接口等的要求)、异常处理等对系统的综合需求,以及对系统信息处理中数据元素的组成、数据的逻辑关系、数据字典和数据模型等系统的数据要求。这些是形成软件需求说明书、进行软件设计与实现的基础。
2.编写软件需求说明书
分析模型建立后,要编写软件需求说明书来进行描述。该说明书是软件生存周期中一份极为重要的文档,它是连接计划阶段和开发阶段的桥梁,是软件设计的依据。许多事实表明,软件需求说明书的任何一个微小错误都有可能导致系统的错误,在纠正时将会付出巨大的代价。
软件需求说明书是沟通用户与分析员的媒介,双方要用它来表达对需要计算机解决的问题的理解。表述的语言应当易于理解而无二义性,尤其是在描述的过程中最好不使用用户不易理解的专业术语。为了便于用户、尤其是不熟悉计算机的用户理解,软件需求说明书应该直观、易读和易于修改,所以应尽量以图文并茂的方式,采用朴实的语言、标准的图形、表格和简单的符号来表示。
需求分析的步骤
软件需求分析必须采用合理的步骤,才能准确地获取软件的需求,产生符合要求的软件需求规格说明书。软件需求分析可以分为需求获取、分析建模、文档编写、需求验证几个过程。
1.需求获取
需求获取通常从分析当前系统包含的数据开始。首先分析现实世界,进行现场调查研究,通过与用户的交流,理解当前系统是如何运行的,了解当前系统的机构、输入/输出、资源利用情况和日常数据处理过程,并用一个具体模型反映分析员对当前系统的理解。这就是当前系统物理模型的建立过程。这一模型应客观地反映现实世界的实际情况。
2.分析建模
分析建模的过程,就是从当前系统的物理模型中,抽象出当前系统的逻辑模型,再利用当前系统的逻辑模型,除去那些非本质的东西,抽象出目标系统的逻辑模型的过程,即对目标系统的综合要求及数据要求的分析归纳过程,是需求分析过程中关键的一步。在理解当前系统“怎么做”的基础上,抽取其“做什么”的本质,从而从物理模型中抽象出当前系统的逻辑模型。在物理模型中有许多物理的因素,随着分析工作的深入,需要对物理模型进行分析,区分出本质和非本质的因素,去掉那些非本质的因素,得出反映系统本质的逻辑模型。分析内容如下。
(1)分析目标系统与当前系统在逻辑上的差别,从当前系统的逻辑模型导出目标系统的逻辑模型。从分析当前系统与目标系统变化范围的不同,决定目标系统与当前系统在逻辑上的差别;将变化的部分看做是新的处理步骤,并对数据流进行调整;由外向里对变化的部分进行分析,推断其结构,获取目标系统的逻辑模型。
(2)补充目标系统的逻辑模型。为了使已经得出的模型能够对目标系统作完整的描述,还需要从目标系统的人机界面、尚未详细考虑的细节,以及其他诸如系统能够满足的性能和限制等方面对其加以补充。
3.文档编写
已经确定的目标系统的逻辑模型应当得到清晰准确的描述。描述目标系统的逻辑模型的文档称为软件需求说明书。软件需求说明书是软件需求分析阶段最主要的文档。同时,为了准确表达用户对软件的输入/输出要求,还需要制定数据要求说明书及编写初步的手册,以及目标系统对人机界面和用户使用的具体要求。此外,依据在需求分析阶段对目标系统的进一步分析,可以更准确地估计被开发项目的成本与进度,从而修改、完善并确定软件开发实施计划。
4.需求验证
虽然分析员提供的软件需求说明书的初稿看起来可能是正确的,但在实现的过程中却会出现各种各样的问题,如需求不一致问题、二义性问题等。这些都必须通过需求分析的验证、复审来发现,确保软件需求说明书可作为软件设计和最终系统验收的依据。这个环节的参与者有用户、管理部门、软件设计人员、编码人员和测试人员等。验证的结果可能会引起修改,必要时要修改软件计划来反映环境的变化。需求验证是软件需求分析任务完成的标志。
需求获取的方法
需求获取是软件开发工作中最重要的环节之一,其工作质量的好坏对整个软件系统开发建设的成败具有决定性的作用。需求获取工作量大,所涉及的过程、人员、数据和信息非常多,因此要想获得真实、全面的需求,必须要有正确的方法。常规的需求获取方法有以下几种。
1.收集资料
收集资料就是将用户日常业务中所用的计划、原始凭据、单据和报表等的格式或样本统统收集起来,以便对它们进行分类研究。
2.召开调查会
召开调查会是一种集中征询意见的方法,适合于对系统的定性调查。
3.个别访问
召开调查会有助于大家的见解互相补充,以便形成较为完整的印象。但是由于时间限制等其他因素,不能完全反映出每个与会者的意见,因此,往往需要在会后根据具体需要再进行个别访问。
4.书面调查
根据系统特点设计调查表,用调查表向有关单位和个人征求意见和收集数据,该方法适用于比较复杂的系统。
5.参加业务实践
如果条件允许,亲自参加业务实践是了解现行系统的最好方法。通过实践还可加深开发人员和用户的思想交流和友谊,这将有利于下一步的系统开发工作。
6.发电子邮件
如果企业已经具有网络设施,可通过互联网和局域网发电子邮件进行调查,这可大大节省时间、人力、物力和财力。
7.电话和电视会议
如果有条件,还可以利用打电话和召开电视会议进行调查,但只能作为补充手段,因为许多资料需要亲自收集和整理。
软件需求说明书
软件需求说明书(Software Requirement Specification,SRS),又称为软件规格说明书,是分析员在需求分析阶段需要完成的文档,是软件需求分析的最终结果。它的作用主要是:作为软件人员与用户之间事实上的技术合同说明;作为软件人员下一步进行设计和编码的基础;作为测试和验收的依据。SRS必须用统一格式的文档进行描述,为了使需求分析描述具有统一的风格,可以采用已有的且能满足项目需要的模板,也可以根据项目特点和软件开发小组的特点对标准进行适当的改动,形成自己的模板。软件需求说明主要包括引言、任务概述、需求规定、运行环境规定和附录等内容。
本节以库存管理系统软件为例来介绍如何撰写软件需求说明书。
1.引言
企业管理信息系统建设有利于企业科学化、合理化、制度化和规范化的管理,使企业的管理水平跨上新台阶,为企业持续、健康、稳定的发展打下基础。××公司为了提高库存周转率,加快资金周转速度,决定开发“库存管理系统”。该软件是以SQL语言作为实现语言,以ASP.NET作为主要技术手段。通过操作手册,用户可以了解本软件的工作过程,考虑到不同层次的用户,一方面可以通过简单的鼠标和键盘操作实现库存管理工作中的相关操作,另一方面也可以使有一定计算机基础的用户根据自己的需求对相关的内容进行相应的修改,以便适应本企业的实际情况。
1)编写目的
本需求说明书的编写目的在于研究库存管理系统软件的开发途径和应用方法。本需求说明书的预期读者是与库存系统开发相联系的决策人、开发组成员、辅助开发者、项目用户的负责人、使用者和软件验证者。
2)项目范围
项目名称:库存管理系统。
项目开发者:库存管理系统开发小组。
项目用户:采购部门、车间、物资管理部门、供应商。
本产品将采购部门、车间、物资管理部门、供应商等联系到一起,便于进行出入库等部分的管理。
3)参考资料
根据需要列举参考资料。
2.任务概述
1)产品概述
(1)软件开发意图:为了使库存管理系统更加完善,以公司局域网作为基础,利用计算机系统实现库存管理各个相关部门的联系,减轻库存管理相关人员的工作负担,实现对库存管理各种相关事务的统一管理。
(2)软件的应用目标:通过本软件,管理人员可利用计算机快速、方便地进行出入库管理等相关事务,使分散、杂乱的管理变得统一。
(3)软件的作用范围:本软件适应于中小型生产企业,对于出入库管理,库存管理系统是比较完善的库存管理软件。
(4)该软件开发背景:为适应该行业的形势,在外在资源得到充分保障之后,为了使库存管理体系的管理工作,特别是信息管理工作科学化、规范化,以适应日益变化的时代需求,公司领导决定重新设计管理信息系统,以现阶段的管理软件为基础,形成一套完善的库存管理体系。
2)用户特点
本软件的使用对象是库存管理部门的工作人员,掌握一般计算机的基本操作就可以利用该软件进行相关操作。
3)条件与约束
(1)项目的开发经费不超过2万元。
(2)项目的开发周期不超过6个月。
(3)主要负责人1名,开发小组成员3名,其他辅助人员2名。
(4)在管理方针、硬件限制、并行操作、安全和保密方面有一定限制。
4)预计不良后果
假设开发经费不到位,管理不完善,数据处理不规范,各部门需求分析调查不细致,本项目的开发会受到很大影响。
3.需求规定
1)对功能的规定
(1)外部功能:该软件具有输入、输出和查询等功能。
(2)内部功能:该软件集命令、编程和编辑于一体,完成过滤、定位、显示等功能。
2)对性能的规定
(1)精度:在精度需求上,根据使用需要,在各项数据的输入/输出及传输过程中,可以满足各种精度的需求。
(2)时间特性要求:在软件响应时间、更新处理时间方面满足用户要求。
(3)灵活性:当用户需求,如操作方式、运行环境、结果精度、数据结构与其他软件接口等发生变化时,设计的软件可以作适当调整,以满足不同用户的要求。
3)输入/输出要求(略)
4)数据管理能力要求(略)
5)故障处理要求(略)
6)其他专门要求(略)
7)保密性(略)
4.运行环境规定
1)设备
(1)最低配置:600MHz PentiumⅢ处理器,256MB内存,5GB可用硬盘空间,DVDROM驱动器。
(2)开发环境:一台服务器作为数据服务器,一台高性能计算机作为Web服务器和开发用机,硬件防火墙。
2)支持软件(略)
3)接口
(1)用户接口:本产品的用户一般需要通过终端进行操作,进入主界面后单击相应的窗口,分别进入相对应的界面。不同部门人员的操作权限不同。用户对程序的维护要有备份。
(2)软件接口:Windows XP及以上操作系统,IE 6.0及以上浏览器。
4)控制
本软件是以SQL语言来控制软件运行的。
结构化分析方法
结构化分析模型
软件的结构化分析模型通常是由一组模型组成的,其中包括数据模型、功能模型和行为模型。目前有两种主要的建立分析模型的方法:一种方法是结构化分析模型,这是传统的建模方法,将在本节进行描述;另一种方法是面向对象分析模型,将在后面章节进行详细介绍。
结构化分析模型的组成结构如下图所示,可以看出模型的核心是数据字典(Data Dictionary,DD),这是系统所涉及的各种数据对象的总和。从数据字典出发主要通过3个模型来构建结构化分析模型。
(1)实体联系图(Entity Relation Diagram,ER图):用于描述数据对象间的关系、构建软件的数据模型,在实体关系中出现的每个数据对象的属性均可用数据对象进行说明描述。
(2)数据流图(Data Flow Diagram,DFD):其主要作用是指明系统中数据是如何流动和变换的,以及描述数据流是如何进行变换的。在DFD中出现的每个功能都会写在加工说明(Process Specification,PSPEC)中,它们是构成系统的功能模型。
(3)状态转换图(Status Transfer Diagram,STD):用于指明系统在外部事件的作用下将如何动作,表明系统的各种状态及各种状态间的变迁。所有软件控制方面的附加信息包含在控制说明(Control Specification,CSPEC)中,它们构成系统的行为模型。
数据流图
数据流图是结构化分析最基本的工具,数据流图从数据传递和加工的角度,以图形化的方式刻画数据流从输入到输出的移动和变换过程。在数据流图中具体的物理元素都已去掉,只剩下数据的存储、流动、加工和使用情况。这种抽象性能使人们总结出信息处理的内部规律性。由于数据流图是用图形来表示逻辑系统的,即使不是计算机专业人员也能比较容易地理解数据流图,因此它成为了一种极好的通信工具。
1.数据流图的基本符号
数据流图由下图所示的4种基本符号表示。
1)数据流
数据流由一组确定的数据组成,例如“发票”为一个数据流,它由品名规格、单位、单价、数量等数据组成。数据流用带有名字的有箭头的线段表示,名字称为数据流名,表示流经的数据,箭头表示流向。数据流可以从加工流向加工,也可以从加工流进或流出文件,还可以从数据源流向加工或从加工流向终点。
对数据流的表示有以下约定。
(1)对流进或流出文件的数据流不需标注名字,这是因为文件本身就足以说明数据流;而别的数据流则必须标出名字,名字应能反映数据流的含义。
(2)数据流不允许同名。
(3)两个数据流在结构上相同是允许的,但必须体现人们对数据流的不同理解。
(4)两个加工之间可以有几股不同的数据流,这是由于它们的用途不同,或它们之间没有联系,或它们的流动时间不同。
(5)数据流图描述的是数据流而不是控制流。
2)加工处理
加工处理是对数据流转换进行的操作,它把流入的数据流转换为流出的数据流。每个加工处理都应取一个名字以表示它的含义,并规定用一个编号来标识该加工在层次分解中的位置。名字中必须包含一个动词,例如“计算”、“打印”等。
数据流加工转换的方式有两种:
(1)改变数据流的结构,如将数组中各数据重新排序;
(2)产生新的数据流,如对原来的数据进行汇总、统计和求平均值等。
3)文件
文件是存储数据的工具。文件名应与它的内容一致,写在开口长条内。从文件流入或流出数据流时,数据流方向是很重要的。如果是读文件,则数据流的方向应从文件流出,写文件时则相反;如果是既读又写文件,则数据流是双向的。在修改文件时,虽然必须首先读文件,但其本质是写文件,因此数据流应流向文件,而不是双向的。
4)数据源或终点
数据源和终点表示数据的外部来源和去处。它通常是系统之外的人员或组织,不受系统控制。
为了避免在数据流图上出现线条交叉,同一个源点、终点或文件均可在不同位置多次出现,这时要在源点或终点符号的右下方画小斜线以示重复。
2.数据流图的画法
1)画法原则
一般遵循“由外向里”的原则,即先确定系统的边界或范围,再考虑系统的内部;先画加工的输入和输出,再画加工的内部,具体如下。
(1)识别系统的输入/输出;
(2)从输入端至输出端画数据流和加工,并同时加上文件;
(3)加工的分解“由外向里”进行分解;
(4)数据流的命名要确切,名字能反映整体;
(5)各种符号布置要合理,分布均匀,尽量避免交叉线。
2)画法步骤
对于不同的问题,数据流图可以有不同的画法,具体操作时可按下述步骤进行。
(1)识别系统的输入/输出,画出顶层图,即确定系统的边界。
在需求分析阶段,系统的功能需求等还不很明确,为了防止遗漏,不妨先将范围定得大一些。在确定系统边界后,越过边界的数据流就是系统的输入/输出,将输入/输出用加工符号连接起来,并加上输入数据来源和输出数据去向就形成了顶层图。
(2)画系统内部的数据流、加工与文件,画出一级细化图。
从系统输入端到输出端(也可反之),逐步用数据流和加工连接起来,当数据流的组成或值发生变化时,就在该处画一个“加工”符号。
画数据流图时还应同时画上文件,以反映各种数据的存储处,并表明数据流是流入文件还是流出文件。
最后,再回过头来检查系统的边界,补上遗漏但有用的输入/输出数据流,删去那些没被系统使用过的数据流。
(3)加工的进一步分解,画出二级细化图。
同样运用“由外向里”的方式对每个加工进行分析,如果在该加工内部还有数据流,则可将该加工分成若干个子加工,并用一些数据流把子加工连接起来,即可画出二级细化图。二级细化图可在一级细化图的基础上画出,也可单独画出该加工的二级细化图,二级细化图也称为该加工的子图。需要注意编号问题,即对上一级加工的分解,应以上一级编号为开始,加一个“.”,并依次编号。例如对1号加工的分解,编号为1.1,1.2,…。
(4)其他注意事项。
一般应先给数据流命名,再根据输入/输出数据流名的含义为加工命名。名字含义要确切,要能反映相应的整体。若碰到难以命名的情况,则很可能是分解不恰当造成的,应考虑重新分解。
从左至右画数据流图。通常左侧、右侧分别是数据源和终点,中间是一系列加工和文件。正式的数据流图应尽量避免线条交叉,必要时可用重复的数据源、终点和文件符号。此外,数据流图中各种符号布置要合理,分布应均匀。
画数据流图是一项艰巨的工作,要做好重画的思想准备,重画是为了消除隐患,有必要不断改进。
因为作为顶层加工处理的改变域是确定的,所以改变域的分解是严格地自顶向下分解的。由于目前还不存在目标系统,因此分解时开发人员还需凭经验进行,这是一项创造性的劳动。同时,在建立目标系统数据流图时,还应充分利用本章讲过的各种方法和技术,例如,分解时尽量减少各加工之间的数据流,数据流图中各个成分的命名要恰当,父图与子图间要注意平衡,等等。
在画出分层数据流图,并为数据流图中各个成分编写词典条目或加工说明后,就获得了目标系统的初步逻辑模型。
3.绘制数据流图时应注意的问题
下面从3个方面讨论画分层数据流图时应注意的问题。
1)合理编号
分层数据流图的顶层称为0层,它是第1层的父图,而第1层既是0层图的子图,又是第2层图的父图,依此类推。由于父图中有的加工可能就是功能单元,不能再分解,因此父图拥有的子图数不大于父图中的加工个数。
为了便于管理,应按下列规则为数据流图的加工编号:
(1)子图中的编号由父图号和子加工的编号组成;
(2)子图的父图号就是父图中相应加工的编号。
为简单起见,约定第1层图的父图号为0,编号只写加工编号1,2,3,…。下面各层由父图号1,1.1等加上子加工的编号1,2,3组成。按上述规则,图的编号既能反映出它所属的层次及它的父图编号的信息,还能反映子加工的处理信息。例如,1表示第1层图的1号加工处理,1.1,1.2,1.3,…表示父图为1号加工的子加工,1.3.1,1.3.2,1.3.3,…表示父图号为1.3加工的子加工。
2)注意子图与父图的平衡
子图与父图的数据流必须平衡,这是分层数据流的重要性质。这里的平衡指的是子图的输入/输出数据流必须与父图中对应加工的输入/输出数据流相同。
3)分解的程度
对于规模较大的系统的分层数据流图,如果一次性把加工直接分解成基本加工单元,一张图上画出过多的加工将使人难以理解,也增加了分解的复杂度。然而,每次分解产生的子加工太少,会使分解层次过多而增加作图的工作量,阅读也不方便。经验表明,一个加工每次的分解量最多不要超过7个为宜。同时,分解时应遵循以下原则。
(1)分解应自然,概念上要合理、清晰。
(2)上层可分解得快些(分解成的子加工个数多些),这是因为上层是综合性描述,对可读性的影响小,而下层应分解得慢些。
(3)在不影响可读性的前提下,应适当地多分解成几部分,以减少分解层数。
一般说来,当加工可用一页纸明确地表述,或加工只有单一输入/输出数据流(出错处理不包括在内)时,就应停止对该加工的分解。另外,对数据流图中不再作分解的加工(功能单元),必须作出详细的加工说明,并且每个加工说明的编号必须与功能单元的编号一致。
数据字典
在数据流图的基础上,还需对其中的每个数据流、文件和数据项加以定义,把这些定义所组成的集合称为数据字典。数据流图是系统的大框架,而数据字典及加工说明则是对数据流图中每个成分的精确描述。它们有着密切的联系,必须结合使用。
在数据字典中有三种类型的条目:数据项条目、数据流条目和文件条目。下面分别讨论。
1.数据项条目
数据项条目用于给出数据项的定义。由于数据项是数据的最小单位,是不可分割的,因此数据项条目只包含名称、代码、类型、长度和值的含义等。对于那些足以从名称看出其含义的“自说明”型数据项,则不必在条目中再行解释。
2.数据流条目
数据流条目对每个数据流进行定义,它通常由数据流名、别名、组成、注释、流入、流出和流通量等部分组成。其中,别名是前面已定义的数据流的同义词;组成是定义的主要部分,通常要列出该数据流的各组成数据项;注释用于记录其他有关信息,如该数据流在单位时间中传输的次数等。
如果数据流的组成很复杂,则可采用“自顶向下,逐步分解”的方式来表示。
在数据字典各条目的定义中,常使用下述符号:
3.文件条目
文件条目用于对文件(或数据库)进行定义,它由5部分组成:文件名、编号、组成、结构和注释。其中,组成的定义方法与前面的数据流条目的相同;结构用于说明重复部分的相互关系,如指出是顺序或索引存取。
加工说明的描述工具
由于自然语言不够精确、简练,不适合编写加工说明。目前有许多适用加工说明的描述工具。下面介绍几种最常用的工具:结构化语言(Structured Language)、判定表(Decision Table)和判定树(Decision Tree)。
1.结构化语言
自然语言的优点是容易理解,但是不精确,可能有多义性。程序设计语言的优点是严格精确,但其语法规定太死板,使用不方便。结构化语言则是介于自然语言和程序设计语言之间的一种语言,是带有一定结构的自然语言。在我国,通常采用较易为用户和开发人员接受的结构化汉语。
在用结构化语言描述问题时只允许使用3种基本逻辑结构:顺序结构、选择结构和循环结构。配合这3种结构所使用的词汇主要有3类:陈述句中的动词;在数据字典中定义的名词;某些逻辑表达式中的保留字、运算符、关系符等。后面我们还会具体说明这3种语句的使用方式。
为了减少复杂性,便于人们理解,编写加工说明需要注意以下几点:
(1)避免结构复杂的长句;
(2)所用名词必须在数据字典中有定义;
(3)不要用意义相同的多种动词,用词应始终如一(例如,“修正”、“修改”、“更改”含义相同,一旦确定使用其中一个以后,就不要再用其余两个);
(4)为提高可读性,书写时可采用阶梯形格式;
(5)嵌套使用各种结构时,应避免嵌套层次过多而影响可读性。
2.判定表
对于具有多个互相联系的条件和可能产生多种结果的问题,用结构化语言描述显得不够直观和紧凑,这时可以用以清晰、简明为特征的判定表来描述。
判定表采用表格形式来表达逻辑判断问题,表格分成4部分:左上角为条件说明;左下角为行动说明;右上角为各种条件的组合说明;右下角为各条件组合下相应的行动。
下面举例说明如何使用判定表。
表1为使用判定表描述订货折扣政策问题。其中,C1~C3为条件,A1~A4为行动,1~8为不同条件的组合,Y表示条件满足,N表示条件不满足,X为该条件组合下的行动。例如,条件4表示:若交易额在5万元以上或最近3个月中有欠款且与本公司交易在20年以下,则可享受5%的折扣率。
判定表是根据条件组合进行判断的,上面表格中每个条件只存在“Y”和“N”两种情况,所以3个条件共有8种可能性。在实际使用中,有的条件组合可能是矛盾的,需要剔除,有的则可以合并。因此需在原始判定表的基础上进行整理和综合,才能得到简单明了且实用的判定表。同时,在整理过程中,还可能对用户的原有业务过程进行改进和提高。表2所示的是由表1合并得到的,其中“—”表示“Y”或“N”均可。
表1 判定有描述的折扣政策
表2 合并整理后的判定表
判定表的内容十分丰富,除了以上介绍的有限判定表(Limited Entry Table)外,根据表中条件取值的状态不同,还有扩展判定表(Extended Entry Table)和混合判定表(Mixed Entry Table)等,它们各有特色,若能合理选择和灵活运用,则可描述、处理更广泛、复杂的判断过程。详细的内容可参阅有关的书籍。
3.判定树
判定树是用于表示逻辑判断问题的一种图形工具。它用“树”来表达不同条件下的不同处理,比语言、表格的方式更为直观。判定树的左侧(称为树根)为加工名,中间是各种条件,所有的行动都列于最右侧。
表1所描述的折扣政策可以用如图1所示的判定树来进行描述。
图1 判定树描述的折扣政策
4.几种表达工具的比较
以上介绍的3种用于描述加工说明的工具各自具有不同的优点和不足,它们之间的比较如表3所示。通过比较可以看出它们的适用范围。
(1)结构化语言最适用于具有判断或循环动作组合顺序的问题。
(2)判定表较适用于含有5~6个条件的复杂组合,条件组合过于庞大则将造成不便。
(3)判定树适用于行动在10~15之间的一般复杂程度的决策,必要时可将判定表上的规则转换成判定树,以便用户使用。
(4)判定表和判定树也可用于软件开发的其他阶段,并被广泛地应用于其他学科。
表3 几种表达工具的比较
成本——效益分析
成本—效益分析法是通过比较项目的全部成本和效益来评估项目价值的一种方法,成本—效益分析法作为一种经济决策方法,将成本费用分析法运用于项目的计划决策之中,以寻求如何以最小的成本获得最大的收益。常在该方法中,某一项目或决策的所有成本和收益都将被一一列出,并进行量化。
成本—效益分析法的基本原理是:针对某项支出目标,提出若干实现该目标的方案,运用一定的技术方法,计算出每种方案的成本和收益,通过比较方法,并依据一定的原则,选择出最优的决策方案。
在开始成本—效益分析前了解成本现状十分重要。你需要权衡每一项投资的利弊。如果可能的话,再权衡一下不投资会有什么影响。不要以为如果不投资成本就会变高。在许多情况下,虽然新投资可获得巨额利润,但是不投资的成本相对更小。
对一项投资进行成本—效益分析的步骤:
①确定购买新产品或一个活动中的成本。
②确定额外收入的效益。
③确定可节省的费用。
④制定预期成本和预期收入的时间表。
⑤评估难以量化的效益和成本。
前三个步骤十分简单明了。首先确定与商业风险相关的一切成本——本年度主要的成本以及下一年度的预计成本。额外收入也许是由于顾客数量的增加或现有顾客购买量的扩大。为了解这些收入的效益,一定要将与收入相关的新成本考虑在内,最后就可以考虑利润了。可节省费用显得简单一些,至少在某种意义上反映了利润的增加,可直接计入利润。然而,有时可节约费用也有微妙之处,更难确认。可节约费用可以来自各种渠道,以下列举的一些渠道比较便于量化:
◇更有效的加工:这意味着减少加工过程需要的人数,或者说简化加工步骤,甚至于缩短每一步骤所用的时间。
◇更精确的加工:要求减少修正错误的时间和尽量避免客户的流失。
下一步,为以下两个要素——即成本和收入或可节约费用——制订出相关一段时期内的计划。你希望该成本何时发生?成本的增量是多少?你期望何时获得效益(额外收入或可节约费用)?效益增值是多少?
成本—效益分析首先是估算新系统的开发成本,然后与可能取得的效益进行权衡比较。涉及的概念如下所示:
(1)货币的时间价值
货币是有时间价值的,这个时间价值简单一点讲,就是今天(2010-12-22)的100元钱与明年今天(2011-12-22)的100元钱的价值是不一样的。为什么呢?很简单,“钱能生钱”,如果我把100元钱存到银行,明年的今天,这100元钱将变为:100元+100元钱1年的利息。那么如果我们将本金与累计利息之和用公式表达为:
F=P×(1+i)n
这就是P元钱在n年之后的价值。
那么反过来思考,得知n年后我有F元钱,要折算为今天的价值,则称为“折现”。计算公式为:
P=F/(1+i)n
注意:该公式就是由上面的式子变换而来的。
(2)投资回收期
累计的经济效益等于最初的投资所需要的时间。
(3)纯收入
整个软件生命期内,累计经济效益(折合成现在值)与投资之差。
3、总体设计
软件设计的概念
结构化设计(Structured Design,SD)方法是一种面向数据流的设计方法,它是以结构化分析阶段所产生的文档(包括数据流图、数据字典和软件需求说明书)为基础,自顶向下、逐步求精和模块化的过程。结构化设计通常可分为总体设计和详细设计等两部分。总体设计的任务是确定软件系统的结构,进行模块划分,确定每个模块的功能、接口及模块间的调用关系。详细设计的任务是为每个模块设计其实现的细节。为了开发出高质量、低成本的软件,在软件开发过程中必须遵循如下软件工程原则。
1.抽象
抽象即抽取事物最基本的特性和行为,忽略非基本的细节。采用分层次抽象的办法可以控制软件开发过程的复杂性,有利于软件的可理解性和开发过程的管理。
2.模块化
模块化使程序由许多个逻辑上相对独立的模块组成。模块(Module)是程序中逻辑上相对独立的单元;模块的大小要适中,高内聚、低耦合。
3.信息隐藏
采用封装技术,将程序模块的实现细节(过程或数据)隐藏起来,对于不需要这些信息的其他模块来说是不能访问的,模块接口应尽量简单。按照信息隐藏的原则,系统中的模块应设计成“黑箱”模块,外部只能使用模块接口说明中给出的信息,如操作、数据类型等。
4.模块独立性
每个模块只能完成系统要求的独立子功能,与其他模块的联系较少且接口简单。模块独立的概念是模块化、抽象、信息隐蔽概念的直接结果。
抽象
人类在认识复杂现象的过程中使用的最强有力的思维工具是抽象。人们在实践中认识到,现实世界中一定事物、状态或过程之间总存在着某些相似的方面(共性)。把这些相似的方面集中和概括起来,暂时忽略它们之间的差异,这就是抽象。或者说抽象就是抽取事物的本质特性而暂时不考虑它们的细节。
由于人类思维能力的限制,如果每次面临的因素太多,是不可能产生精确思维的。处理复杂系统的唯一有效的方法是用层次的方式构造和分析它。一个复杂的动态系统首先可以用一些高层次的抽象概念构造和理解,这些高级概念又可以用一些较低层次的概念构造和理解,如此进行下去,直至最低层次的具体元素为止。
这种层次的思维和解题方式必须反映在定义动态系统的程序结构中,每级的一个概念将以某种方式对应于程序的一组成分。
当考虑对任何问题的模块化解法时,可以提出许多抽象的层次:在抽象的最高层次采用问题环境的语言,以概括的方式叙述问题的解法;在较低层次采用过程化的方法,把面向问题的术语和面向实现的术语结合起来叙述问题的解法;在最低层次采用直接实现的方式叙述问题的解法。
软件工程过程的每一层都是对软件解法的抽象层次的一次精化。在可行性研究阶段,软件作为系统的一个完整部件;在需求分析期间,软件解法是使用在问题环境内熟悉的方式描述的;当总体设计向详细设计过渡时,抽象的程度也就随之减小了;最后,在源程序写出来以后,也就达到了抽象的最低层次。
逐步求精和模块化的概念,与抽象是紧密相关的。随着软件开发工程的进展,在软件结构每一层中的模块,表示了对软件抽象层次的一次精化。事实上,软件结构顶层的模块,控制了系统的主要功能并且影响全局;在软件结构最底层次的模块,完成对数据的一个具体处理。用自顶向下由抽象到具体的方式分配控制,简化了软件的设计和实现,提高了软件的可理解性和可测试性,并且使软件更容易维护。
模块化
模块是由边界元素限定的相邻程序元素(如数据说明、可执行的语句)的序列,而且有一个总体标识符代表它。像Pascal或Ada这样的块结构语言中的Begin…End对,或者C、C++和Java语言中的{…}对,都是边界元素的例子。按照模块的定义,过程、函数、子程序和宏等,都可作为模块。面向对象方法学中的对象是模块,对象内的方法(也称为服务)也是模块。模块是构成程序的基本构件。
模块化就是把程序划分成独立命名且可独立访问的模块的过程,每个模块完成一个子功能,把这些模块集成起来构成一个整体,可以完成指定的功能并满足用户的需求。
有人说,模块化的目的是使一个复杂的大型程序能被人类的智力所管理,模块化是软件应该具备的唯一属性。如果一个大型程序仅由一个模块组成,它将很难被人类所理解。下面根据人类解决问题的一般规律,论证上面的结论。
设函数C(x)定义问题x的复杂程度,函数E(x)确定解决问题x需要的工作量或时间。对于两个问题P1和P2,如果
C(P1)>C(P2)
显然,E(P1)>E(P2)
根据人类解决一般问题的经验,另一个有趣的规律是
C(P1+P2)>C(P1)+C(P2)
也就是说,如果一个问题由P1和P2两个问题组合而成,即P1+P2,那么它的复杂程度大于P1、P2的复杂程度之和。
综上所述,得到下面的不等式:
E(P1+P2)>E(P1)+E(P2)
由上述不等式可以得出,“各个击破”的结论——把复杂的问题分解成许多容易解决的小问题,原来的问题也就容易解决了。这就是模块化的根据。
由上面的不等式似乎还能得出下述结论:如果无限地分割软件,最后为了开发软件而需要的工作量也就小得可以忽略了。事实上,还有另一个因素在起作用。当模块数目增加时,每个模块的规模将减小,开发单个模块需要的成本(工作量)确实减小了;但是,随着模块数目增加,设计模块之间的接口所需的工作量则将增加。每个程序都相应地有一个最适当的模块数目,使得系统的开发成本最小。
模块化原理可以使软件结构清晰,不仅容易设计,也容易阅读和理解。因为程序错误通常局限在有关的模块及它们之间的接口中,所以模块化使软件容易测试和调试,因而有助于提高软件的可靠性。因为变动往往只涉及少数几个模块,所以模块化能够提高软件的可修改性。模块化也有助于软件开发工程的组织管理,一个复杂的大型程序可以由许多程序员分工编写不同的模块,并且可以进一步把复杂的模块分配给技术熟练的程序员来编写。
信息隐藏与局部化
应用模块化原理时,自然会产生的一个问题是:“为了得到最好的一组模块,应该怎样分解软件呢?”信息隐藏原理指出:对于不需要这些信息的模块来说,应该这样设计和确定模块,使得一个模块内包含的信息(过程和数据)是不能访问的。
局部化的概念和信息隐藏的概念是密切相关的。所谓局部化是指把一些关系密切的软件元素物理地放得彼此靠近的过程。在模块中使用局部数据元素是局部化的一个例子。显然,局部化有助于实现信息隐藏。
实际上,应该隐藏的不是有关模块的一切信息,而是模块的实现细节。因此,有人主张把这条原理称为细节隐藏。
“隐藏”意味着有效的模块化可以通过定义一组独立的模块来实现,这些独立的模块彼此间仅仅交换那些为了完成系统功能而必须交换的信息。
如果在测试期间和以后的软件维护期间需要修改软件,那么使用信息隐藏原理作为模块化系统设计的标准就会带来极大好处。因为对于软件的其他部分而言,绝大多数数据和过程是隐藏的(也就是“看”不见的),在修改期间由于疏忽而引入的错误就很少可能传播到软件的其他部分。
模块独立性
模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果。开发具有独立功能而且与其他模块之间没有过多的相互作用的模块,就可以做到模块独立。换句话说,这样设计的软件结构,使得每个模块完成一个相对独立的特定子功能,并且与其他模块之间的关系很简单。
为什么模块独立性很重要呢?主要有两条理由:第一,有效的模块化(具有独立的模块)软件比较容易开发出来,当许多人分工合作开发同一个软件时,这个优点尤其重要;第二,独立的模块比较容易测试和维护。相对来说,修改、设计程序需要的工作量比较小,错误传播范围小,需要扩充功能时能够“插入”模块。总之,模块独立是好的设计的关键,而设计又是决定软件质量的关键环节。
模块的独立程度有两个定性标准度量,即耦合和内聚。耦合衡量不同模块彼此间互相依赖(连接)的紧密程度;内聚衡量一个模块内部各个元素彼此结合的紧密程度。以下分别详细阐述。
1.耦合
耦合是对一个软件结构内不同模块之间互连程度的度量。耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点,以及通过接口的数据。
在软件设计中应该追求尽可能松散耦合的系统。在这样的系统中可以研究、测试或维护任何一个模块,而不需要对系统的其他模块有很多了解。此外,由于模块间联系简单,所以发生在一处的错误传播到整个系统的可能性就很小。因此,模块间耦合程度强烈影响着系统的可理解性、可测试性、可靠性和可维护性。
怎样具体区分模块间耦合程度的强弱呢?
如果两个模块中的每一个模块都能独立地工作而不需要另一个模块的存在,那么它们彼此完全独立,这意味着模块间无任何连接,耦合程度最低。但是,在一个软件系统中不可能所有模块之间都没有任何连接。
如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,那么这种耦合称为数据耦合。如果传递的信息中有控制信息(尽管有时这种控制信息以数据的形式出现),那么这种耦合称为控制耦合。
数据耦合是低耦合。系统中至少必须存在这种耦合,因为只有当某些模块的输出数据作为另一些模块的输入数据时,系统才能完成有价值的功能。一般说来,一个系统内可以只包含数据耦合。控制耦合是中等程度的耦合,它增加了系统的复杂程度。控制耦合往往是多余的,在把模块适当分解之后通常可以用数据耦合代替它。
如果被调用的模块需要使用作为参数传递进来的数据结构中的所有元素,那么,把整个数据结构作为参数传递就是完全正确的。但是,当把整个数据结构作为参数传递而被调用的模块只需使用其中一部分数据元素时,就出现了特征耦合。在这种情况下,被调用的模块可以使用的数据多于它确实需要的数据,这将导致对数据的访问失去控制,从而给计算机犯罪提供了机会。
当两个或多个模块通过一个公共数据环境相互作用时,它们之间的耦合称为公共环境耦合。公共环境可以是全程变量、共享的通信区、内存的公共覆盖区、任何存储介质上的文件、物理设备等。
公共环境耦合的复杂程度随耦合的模块个数的变化而变化,当耦合的模块个数增加时,其复杂程度显著增加。如果只有两个模块有公共环境耦合,那么这种耦合有下面两种可能。
(1)一个模块向公共环境传送数据,另一个模块从公共环境读取数据。这是数据耦合的一种形式,是比较松散的耦合。
(2)两个模块都向公共环境传送数据和读取数据,这种耦合比较紧密,介于数据耦合和控制耦合之间。
如果两个模块共享的数据很多,都通过参数传递可能很不方便,这时可以利用公共环境耦合。
最高程度的耦合是内容耦合。如果出现下列情况之一,两个模块间就发生了内容耦合。
(1)一个模块访问另一个模块的内部数据。
(2)一个模块不通过正常入口而转到另一个模块的内部。
(3)两个模块有一部分程序代码重叠(只可能出现在汇编程序中)。
(4)一个模块有多个入口(这意味着一个模块有几种功能)。
应该坚决避免使用内容耦合。事实上,许多高级程序设计语言已经设计成不允许在程序中出现任何形式的内容耦合。
总之,耦合是影响软件复杂程度的一个重要因素。应该采取下述设计原则:尽量使用数据耦合,少用控制耦合和特征耦合,限制公共环境耦合的范围,完全不用内容耦合。
2.内聚
内聚标志着一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然扩展。简单地说,理想内聚的模块只做一件事情。
设计时应该力求做到高内聚,通常中等程度的内聚也是可以采用的,而且效果和高内聚相差不多。
内聚和耦合是密切相关的,模块内的高内聚往往意味着模块间的松耦合。内聚和耦合都是进行模块化设计的有力工具,但是实践表明,应该把更多注意力集中到提高模块的内聚程度上。
1)低内聚的类别
(1)如果一个模块完成一组任务,这些任务彼此间即使有关系,其关系也是很松散的,这称为偶然内聚。有时在写完一个程序之后,发现一组语句在两处或多处出现,于是把这些语句作为一个模块以节省内存,这样就出现了偶然内聚的模块。在偶然内聚的模块中,各种元素之间没有实质性联系,很可能在一种应用场合需要修改这个模块,而在另一种应用场合又不允许这种修改,从而陷入困境。事实上,偶然内聚的模块出现修改错误的概率比其他类型模块的高得多。
(2)如果一个模块完成的任务在逻辑上属于相同或相似的一类(如一个模块产生各种类型的全部输出),则称为逻辑内聚。在逻辑内聚的模块中,不同功能的任务混在一起,合用部分程序代码,即使是局部功能的修改有时也会影响全局。因此,这类模块的修改也比较困难。
(3)如果一个模块包含的任务必须在同一段时间内执行(例如,模块完成各种初始化工作),就称为时间内聚。
时间关系在一定程度上反映了程序的某些实质,所以时间内聚比逻辑内聚好一些。
2)中内聚的类别
(1)如果一个模块内的处理元素是相关的,而且必须以特定次序执行,则称为过程内聚。使用程序流程图作为工具设计软件时,常常通过研究流程图确定模块的划分,这样得到的往往是过程内聚的模块。
(2)如果模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据,则称为通信内聚。
3)高内聚的类别
高内聚包含顺序内聚和功能内聚。
(1)如果一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行(通常一个处理元素的输出数据作为下一个处理元素的输入数据),则称为顺序内聚。根据数据流图划分模块时,通常得到顺序内聚的模块,这种模块彼此间的连接往往比较简单。
(2)如果模块内所有处理元素属于一个整体,完成一个单一的功能,则称为功能内聚。功能内聚是最高程度的内聚。
耦合和内聚的概念是由Constantine、Yourdon、Myers和Stevens等人提出来的。按照他们的观点,如果给上述7种内聚的优劣评分,将得到如下结果:
功能内聚10分 时间内聚3分
顺序内聚9分 逻辑内聚1分
通信内聚7分 偶然内聚0分
过程内聚5分
事实上,没有必要精确确定内聚的级别。重要的是设计时力争做到高内聚,并且能够辨认出低内聚的模块,有能力通过修改设计提高模块的内聚程度并且降低模块间的耦合程度,就可获得较高的模块独立性。
软件结构设计的图形工具
层次图和HIPO图
软件结构可用非常简洁的层次图(也称为H图)来描述。层次图是一种树型结构图,图中的矩形代表模块,矩形间的连线代表模块间的调用关系。它自顶向下地描述出了一个软件系统的软件结构图。
HIPO图是美国IBM公司在IPO图和层次图的基础之上发明而来的。所以,HIPO图也称为层次IPO图,或表示为H图+IPO图。
HIPO图既描述了树型的软件结构关系,又深入模块内部对其调用接口、设计策略等进行描述,因此,它是设计软件结构的有力工具。图1给出了一个软件系统的H图。若需深入描述每个模块的设计策略和细节,可借助IPO图。
图1 带编号的层次图
IPO图(Input Process Output),它描述了输入数据、输出数据和数据处理之间的关系。由于所有的软件系统都可看成是一个信息加工的中心,所以IPO图可以作为分析处理内部算法以及本处理与其他处理之间接口关系的重要工具,而这些处理涵盖了数据流图中的所有处理。常用的IPO图如下:
结构图
结构图也是一种树型软件结构图,它同样采用层次描述,但在结构图中的模块类型则更加细化,该图能更加显式地表达模块之间是如何实现调用关系和传递数据的。结构图中包含的4种类型的模块如下:
◇传入模块:实现数据从下层模块传入到上层模块。
◇传出模块:实现数据从上层模块传出到下层模块。
◇变换模块:实现数据的数据变换。
◇协调模块:协调传入、传出和变换模块。
图1 软件结构图中的4种模块
图2 A模块选择调用模块B、C、D
图3 A模块选择调用模块B和C
软件结构图举例如图4所示,该例子完成“产生最佳解”的功能。此图综合运用了图1所示的4种模块。图中左边完成数据的输入,中间计算出最佳解,右边则将最佳解输出。
图4 软件结构图的例子——产生最佳解的一般结构
总体设计方法
结构化设计方法是在模块化、自顶向下细化、结构化程序设计等程序设计技术的基础上发展起来的。它属于面向数据流的设计方法,可以很方便地将数据流图表示的信息转换成程序结构的设计描述。
1.数据流图的类型
结构化设计方法把数据流图映射成软件结构,信息流的类型决定了映射的方法。信息流分为变换流和事物流两种类型,因此组成的数据流图也分为变换型数据流图和事物型数据流图等两类。
1)变换型数据流图
系统从输入设备获取信息,同时由外部形式变换成内部形式,进入系统的信息通过变换中心,经加工处理后沿输出通路变换成外部形式,最后由输出设备离开软件系统,具有这些特性的数据流图称为变换型数据流图。
变换型数据流图是一个线性结构,由输入、变换中心和输出3部分组成,如图1所示。
图1 变换型数据流图
2)事务型数据流图
如果在一个数据流图中明显地存在着一个“事务中心”,即接受一项事务,并根据事务处理的特点和性质,选择分派一个适当的处理单元,给出结果,这种数据流图称为事务型数据流图。它由至少一条接受路径、一个事务中心与若干条动作路径组成,如图2所示。
图2 事务型数据流图
2.设计过程
面向数据流设计方法的设计过程如图3所示。
图3 面向数据流的设计方法的设计过程
3.变换分析
因为变换型结构由输入、处理和输出部分组成,所以从变换型数据流图导出变换型模块的结构图,可分三步进行。
1)找出系统的主加工
为了处理方便,先不考虑数据流图的一些支流,如出错处理等。
通常,数据流图中多股数据流的汇合处是系统的主加工。若没有明显的汇合处,则可先确定哪些数据流是逻辑输入和逻辑输出,从而获得主加工。
从物理输入端一步步向系统中间移动,直至到达这样一个数据流,它再不能被作为系统的输入,则其前一个数据流就是系统的逻辑输入,即离物理输入端最远的,但仍可视为是系统输入的那个数据流就是逻辑输入。
用类似的方法,从物理输出端一步步向系统中间移动,则离物理输出端最远的,但仍可视为系统输出的那个数据流就是逻辑输出。
逻辑输入和逻辑输出之间的加工就是要找的主加工。
2)设计顶层模块和第一层模块
首先在与主加工对应的位置上画出主模块,主模块的功能就是整个系统要做的工作,主模块称为主控制模块。主模块是模块结构图的“顶”,接着按“自顶向下,逐步细化”的思想来画模块结构图的各层。每一层均需按输入、变换、输出等分支来处理。模块结构图第一层的画法如下:
(1)为每一个逻辑输入画一个输入模块,其功能是向主模块提供数据;
(2)为每一个逻辑输出画一个输出模块,其功能是把主模块提供的数据输出;
(3)为主处理画一个变换模块,其功能是把逻辑输入变换成逻辑输出。
3)设计中、下层模块
因为输入模块的功能是向调用它的模块提供数据,所以它本身也需要一个数据来源。此外,输入模块必须向调用模块提供所需的数据,因此它应具有变换功能,能够将输入数据按模块的要求进行变换,再提交该调用模块,从而为每个输入模块设计两个下层模块,其中一个是输入模块,另一个是变换模块。
同理,也应为每个输出模块设计两个下层模块:一个是变换模块,将调用模块所提供的数据变换成输出的形式;另一个是输出模块,将变换后的数据输出。
该过程由顶向下递归进行,直到系统的物理输入端或物理输出端为止。每设计出一个新模块,应同时给它起一个能反映模块功能的名字。
4.事务分析
当数据流图呈现“束状”结构时,应采用事务分析的设计方法。就步骤而言,该方法与变换分析方法大部分类似,其主要差别在于由数据流图到模块结构的映射方式不同。
进行事务分析时,通常采用以下4步。
(1)确定以事务为中心的结构,包括找出事务中心和事务来源。以图4-9所示的典型事务型数据流结构为例进行说明。
(2)按功能划分事务,将具备相同功能的事务分为同一类,建立事务模块。
(3)为每个事务处理模块建立全部的操作层模块。其建立方法与变换分析方法类似,但事务处理模块可以共享某些操作模块。
(4)若有必要,则为操作层模块定义相应的细节模块,并尽可能使细节模块被多个操作模块共享。
总体设计过程
总体设计过程包括设计供选择的方案、推荐最佳方案、设计软件结构、制订测试计划、编写总体设计文档、审查与复查总体设计文档。
1.设计供选择的方案
软件分析员根据系统要求,提出并分析各种可能的方案,并且从中选出最佳的方案,为以后的工作做好准备。
需求分析阶段得出的数据流图是总体设计的根本出发点。数据流图中的处理可以进行逻辑分组,每一组都代表不同的实现策略。然后对这些分组得出的方案进行分析,产生一系列可供选择的方案。最后结合实际因素,如工程的目标、规模和用户的意见等,从可能的实现方案中选取若干个合理的方案。通常,选取的这些方案中应包括低成本、中成本和高成本几种方案。为每个方案需提供系统流程图、数据字典、成本效益分析、实现系统的进度计划。
2.推荐最佳方案
分析员从合理方案中选择一个最佳方案向用户推荐,并为推荐的方案制订详细的实现计划。
对于分析员推荐的最佳方案,用户和有关专家应该认真审查。如果确认该方案确实符合用户的需要,并且在现有条件下完全能够实现,则应该提请使用部门负责人进一步审批。在使用部门负责人也接受了分析员所推荐的方案之后,方可进入总体设计过程的下一步工作,即结构设计阶段。
3.设计软件结构
软件结构的设计,首先要把复杂的系统功能分解成简单的功能,即功能分解,同时进一步细化数据流图。分解后,分析员使用层次图或结构图来描述模块组织层的层次结构,实现由上层向下层的调用,最下层的模块完成具体的功能。
4.制订测试计划
在软件设计的早期阶段,考虑软件测试问题是非常必要的,有利于提高软件的可测试性。本书将在后面的章节中详细地介绍软件测试的有关内容。
5.编写总体设计文档
总体设计阶段结束时,应该提供以下相应的文档:
(1)总体设计说明书,包括系统实现方案和软件模块结构;
(2)测试计划,包括测试方案、策略、步骤和结果等;
(3)用户手册,根据总体设计阶段的结果对需求分析阶段的用户手册进行进一步的修改;
(4)详细的实现计划,包括系统目标、总体设计、数据设计、处理方式设计、运行设计和出错设计等。
6.审查与复审总体设计文档
对总体设计的结果要进行严格的技术审查,并在技术审查通过之后,使用部门负责人还要从管理的角度进行复审。
总体设计文档
总体设计说明书的编写完全遵循概要设计说明书文档的内容要求,其可参考的编写步骤如下:
①阐述编写概要设计说明书的目的,指明读者对象。
②阐明项目的背景,包括项目的委托单位、开发单位和主管部门。
③列出文档中用到的专门术语定义和缩写词的原文。
④列出文档中用到的参考资料,可包括项目经核准的计划任务书、合同或上级机关的批文、项目开发计划、文档所引用的资料,标准和规范等。
⑤列出系统需要的基本运行环境、条件与限制。
⑥列出总体设计的结果,包括处理流程、总体结构、功能分配等。
⑦列出接口设计的结果,包括用户界面、软件接口和硬件接口。
⑧列出数据设计的结果,包括逻辑结构设计和物理结构设计。
⑨列出其他设计的结果,包括出错、保密等。
总体设计说明书的步骤
总体设计说明书的编写完全遵循概要设计说明书文档的内容要求,其可参考的编写步骤如下:
①阐述编写概要设计说明书的目的,指明读者对象。
②阐明项目的背景,包括项目的委托单位、开发单位和主管部门。
③列出文档中用到的专门术语定义和缩写词的原文。
④列出文档中用到的参考资料,可包括项目经核准的计划任务书、合同或上级机关的批文、项目开发计划、文档所引用的资料,标准和规范等。
⑤列出系统需要的基本运行环境、条件与限制。
⑥列出总体设计的结果,包括处理流程、总体结构、功能分配等。
⑦列出接口设计的结果,包括用户界面、软件接口和硬件接口。
⑧列出数据设计的结果,包括逻辑结构设计和物理结构设计。
⑨列出其他设计的结果,包括出错、保密等。
总体设计说明书
GB8567-88概要设计说明书
一、引言
(一)编写目的
说明编写这份概要设计说明书的目的,指出预期的读者。
(二)背景
说明:
①待开发软件系统的名称。
②列出此项目的任务提出者、开发者、用户以及将运行该软件的计算站(中心)。
(三)定义
列出本文件中用到的专门术语的定义和外文首字母组词的原词组。
(四)参考资料
列出有关的参考文件,如:
①本项目的经核准的计划任务书或合同,上级机关的批文。
②属于本项目的其他已发表文件。
③本文件中各处引用的文件、资料,包括所要用到的软件开发标准。列出这些文件的标题、文件编号、发表日期和出版单位,说明能够得到这些文件资料的来源。
二、总体设计
(一)需求规定
说明对本系统的主要的输入输出项目、处理的功能性能要求。
(二)运行环境
简要地说明对本系统的运行环境(包括硬件环境和支持环境)的规定。
(三)基本设计概念和处理流程
说明本系统的基本设计概念和处理流程,尽量使用图表的形式。
(四)结构
用一览表及框图的形式说明本系统的系统元素(各层模块、子程序、公用程序等)的划分,扼要说明每个系统元素的标识符和功能,分层次地给出各元素之间的控制与被控制关系。
(五)功能需求与程序的关系
本条用一张如下的矩阵图说明各项功能需求的实现同各块程序的分配关系:
(六)人工处理过程
说明在本软件系统的工作过程中不得不包含的人工处理过程(如果有的话)。
(七)尚未解决的问题
说明在概要设计过程中尚未解决而设计者认为在系统完成之前必须解决的各个问题。
三、接口设计
(一)用户接口
说明将向用户提供的命令和它们的语法结构,以及软件的回答信息。
(二)外部接口
说明本系统同外界的所有接口的安排包括软件与硬件之间的接口、本系统与各支持软件之间的接口关系。
(三)内部接口
说明本系统之内的各个系统元素之间的接口的安排。
四、运行设计
(一)运行模块组合
说明对系统施加不同的外界运行控制时所引起的各种不同的运行模块组合,说明每种运行所历经的内部模块和支持软件。
(二)运行控制
说明每一种外界的运行控制的方式方法和操作步骤。
(三)运行时间
说明每种运行模块组合将占用各种资源的时间。
五、系统数据结构设计
(一)逻辑结构设计要点
给出本系统内所使用的每个数据结构的名称、标识符以及它们之中每个数据项、记录、文卷和系的标识、定义、长度及它们之间的层次或表格的相互关系。
(二)物理结构设计要点
给出本系统内所使用的每个数据结构中的各个数据项的存储要求,访问方法、存取单位、存取的物理关系(索引、设备、存储区域)、设计考虑和保密条件。
(三)数据结构与程序的关系
说明各个数据结构与访问这些数据结构的形式。
六、系统出错处理设计
(一)出错信息
用一览表的方式说明每种可能的出错或故障情况出现时,系统输出信息的形式、含义及处理方法。
(二)补救措施
说明故障出现后可能采取的变通措施,包括:
①后备技术说明准备采用的后备技术,当原始系统数据万一丢失时启用的副本的建立和启动的技术,例如,周期性地把磁盘信息记录到磁带上去就是对于磁盘媒体的一种后备技术。
②降效技术说明准备采用的后备技术,使用另一个效率稍低的系统或方法来求得所需结果的某些部分,例如,一个自动系统的降效技术可以是手工操作和数据的人工记录。
③恢复及再启动技术说明将使用的恢复再启动技术,使软件从故障点恢复执行或使软件从头开始重新运行的方法。
(三)系统维护设计
说明为了系统维护的方便而在程序内部设计中作出的安排,包括在程序中专门安排用于系统的检查与维护的检测点和专用模块。各个程序之间的对应关系,可采用如下的矩阵图的形式。
4、详细设计
详细设计的任务和原则
1.详细设计的任务
详细设计的目的是为软件结构图(SC图或HC图)中的每一个模块确定使用的算法和块内数据结构,并用某种选定的表达工具给出清晰的描述。这一阶段的主要任务如下:
(1)为每个模块确定所采用的算法,选择某种适当的工具表达算法的过程,写出模块的详细过程性描述;
(2)确定每一个模块使用的数据结构;
(3)确定模块接口的细节,包括对系统外部的接口和人-机界面,对系统内部其他模块的接口,以及模块输入数据、输出数据及局部数据的全部细节;
(4)在详细设计结束时,应该把上述结果写入详细设计说明书,并且通过复审形成正式文档,作为下一阶段(编码阶段)的工作依据。
要为每一个模块设计出一组测试用例,以便在编码阶段对模块代码(程序)进行预定的测试,模块的测试用例是软件测试计划的重要组成部分,通常应包括输入数据、期望输出等内容。
2.详细设计的原则
(1)由于详细设计的蓝图是供人阅读的,所以模块的逻辑描述要清晰易读、准确可靠。
(2)采用结构化设计方法,改善控制结构,降低程序的复杂程度,从而提高程序的可读性、可测试性和可维护性,其基本内容归纳为如下几点。
①程序语言中应尽量少用GOTO语句,以确保程序结构的独立性。
②使用单入口单出口的控制结构,确保程序的静态结构与动态执行情况相一致,保证程序易理解。
③程序的控制结构一般采用顺序、选择、循环等3种结构来构成,确保结构简单。
④用自顶向下逐步求精的方法完成程序设计。
⑤结构化程序设计虽然在存储容量和运行时间上增加了10%~20%,但易读、易维护性好。
⑥经典的控制结构为顺序,IF...THEN...ELSE分支,DO...WHILE循环。扩展的还有多分支CASE,DO...UNTIL循环结构。
(3)选择恰当描述工具来描述各模块的算法。
详细设计的工具
程序流程图
程序流程图(Program Flow Chart)也称为程序框图,是程序设计中应用最广泛的算法描述工具。程序流程图独立于各种程序设计语言,且直观、清晰,易于学习掌握。从20世纪40年代末到20世纪70年代中期,程序流程图一直是传统的软件工程方法的结构化设计技术所使用的经典设计工具。尽管程序流程图被广泛使用,但是其不够规范、符号不统一,且使用箭头过于灵活,容易导致流程图难以看懂,导致使用程序流程图的程序设计人员逐渐减少。
程序流程图一般包含三种基本元素:加工处理步骤、逻辑条件和控制流方向。
为了实现使用程序流程图描述结构化程序,必须限制程序流程图只使用以下五种基本控制结构。
为了使得流程图的使用更加规范化,很有必要对流程图所使用的符号作出确切的规定。国家标准《信息处理——数据流程图、程序流程图、系统流程图、程序网络图和系统资源图的文件编制符号及约定》(GB/T1526-1989)中对程序流程图的符号作出了规定。图1给出了由国际标准化组织提出的,并由中国国家技术监督局批准的在程序流程图中使用的部分标准符号。
图1 程序流程图的符号
程序流程图的主要缺点如下:
(1)程序流程图本质上不是逐步求精的好工具,它诱使程序员过早地考虑程序的控制流程,而不去考虑程序的全局结构。
(2)程序流程图中用箭头代表控制流,因此程序员不受任何约束,可以完全不顾结构程序设计的精神,随意转移控制。
(3)程序流程图不易表示数据结构。
这些缺点均构成流程图中的隐患。为防患于未然,在使用程序流程图时,应该注意以下几点:
①必须严格按结构程序设计方法的三种基本控制结构进行设计。
②严格控制箭头的任意转向。
③用其他描述工具(如IPO图)辅助描述每个模块的有关数据,以弥补流程图之不足。应该指出的是,详细的微观程序流程图——每个符号对应于源程序的一行代码,对于提高大型系统的可理解性作用甚微。
盒图
N-S图也被称为盒图,是1973年美国学者Nassi和Shneiderman提出的一种图形化描述工具。实际上N-S图是程序流程图的一个变种,N-S图去掉了程序流程图中的控制流线和箭头两种图形元素。对于程序中的每一个处理步骤,N-S图用一个盒子来表示,并规定程序流向只能从盒子的上端进入,从下端出来,并且再也没有其他出入口。N-S图的这种设计方式有效地排除了因任意使用控制转移对程序质量的影响,符合了结构化程序设计的原则,使得程序的结构更加良好。
与程序流程图的五种基本控制结构相对应,N-S图也有五种相应的表示形式。
N-S图的特点如下:
◇图形条理清晰,易于理解。
◇便于确定全局数据和局部数据的作用域。
◇有效地限制了随意地使用控制转移,符合结构化程序设计的原则。
◇易于表现模块的层次结构及其嵌套关系。
◇容易表示出递归结构。
当程序嵌套层次过多时,使用一个N-S图完全描述控制结构会使图形过于复杂,使用嵌套关系图形元素可以简化N-S图的结构。N-S图中的嵌套关系可以通过在盒子中多加一个椭圆来表示。该椭圆又代表另一个盒子,即N-S子图。
PAD图
PAD图(Problem Analysis Diagram)是由日本日立公司1973年提出的描述程序逻辑结构的图形工具,这种图形由程序流程图演化而来,它用二维树形结构图来表示程序的控制流,这样使得图形的描述更加地清晰、直观,将这种图翻译成程序代码也比较容易。
PAD图与前面介绍的两种程序流程图和N-S图一样,也分5种基本的图形结构。
PAD图的主要特点如下:
(1)PAD图对程序结构的描述更加清晰、直观、易懂。图中最左面的竖线是程序的主线,即第一层结构。随着程序层次的增加,图逐渐向右延伸,每增加一个层次,图形向右扩展一条竖线。PAD图中竖线的总条数就是程序的层次数。
(2)PAD采用自顶向下、逐步求精和结构化设计的原则,力求将模糊的问题的概念迈步转换成确定的、详尽的过程。
(3)PAD图是二维树形结构的图形,程序从图中最左竖线上端的结点开始执行,自上而下,从左向右顺序执行,经历所有结点。
(4)PAD图既可用于表示程序逻辑,也可用于描绘数据结构。
(5)很容易将PDA图转换成高级程序语言源程序,这种转换可由软件工具自动完成,从而可省去人工编码的工作,有利于提高软件可靠性和软件生产率。
伪代码
伪代码又称之为过程设计语言(Program Design Language,简称PDL),是一种软件设计描述语言,形式上不同于前面的几种图形表示方法,但同样能在详细设计阶段描述程序的逻辑结构。PDL于1975年由凯思(Caine.S)与戈登(K.Cordon)首先提出。PDL是一种“混杂”语言,它掺杂了某种结构化语言和自然语言。PDL在程序的总体框架上采用结构化语言,而其细节处理则用自然的说明性语言来代替,这样一来描述算法就显得更加自由灵活,且比较易于实现。PDL的结构和一般的程序类似,包括注释部分、数据说明部分和过程部分。到目前为止已推出多种PDL语言,但PDL与程序是完全不同的,它仅仅是算法的一种描述语言,它具有“非纯粹”的编程语言的特点。
PDL具有严格的语法结构,与编程语言控制结构的关键字较类似,如if then-else、while-do、repeat、until等,用来定义控制结构和数据结构;PDL的内层语法则是自由灵活的,可以采用自然语言描述。下面以基于任何一种比较通用的高级语言得到的PDL来说明它的基本结构。
PDL的基本构成:
1.数据说明
数据说明用以指明数据名的类型以及作用域,其形式如下:
declare<数据名>as<限定词>
其中,<数据名>为变量或常量名表,<限定词>为具体的数据结构,如基本数据类型(例如单精度数等)、数组、列表、集合、枚举、结构等。
2.子程序结构
procedure<子程序名>
interface<参数表>
<分程序(或PDL语句)>
return
end<子程序名>
3.分程序结构
begin<分程序名>
<PDL语句>
end<分程序名>
4.分支控制结构
条件分支:
if<条件>then
<PDL语句>
else
<PDL语句>
end if
当有多个条件复合时,可用else if结构:
if<条件>then
<PDL语句>
else if<条件>then
<PDL语句>
else
<PDL语句>
End if
多分支:
case<选择因子>of
<标号>,|<标号>|;<PDL语句>
…
[default]:<PDL语句>
end case
其中,<选择因子>是一组具有确定值的数据元素,这些值就是后面各行的<标号>。case结构根据<选择因子>的限值,执行相应<标号>所指的<PDL语句>。中括号表示标号可以有几个,标号和标号之间用逗号隔开。方括号表示其中的元素是任选的,可有可无。default为<选择因子>可能选取的最后一种情况。
5.循环控制结构
当型循环:
loop while<条件>
<PDL语句>
end loop
当条件为真时,执行循环体,否则结束循环。
直到型循环:
loop until<条件>
<PDL语句>
end if
当条件为真时,退出循环体,它的等价形式是:
loop until<条件>
<PDL语句>
exit when<条件>
end loop
6.输入输出结构
read
display
input
output
7.注释说明
可以用“--*”打头的注释行对语句进行注解说明,以提高可读性。
为了进一步加深理解,现以××系统主控制模块详细设计为例,说明如何用PDL来描述。
--*PDL示例*--
procedure函数名()
清屏;
显示××系统用户界面;
output(“请输入用户注册号:”);
input(password);
if password<>系统注册号
提示警告信息;
重新输入/退出运行
end if
显示本系统主菜单;
loop while(true)
input(用户选择XYZ);
if XYZ=“退出”
break;
end if
调用相关的模块完成用户选择功能;
end loop
清屏;
return
end函数名
通过上例可以看出PDL在用于描述过程时的总体结构与一般程序完全相同。外部语法同相应程序语言一致,内部语法使用自然语言,这样可以使过程的描述容易编写、容易理解也很容易转换成源程序。由此可见,PDL具有很强的描述功能,是一种十分灵活和有用的详细设计表达方法,它具有如下优点:
(1)用PDL写出的程序,既可以很抽象,又可以很具体。因此,符合“自上而下、逐步求精”的设计原则。
(2)PDL虽不是程序设计语言,但它非常类似于高级程序设计语言。PDL作为软件设计工具与具体使用哪种编程语言无关,只要对PDL描述稍加变换就可变成任意一种源程序代码(转换的难易程度有所区别)。因此它是详细设计阶段很受欢迎的表达工具。
(3)PDL描述同自然语言很接近,易于理解。
(4)PDL描述可以以注释形式嵌在程序中,成为程序的内部文档。这对提高程序的可读性是非常有益的。
(5)PDL描述与程序结构相似,因此比较容易自动产生各种相关开发程序,提高软件生产率。
PDL的缺点是不如图形描述形象直观,很容易使人陷入程序的具体细节中去,因此人们常常将PDL描述与具体的图形描述结合起来使用。
PDL具有下面具体特点:
(1)有固定的关键字、外部语法,提供全部结构化控制结构、数据说明和模块特征。为了使结构清晰和可读性强,通常在所有可能嵌套使用的控制结构的头和尾都有关键字,例如if…if(或endif)等。
(2)内部语法使用自然语言来描述处理特性,为开发者提供方便,提高可读性。有数据说明机制,包括简单的(如标志量和数组)与复杂的(如链表和层次结构)数据结构。
(3)有模块定义和调用的机制,用以表达各种方式的接口说明。PDL在描述程序的逻辑设计方面有它的独到之处,通常它具有以下特点:(1)有固定的关键字语法,用以描述程序总体控制结构、数据说明以及模块关系。
(2)加入了自然的说明性语言,用于描述各个处理细节,使得描述更加方便灵活。
(3)具备数据说明机制,能够描述简单的和复杂的数据结构。
(4)提供了子程序定义及其调用机制,对各种方式的接口进行描述。
PDL作为一种设计工具有如下一些优点:
(1)可以作为注释直接插在源程序中间。这样做能促使维护人员在修改程序代码的同时也相应地修改PDL注释,因此有助于保持文档和程序的一致性,提高了文档的质量。
(2)在书写和编辑PDL时,可以使用普通的正文编辑程序或文字处理系统方便地完成。
(3)可以使用已有的自动处理程序自动由PDL生成程序代码。
PDL的缺点是不如图形工具形象直观,因此人们常常将PDL描述与一种图形描述结合起来使用。
例如,查找拼错单词的程序。
PROCEDURE spellcheck IS #查找错拼的单词 BE-GIN
split document into single words #把整个文档分离成单词
lood up words in dictionary #在字典中查这些单词
display words which are not in dictionary #显示字典中查不到的单词
create a new dictionary #造一新字典
END spellcheck
人机界面设计
随着计算机硬件技术的迅猛发展,计算机的存储容量、运行速度和可靠性等技术性能指标有了显著的提高,使计算速度与存储容量不再成为软件开发人员所担心的问题,而软件的易用性就成为衡量一个应用软件质量高低的重要标准。软件的易用性主要取决于人机界面的优劣,因此软件能否提供一个轻松、愉快、感觉良好的人机界面影响软件产品的竞争力和寿命,针对这一点,软件设计人员应对用户界面设计给予足够的重视。人机界面研究已经从过去的从属地位上升为一个专门的领域,现在用户界面设计在系统软件设计中所占的比例越来越大,有时可能占总工作量的一半。用户界面设计,包括用户界面设计问题、界面设计过程、界面设计指南。
设计问题
评价一个人机界面设计质量的优劣目前还没有统一的标准。一般来说,主要考虑以下几个主要方面:
①用户对人机界面的满意程度。
②人机界面的标准化程度。
③人机界面的适应性和协调性。
④人机界面的应用条件。
⑤人机界面的性能价格比。
人们通常用“界面友好性”这一抽象概念来评价一个人机界面的好坏,一般认为一个友好的人机界面应该至少具备以下特征:
①操作简单,易学,易掌握。
②界面美观,操作舒适。
③快速反应,响应合理。
④用语通俗,语义一致。
综上所述,用户界面设计应考虑以下几个方面的问题:系统响应时间、用户帮助设施、出错信息处理和命令交互。
一、系统响应时间
系统响应时间包括两个方面:时间长度和时间的易变性。用户响应时间应该适中,系统响应时间过长,用户就会感到不安和沮丧,而响应时间过短有时会造成用户加快操作节奏,从而导致错误。系统响应时间的易变性是指相对于平均响应时间的偏差。即使响应时间比较长,低的响应时间易变性也有助于用户建立稳定的节奏。因此在系统响应时间上坚持如下原则:
从用户完成某个控制动作(如按回车键或单击鼠标)到软件给出预期的响应(输出或做动作)之间的时间,称为系统响应时间。响应时间有两个属性:长度和易变性。
1.长度
(1)响应时间过长,用户会不满意。
(2)响应时间过短,会迫使用户加快操作节奏,从而可能会犯错误。
2.易变性
(1)响应时间相对平均响应时间的偏差称为易变性。
(2)响应时间易变性小,有助于用户建立稳定的工作节奏。
(3)响应时间的易变性大,暗示系统工作出现异常。
合理设计系统功能的算法,使系统响应时间不要过长;如果系统响应时间过短,要适当地延时。总之,要根据用户的要求来调节系统响应时间。
二、用户帮助设施
常用的用户帮助设施有两种:集成的和附加的。集成的帮助设施一开始就是设计在软件中的,它与语境有关,用户可以直接选择与所要执行操作相关的主题。通过集成帮助设施可以缩短用户获得帮助的时间,增加界面的友好性。附加的帮助设施在系统建好以后再加进去的。通常是一种查询能力比较弱的联机帮助。
本系统提供这两种帮助设施,设计和实现时应遵循以下原则:
◇进行系统交互时,提供部分帮助功能,即:提供主要操作的帮助。
◇用户可以通过帮助菜单、F1键和帮助按钮(如果有的话)访问帮助。
◇在表示帮助时,根据需要提供三种方式的选择:另一个窗体、微帮助和指出参考某个文档。
◇用户如何回到正常交互方式有两种选择:返回键和功能键。
◇帮助信息的构造:采用分层式帮助。
◇微帮助提供:由状态栏提供,或控件上的提示文本。
几乎每个软件用户都需要帮助,用户帮助设施可使用户不离开用户界面就能解决问题。常见的帮助设施有两类。
1.集成的帮助设施
集成的帮助设施设计在软件里,对用户工作内容敏感,用户可以从与操作有关的主题中选择一个,请求帮助。集成的帮助设施可以缩短用户获得帮助的时间,增加界面的友好性。
2.附加的帮助设施
附加的帮助设施实际上是一种查询能力有限的联机用户手册。
集成的帮助设施优于附加的帮助设施。具体设计用户帮助时必须对以下问题进行选择:
①提供部分功能的帮助信息还是提供全部功能的帮助信息。
②请求帮助的方式:帮助菜单、特殊功能键或HELP命令。
③显示帮助信息的方式:独立窗口、指出参考某个文件或在屏幕的固定位置显示提示。
④返回正常交互的方式:屏幕上的返回按钮或功能键。
⑤帮助信息的组织方式:平面结构(所有信息都通过关键字访问)、层次结构或超文本结构。
设计必要的、简明的、合理有效的帮助信息,可以大力提高用户界面的友好性,使软件受到用户的欢迎。
三、出错信息处理
出错信息和警告是指出现问题时系统给出的坏消息,本系统对于出错信息和警告应该遵循以下原则:
①信息应以用户可以理解的术语描述。
②信息应提供如何从错误中恢复的建设性意见。
③信息应指出错误可能导致哪些不良后果,以便用户检查是否出现了这些情况或帮助用户进行改正。
④信息应伴随着视觉上的提示,如特殊的图像、颜色或信息闪烁。
⑤信息不能带有判断色彩,即任何情况下不能指责用户
出错信息和警告信息是出现问题时给出的消息。有效的出错信息能提高交互系统的质量,减少用户的挫折感。设计出错信息和警告信息应考虑以下问题。
①信息应以用户可理解的术语描述问题。
②信息应提供有助于从错误中恢复的建设性意见。
③信息应指出错误可能导致的负面后果(例如,破坏数据文件),以便用户检查是否出现了这些问题,并在问题出现时予以改正。
④信息应伴随听觉上或视觉上的提示,在显示信息时同时发出警告声或用闪烁方式显示,或用明显的颜色表示出错信息。
⑤信息不能指责用户。
四、命令交互
现在,面向窗口图形界面已减少了用户对命令执行的依赖。但还是有些用户偏爱用命令方式进行交互。在提供命令交互方式时,应考虑下列设计问题。
①每个菜单项都应有对应的命令。
②命令形式:控制序列、功能键或键入命令。
③考虑学习和记忆命令的难度,命令应当有提示。
④宏命令代表一个常用的命令序列。只需输入宏命令的标识符就可以按顺序执行它所代表的全部命令。
⑤所有应用软件都有一致的命令使用方法。
设计过程
用户界面设计过程是一个迭代的过程,一般步骤如下:
①先设计和实现用户界面原型。
②用户试用该原型,向设计者提出对界面的评价。
③设计者根据用户的意见修改设计并实现下一级原型。
④不断进行下去,直到用户满意为止。
人机界面设计指南
根据用户界面设计的基本原则,下面分别介绍三类用户界面的实用设计指南:一般交互、信息显示和数据输入。
一、一般交互
一般交互是指整体系统控制、信息显示和数据输入,下面这些设计指南是全局性的。
①保持一致性:菜单选择、命令输入、数据显示、其他功能要使用一致的格式。
②提供有意义的反馈:对用户的所有输入都应立即提供视觉、听觉的反馈,建立双向通信。
③要求确认:在执行有破坏性的动作之前要求用户确认。如,在执行删除、覆盖信息、终止运行等操作前时,提示“是否确实要……”。
④允许取消操作:应该能方便地取消已完成的操作。
⑤尽量减少记忆量。
⑥提高效率:人机对话、要求用户思考等要提高效率。尽量减少用户按键的次数,减少鼠标移动的距离,避免用户对交互操作产生疑惑。
⑦允许用户犯错误:系统应保护自己不受致命错误的破坏,用户出错后提示出错信息。
⑧按功能对动作分类:如采用分类分层次的下拉菜单,应尽力提高命令和动作的内聚性。
⑨提供帮助设施。
⑩命令名要简单:用简单动词或动词短语作为命令名。
二、信息显示
为了满足用户的需求,应用软件的用户界面所显示的信息应当完整、明确、易于理解、有意义。下面是关于信息显示的设计指南。
①用户界面显示的信息应简单、完整、清晰、含义明确。
②可用不同方式显示信息:用文字、图片、声音表示信息;按位置的移动、大小的不同、颜色、分辨率等区分信息。
③只显示与当前工作内容有关的信息。
④使用一致的标记、标准的缩写和适当的颜色。
⑤产生必要的出错信息。
⑥使用大小写、缩进和文本分组等方式帮助理解。
⑦使用模拟显示方式表示信息。例如,垂直方向的矩形表示温度、压力等的变化,颜色都有空间显示信息。屏幕大小应选择得当。
⑧高效率地使用显示屏,用窗口分隔不同类型的信息。使用多窗口时,应使每个窗口都有空间显示信息。屏幕大小应选择得当。
三、数据输入
在设计数据输入类的用户界面时,考虑到用户的很多时间都用在选择命令和向系统输入信息上,因而设计人员应做到以下几点:
①尽量减少用户的输入动作。可用鼠标从预定的一组输入中选择某一个,或在给定的值域中指定输入值,或利用宏命令把一次击键转变为复杂的输入数据集的过程。
②保持信息显示和数据输入的一致性。如文字大小、颜色、位置与输入一致。
③允许用户自己定义输入。如定义专用命令、警告信息、动作确认等。
④允许用户选择输入方式(如键盘、鼠标、扫描仪等)。
⑤使当前不适用的命令不可用。
⑥允许用户控制交互流程。跳过不必要的动作,改变工作的顺序,从错误状态中恢复正常。
⑦为所有的输入动作提供帮助。
⑧消除冗余的输入。利用程序可以自动获得或计算出来的信息不应要求用户提供。例如,当前日期、时间等计算机可以自动获得。又如,单位的职工信息管理系统中,姓名、性别、部门、职称等应当预先存放在数据库中,使用时只要输入职工工号,则可以立即通过程序调用数据库,显示出该职工工号所对应的姓名、性别、部门、职称等信息,不需要每次都输入这些信息。还有,整数后面不要求用户输入“.00”。
程序复杂度的定量度量
软件的质量属性与软件的复杂性是密切相关的,同时软件的复杂性也能从某些方面反映软件的可维护性和可靠性等质量要素。如何对详细设计阶段设计出的模块质量进行度量呢?前面章节介绍了模块设计质量的好与坏可以用内聚和耦合两个定性的指标来衡量。但是这种衡量毕竟只能是定性的,人们总希望能对软件的设计质量进行定量的度量。下面介绍两种应用广泛且比较成熟的程序复杂性定量度量方法:McCabe方法和Halstead方法,使用McCabe方法或Halstead方法对程序模块进行定量度量,可得出程序模块的复杂性程度。把程序的复杂性程度乘以适当的常数就可估算出软件中故障的数量,也能估算出软件开发所需用的工作量,同时定量度量的结果还可以用来比较不同设计方案的优劣,因此定量度量程序复杂性程度的方法具有实际应用价值。
McCabe方法
McCabe方法是根据程序流程图的结构复杂度对软件复杂度和质量进行度量。McCabe方法由T.J.McCabe于1976年提出的,McCabe把程序看成是有一个入口节点和一个出口节点的有向图,图中每个节点对应一个语句或一个顺序流程的程序代码块、弧对应于程序中的转移。这种图也称为程序流图。
程序流图实际上可以看成是一种简化了的程序流程图。在程序流图中,由于我们关心的只是程序的流程,而不关心各个处理框的细节,因此原来程序流程图中的各个处理框(包括语句框、判断框、输入/输出框等)都被简化为结点,一般用圆圈表示,而原来程序流程图中的带有箭头的控制流变成了程序流图中的有向边。从图论的观点看,程序流图是一个可表示为G=<N,E>的有向图。其中,N表示程序流图中的结点,而E表示程序流图中的有向边。
程序流图仅仅用于描绘程序内部的控制流程,而完全不反映对数据的具体操作以及分支和循环结构中的判断条件。由于程序流图舍弃了程序流程图中不需要的内容,从而使画面更加简洁,便于实现对程序复杂度的实际度量。程序流图可以通过简化程序流程图得到,也可以由PAD图或其他详细设计表达工具变换获得。用McCabe方法度量得出的结果称为程序的环形复杂度,它等于强连通的程序流图中线性无关的有向环的个数。图1表示把程序流程图映射成程序流图的方法,图2表示由PDL翻译成程序流图的方法。
图1 把程序流程图映射成程序流图
图2 PDL翻译成程序流图
当判定条件为复合条件时,生成程序流图的方法稍微复杂一些。所谓复合条件,就是在条件中包含了一个或多个布尔运算符(逻辑OR,AND,NAND,NOR)。在这种情况下,应该把复合条件分解为若干个简单条件,每个简单条件对应程序流图中一个结点。包含条件的结点称为判定节点,从每个判定结点引出两条或多条边。图3是由包含复合条件的PDL片断映射成的程序流图。
图3 由包含复合条件的PDL映射成的程序流图
McCabe方法的计算步骤分如下三步:
步骤1:将程序流程图退化成有向图,即将程序流程图的每个处理框退化成一个节点,将控制流箭头退化成连接各节点的有向弧。
步骤2:在有向图中,由程序出口到入口连接一条虚有向弧,使有向图达到强连通。
步骤3:计算V(G)=m-n+1
其中,V(G)是有向图G中的环数,m是有向图G中的弧数,n是有向图G中的节点数。
例如,图5.22中节点数为11,弧数为13,因此环形复杂度为:
V(G)=13-11+1=3
McCabe方法还有另外两种简单的方法计算环形复杂度。
(1)环形复杂度V(G)=P+1,其中,P是程序流图中判定结点的数目。
从图1中可看出P=2,故V(G)=2+1=3
(2)对于平面图,环形复杂度等于强连通的程序图在平面上围成的区域的个数。
如图1中有(b,c,d,j,b),(d,e,f,g,h,d)和(e,i,h,g,f,e)三个区域,即有V(G)=3。
McCabe的环形复杂度的度量方法非常清楚地表明了模块(或程序)的控制复杂度以及模块的规模。当程序中分支结构数和循环结构数增加时,控制结构图中的区域数就会增加,程序的结构会变得更复杂,V(G)的值也会相应地增大,同时模块测试难度也相应增加。其次,在结构化程序设计中,力求控制流从高层指向低层,如果出现从低层指向高层的流向,则会增加封闭区域的个数,于是,反方向的控制流向越多,程序结构越复杂,V(G)越大。McCabe研究大量程序后发现,程序的环形复杂度越高,程序的可理解性就越差,程序测试和维护的难度也就越大,环形复杂度高的程序往往是最困难、最容易出问题的程序。实践表明,模块规模以V(G)≤10为宜,也就是说,V(G)=10是模块规模的一个更科学、更精确的上限。
实践证明,McCabe度量方法存在一些缺点:
(1)不能区分不同种类的控制流的复杂性。
(2)简单IF语句与循环语句的复杂性相同。
(3)嵌套IF语句与简单CASE语句的复杂性相同。
(4)模块间接口当成一个简单分支一样处理。
(5)一个具有1000行的顺序程序与一行语句的复杂性相同。
Halstead方法
Halstead的软件科学理论由Halstead M.H于1977年提出,是研究最完全且最著名的一种软件复杂度的复合度量理论。Halstead方法也称为文本复杂性度量,它根据程序中运算符和操作数的总数来度量程序的复杂程度。运算符是通常语法中的像“+,-,>,<,if-then-else,while-do”等这样一些语法元素。操作数是指那些变量、常量等。至于注解、说明和其他的非执行语句并不计入在内。利用Halstead方法可以预测出程序长度、程序量并能预测出程序中可能包含的错误个数等。
设N1为程序中实际出现的运算符总个数,N2为程序中实际出现的操作数总个数,程序长度N定义为:N=N1+N2。
详细设计完成之后,可以知道程序中使用的不同运算符(包括关键字)的个数n1,以及不同操作数(变量和常数)的个数n2。Halstead给出预测程序长度的公式如下:
H=n1log2n1+n2log2n2
多次验证都表明,预测的长度H与实际长度N非常接近。
Halstead也给出了程序量的计算公式,设程序量为V,则:
V=Nlog2(n1+n2)
Halstead还给出了预测程序中包含错误的个数的公式:
E=N log2(n1+n2)/3000
例如,一个程序对75个数据库项共访问1300次,对150个运算符共使用了1200次,那么预测该程序的错误数B:
B=(1200+1300)*log2(75+150)/3000≈6.5
即预测该程序中可能包含6~7个错误。
在设计完成之后估算时,可以用预测的长度H近似代替N。有人曾对从3000条到12000条语句范围内的程序核实了上述公式,发现预测的错误数与实际错误数相比误差在8%之内。
Halstead度量方法也存在下列缺点。
(1)没有区别自己编的程序与别人编的程序。这是与实际经验相违背的。这时应将外部调用乘上一个大于1的常数Kf(应在1~5之间,它与文档资料的清晰度有关)。
(2)没有考虑非执行语句。补救办法:在统计n1、n2、N1、N2时,可以把非执行语句中出现的运算对象、运算符统计在内。
(3)在允许混合运算的语言中,每种运算符与它的运算对象相关。如果一种语言有整型、实型、双精度型三种不同类型的运算对象,则任何一种基本算术运算符(+、-、×、/)实际上代表了6种运算符。在计算时应考虑这种因数据类型而引起差异的情况。
(4)没有把不同类型的运算对象,运算符与不同的错误发生率联系起来,而是把它们同等看待。例如,对简单if语句与while语句就没有区别。
(5)没有注意调用的深度。Halstead公式应当对调用子程序的不同深度区别对待。在计算嵌套调用的运算符和运算对象时,应乘上一个调用深度因子。这样可以增大嵌套调用时的错误预测率。
(6)忽视了嵌套结构(嵌套的循环语句、嵌套if语句、括号结构等)。一般地,运算符的嵌套序列,总比具有相同数量的运算符和运算对象的非嵌套序列要复杂得多。解决的办法是对嵌套结果乘上一个嵌套因子。
详细设计文档
详细设计说明书的步骤
软件详细设计阶段,将生成详细设计说明书,为每个模块确定采用的算法,确定每个模块使用的数据结构,确定每个模块的接口细节。详细设计的目标有两个:实现模块功能的算法要在逻辑上正确和算法描述上简明易懂。在软件详细设计结束时,软件详细设计说明书通过复审的形成正式文档,作为下一个阶段的工作依据。编写详细设计说明书的步骤如下。
(1)确定每一个模块所用的算法,选择某种适当的工具表达算法的过程,写出模块的详细过程性描述。
(2)确定每一个模块使用的数据结构。
(3)确定模块接口的细节,包括对系统外部的接口和用户界面,对系统内部其他模块的接口,以及模块输入数据、输出数据及局部数据的全部细节。
(4)要为每一个模块设计出一组测试用例,以便在编码阶段对模块代码(即程序)进行预定的测试,模块的测试用例是软件测试计划的重要组成部分,通常应包括输入数据、期望输出等内容。
(5)在详细设计结束时,把上述结果写入详细设计说明书,并且通过复审形成正式文档,作为付给下一阶段(软件实现阶段)的工作依据。
详细设计说明书
详细设计说明书又称程序设计说明书。编制本说明书的目的,是为了说明一个软件系统各个层次中的每一个程序(每个模块或子程序)的设计思路,如实现算法、逻辑流程等。计算机软件产品开发文件编制指南(GB8567-88)中给出了编写详细设计说明书的参考格式,具体如下。
GB8567-88详细说明书参考格式
一、引言
(一)编写目的
说明编写这份详细设计说明书的目的,指出预期的读者。
(二)背景
说明:
①待开发软件系统的名称。
②本项目的任务提出者、开发者、用户和运行该程序系统的计算中心。
(三)定义
列出本文件中用到的专门术语的定义和外文首字母组词的原词组。
(四)参考资料
列出有关的参考资料,如:
①本项目的经核准的计划任务书或合同、上级机关的批文。
②属于本项目的其他已发表的文件。
③本文件中各处引用到的文件资料,包括所要用到的软件开发标准。列出这些文件的标题、文件编号、发表日期和出版单位,说明能够取得这些文件的来源。
二、程序系统的结构
用一系列图表列出本程序系统内的每个程序(包括每个模块和子程序)的名称、标识符和它们之间的层次结构关系。
三、程序1(标识符)设计说明
从本章开始,逐个地给出各个层次中的每个程序的设计考虑。以下给出的提纲是针对一般情况的。对于一个具体的模块,尤其是层次比较低的模块或子程序,其很多条目的内容往往与它所隶属的上一层模块的对应条目的内容相同,在这种情况下,只要简单地说明这一点即可。
(一)程序描述
给出对该程序的简要描述,主要说明安排设计本程序的目的意义,并且,还要说明本程序的特点(如是常驻内存还是非常驻内存?是否子程序?是可重人的还是不可重人的?有无覆盖要求?是顺序处理还是并发处理等)。
(二)功能
说明该程序应具有的功能,可采用IPO图(即输入一处理一输出图)的形式。
(三)性能
说明对该程序的全部性能要求,包括对精度、灵活性和时间特性的要求。
(四)输入项
给出对每一个输入项的特性,包括名称、标识、数据的类型和格式、数据值的有效范围、输入的方式。数量和频度、输入媒体、输入数据的来源和安全保密条件等等。
(五)输出项
给出对每一个输出项的特性,包括名称、标识、数据的类型和格式,数据值的有效范围,输出的形式、数量和频度,输出媒体、对输出图形及符号的说明、安全保密条件等等。
(六)算法
详细说明本程序所选用的算法、具体的计算公式和计算步骤。
(七)流程逻辑
用图表(例如流程图、判定表等)辅以必要的说明来表示本程序的逻辑流程。
(八)接口
用图的形式说明本程序所隶属的上一层模块及隶属于本程序的下一层模块、子程序,说明参数赋值和调用方式,说明与本程序相直接关联的数据结构(数据库、数据文卷)。
(九)存储分配
根据需要,说明本程序的存储分配。
(十)注释设计
说明准备在本程序中安排的注释,如:
①加在模块首部的注释。
②加在各分支点处的注释,对各变量的功能、范围、缺省条件等所加的注释。
③对使用的逻辑所加的注释等等。
(十一)限制条件
说明本程序运行中所受到的限制条件。
(十二)测试计划
说明对本程序进行单体测试的计划,包括对测试的技术要求、输入数据、预期结果、进度安排、人员职责、设备条件驱动程序及桩模块等的规定。
(十三)尚未解决的问题
说明在本程序的设计中尚未解决而设计者认为在软件完成之前应解决的问题。
5、编码
程序设计语言
程序设计语言是软件开发人员在编码阶段所使用的基本工具,程序设计语言所具有的特性不可避免地会影响编程者处理问题的方式和方法。为了能够编写出高效率、高质量的程序,根据具体问题和实际情况选择合适的程序设计语言是编码阶段中一项非常重要的工作。
一、程序设计语言的分类
随着计算机技术的发展,目前已经出现了数百种程序设计语言,但被广泛应用的只有几十种。由于不同种类的语言适用于不同的问题域和系统环境,因此了解程序设计语言的分类可以帮助我们选择出合适的语言。通常可将程序设计语言分为面向机器语言和高级语言两大类。
1.面向机器语言
面向机器语言包括机器语言(Machine Language)和汇编语言(Assemble Language)两种。
机器语言是计算机系统可以直接识别的程序设计语言。机器语言程序中的每一条语句实际上就是一条二进制形式的指令代码,由操作码和操作数两部分组成。由于机器语言难以记忆和使用,通常不用机器语言编写程序。汇编语言是一种符号语言,它采用了一定的助记符来替代机器语言中的指令和数据。汇编语言程序必须通过汇编系统翻译成机器语言程序,才能在计算机上运行。汇编语言与计算机硬件密切相关,其指令系统因机器型号的不同而不同。由于汇编语言生产效率低且可维护性差,所以目前软件开发中很少使用汇编语言。
但面向机器语言易于实现系统接口,运行效率高。一般在设计应用软件时,应当优先选择高级程序设计语言,只有下列三种情况选用面向机器语言进行编码:
①软件系统对程序执行时间和使用空间都有严格限制。
②系统硬件是特殊的微处理机,不能使用高级程序设计语言。
③大型系统中某一部分,由于其执行时间非常关键,或直接依赖于硬件,因此这部分用面向机器语言编写。其余部分用高级程序设计语言编写。
2.高级语言
高级语言中的语句标识符与人类的自然语言(英文)较为接近,并且采用了人们十分熟悉的十进制数据表示形式,利于学习和掌握。高级语言的抽象级别较高,不依赖于实现它的计算机硬件,且编码效率较高,往往一条高级语言的语句对应着若干条机器语言或汇编语言的指令。高级语言程序需要经过编译或解释之后,才能生成可在计算机上执行的机器语言程序。
高级语言按其应用特点的不同,可分为通用语言和专用语言两大类。
(1)通用语言。通用语言是指可用于解决各类问题、可广泛应用于各个领域的程序设计语言。从较早出现的基础语言BASIC、FORTRAN等,到后来出现的结构化语言Pascal、C等,再到现在被广泛使用的面向对象语言C++、Ja-va等都属于通用语言的范畴。
(2)专用语言。专用语言是为了解决某类特殊领域的问题而专门设计的具有独特语法形式的程序设计语言。如,专用于解决数组和向量计算问题的API语言;专用于开发编译程序和操作系统程序的BLISS语言;专用于处理人工智能领域问题的LISP语言和PROLOG语言等。这些语言的共同特点是可高效地解决本领域的各种问题,但难以应用于其他领域。
二、程序设计语言的选择
程序设计语言的选择将影响人们思考问题、解决问题的方式,影响软件的可靠性、可读性和可维护性。因此,选择一种适当的程序设计语言进行编码非常重要。在开发软件系统时,必须确定使用什么样的程序设计语言来实现这个系统。恰当的程序设计语言对成功实现从软件设计到编码的转换,提高软件质量,改善软件的可测试性和可维护性是极为重要的。为某个特定开发项目选择程序设计语言时,可以按照以下标准对程序语言进行比较选择,尽量同时兼顾理想标准和实用标准。
1.理想标准
(1)理想的模块化机制是易于阅读和使用的控制结构及数据结构。模块化、良好的控制结构和数据结构可以降低编码工作的难度,增强程序的可理解性,提高程序的可测试性和可维护性,从而减少软件生存周期中的总成本,并缩短软件开发所需的时间。
(2)完善、独立的编译机制。完善的编译系统可尽可能多地发现程序中的错误,便于程序的调试和提高软件的可靠性,并且可以使生成的目标代码紧凑、高效;独立的编译机制便于程序的开发、调试和维护,可以降低软件开发和维护的成本。
2.实用标准
(1)软件的应用领域。从应用领域角度考虑,各种语言都具有各自的特点和适合自己的应用领域。如,在事务处理方面COBOL和BASIC有较大优势;科学工程计算领域由于需要大量的标准库函数,来处理复杂的数值计算,所以常选择的语言有FORTRAN、Pascal、C等;在信息管理、数据库操作方面,可以选用COBOL、SQL、FoxPro、Oracle、Access或Delphi等语言;在系统软件开发方面,C语言占优势,汇编语言也常被使用;在实时系统中或很特殊的复杂算法、代码优化要求高的领域,可选用汇编语言、Ada语言或C语言;在网络编程应用中,选择Java语言较为合适;在人工智能领域,如知识库系统、专家系统、决策支持系统、推理工程、语言识别、模式识别、机器人视角、自然语言处理等,应选择PROLOG、LISP语言。只有充分考虑软件的应用领域,并熟悉当前使用较为流行的语言的特点和功能,程序员才能更好地发挥语言各自的功能优势,选择出最有利的语言工具。
(2)系统用户的要求。由于用户是软件的使用者,因此软件开发者应充分考虑用户对开发工具的要求,特别是当用户要负责软件的维护工作时,用户理所应当地会要求采用他们熟悉的语言进行编程。
(3)工程的规模。语言系统的选择与工程的规模有直接的关系。例如,FoxPro与Oracle及Sybase都是数据库处理系统,但FoxPro仅适用于解决小型数据库问题,而Oracle和Sybase则可用于解决大型数据库问题。特别是在工程的规模非常庞大,并且现有的语言都不能完全适用时,为了提高开发的效率和质量,就可以考虑为这个工程设计一种专用的程序设计语言。
(4)软件的运行环境。软件在提交给用户后,将在用户的机器上运行,在选择语言时应充分考虑到用户运行软件的环境对语言的约束。此外,在运行目标系统的环境中可以提供的编译程序往往也限制了可以选用的语言的范围。
(5)可以得到的软件开发工具。由于开发经费的制约,往往使开发人员无法任意选择、购买合适的正版开发系统软件。此外,若能选用具有支持该语言程序开发的软件工具的程序设计语言,则将有利于目标系统的实现和验证。
(6)软件开发人员的知识。软件开发人员采用自己熟悉的语言进行开发,可以充分运用积累的经验使开发的目标程序具有更高的质量和运行效率,并可以大大缩短编码阶段的时间。为了能够根据具体问题选择更合适的语言,软件开发人员应拓宽自己的知识面,多掌握几种程序设计语言。
(7)软件的可移植性要求。要使开发出的软件能适应于不同的软、硬件环境,应选择具有较好通用性的、标准化程度高的语言。
在实际选择语言时,往往任何一种语言都无法同时满足项目的所有需求和各种选择的标准,这时就需要编程者对各种需求和标准进行权衡,分清主次,在所有可用的语言中选取最合适的一种进行编程。
编码风格
在选择了合适的程序设计语言和保证程序正确的前提下,在根据详细设计结果编写程序时还应注意使用某些编程技巧,力求使程序具有良好的风格和较高的效率。
一、程序设计风格
程序设计风格或编码风格是指在不影响程序正确性和效率的前提下,有效编排和合理组织程序的基本原则。一个具有良好编码风格的程序主要表现为可读性好、易测试、易维护。由于测试和维护阶段的费用在软件开发总成本中所占比例很大,因此程序设计风格的好坏直接影响着整个软件开发中成本耗费的多少。特别是在需要团队合作开发大型软件的时候,程序设计风格显得尤为重要。若团队中的成员不注重自己的程序设计风格,则会严重影响与其他成员的合作和沟通,最终将可能导致软件质量上出现问题。
为了编写出可读性好、易测试、易维护且可靠性高的程序,软件开发人员必须重视程序设计风格,具体体现在以下几个方面。
1.源程序文档化
为提高源程序的可读性和可维护性,需要对源代码进行文档化,即在程序中加入说明性注释信息。在修改程序时,不要忘记对相应的注释也要进行修改。
程序中的注释一般可按其用途分为两类:序言性注释和功能性注释。
(1)序言性注释。序言性注释一般位于模块的首部,用于说明模块的相关信息。主要包括:对模块的功能、用途进行简要说明;对模块的界面进行描述,如调用语句的格式、各个参数的作用及需调用的下级模块的清单等;对模块的开发历史进行介绍,如模块编写者的资料、模块审核者的资料及建立、修改的时间等;对模块的输入数据或输出数据进行说明,如数据的格式、类型及含义等。
(2)功能性注释。功能性注释位于源程序模块内部,用于对某些难以理解的语句段的功能或某些重要的标识符的用途等进行说明。比如如果设计了一个复杂数据结构,应使用注释说明在实现时这个数据结构的特点。
通过在程序中加入恰当的功能性注释可以大大提高程序的可读性和可理解性,对语句的注释应紧跟在被说明语句之后书写。需要注意的是,并不是对所有程序中的语句都要进行注释,太多不必要的注释反而会影响人们对程序的阅读。
2.标识符的命名及说明
编写程序必然要使用标识符,用于定义模块名、变量名、常量名、函数名、程序名、过程名、数据区名、缓冲区名等,特别是对于大型程序,使用的标识符可能成千上万。由于对程序中的标识符作用的正确理解是读懂程序的前提,因此,若编程者随心所欲地进行标识符的命名和说明,可能就会给阅读程序带来麻烦。
为了便于阅读程序时对标识符作用进行正确的理解,标识符的命名应注意以下几个问题:
(1)选用具有实际含义的标识符,如用于存放年龄的变量最好命名为age,用于存放学生信息的数组最好命名为student。若标识符由多个单词构成,则每个单词的第一个字母最好采用大写或单词间用下划线分隔,以利于对标识符含义的理解。
(2)为了便于程序的输入,标识符的名字不宜过长,通常不要超过八个字符。特别是对于那些对标识符长度有限制的语言编译系统来说,取过长的标识符名没有任何的意义。
(3)为了便于区分,不同的标识符不要取过于相似的名字。如student和students,很容易在使用或阅读时产生混淆。
由于程序中通常需要使用大量不同类型的标识符,为了使说明部分阅读起来更加清晰,在对其进行类型说明时应注意以下几点:
(1)应按照某种顺序分别对各种类型的变量进行集中说明,如,先说明简单类型,再说明指针类型,然后说明记录类型;对简单类型的变量进行说明时,可先说明整型,再说明实型,然后说明字符型等等。
(2)在使用一个说明语句对同一类型的多个变量进行说明时,应按照变量名中的字母顺序(a~z)对其进行排列。
3.语句的构造及书写
语句是构成程序的基本单位,语句的构造方式和书写格式对程序的可读性具有非常重要的决定作用。
为了使程序中语句的功能更易于阅读和理解,构造语句时应该注意以下几个问题:
(1)语句应简单直接,避免使用华而不实的程序设计技巧。
(2)对复杂的表达式应加上必要的括号使表达更加清晰。
(3)由于人的一般思维方式对逻辑非运算不太适应,因此在条件表达式中应尽量不使用否定的逻辑表示。
(4)为了不破坏结构化程序设计中结构的清晰性,在程序中应尽量不使用强制转移语句GOTO。
(5)为了便于程序的理解,不要书写太复杂的条件,嵌套的重数也不宜过多。
(6)为了缩短程序的代码,在程序中应尽可能地使用编译系统提供的标准函数。对于程序中需要重复出现的代码段,应将其定义成独立模块(函数或过程)实现。
为了便于人们对程序的阅读,清晰整齐的书写格式是必不可少的。以下列出了书写程序时需注意的几个主要格式问题:
(1)虽然许多语言都允许在一行上书写多个语句,但为了程序看起来更加清楚,最好在一行上只书写一条语句。
(2)在书写语句时,应采用分层缩进的格式使程序的层次更加清晰。
(3)在模块之间通过加入空行进行分隔。
(4)为了便于区分程序中的注释,最好在注释段的周围加上边框。
4.输入和输出
由于输入和输出是用户与程序之间进行交互的渠道,因此输入、输出的方式往往是用户衡量程序好坏的重要指标。为了使程序的输入、输出能便于用户的使用,在编写程序时应对输入和输出的设计格外注意。
在运行程序时,原始数据的输入工作通常要由用户自己完成。为了使用户能方便地进行数据的输入,应注意以下几点:
(1)输入方式应力求简单,尽可能减少用户的输入量。当程序中对输入数据的格式有严格规定时,同一程序中的输入格式应尽可能保持一致。
(2)交互式输入数据时应有必要的提示信息,提示信息可包括:输入请求、数据的格式及可选范围等。同时,在数据输入的过程中和输入结束时,也应在屏幕上给出相应的状态信息。
(3)程序应对输入数据的合法性进行检查。若用户输入了非法的数据,则应向用户输出相应的提示信息,并允许用户重新输入正确的信息。例如,除法操作的除数不能为0,当用户输入0时,应输出出错提示并允许用户再次输入。
(4)若用户输入某些数据后可能会产生严重后果,应给用户输出必要的提示并在必要的时候要求用户确认。
(5)当需要输入一批数据时,不要以记数方式控制数据的输入个数,而应以特殊标记作为数据输入结束的标志。例如,要输入一个班学生的成绩,若要求用户输入学生的总数并通过总数来控制输入数据的个数,无疑就会增加用户的麻烦;而若以特殊标记来控制数据的录入,如当用户输入-l时结束输入,对于用户而言就方便多了。
(6)应根据系统的特点和用户的习惯设计出令用户满意的输入方式。
用户需要通过程序的输出来获取处理结果。为了使用户能够清楚地看到需要的结果,设计数据输出方式时应注意以下几点:
第一,输出数据的格式应清晰、美观。如对大量数据采用表格的形式输出,可以使用户一目了然。
第二,输出数据时要加上必要的提示信息。例如,表格的输出一定要带有表头,用以说明表格中各项数据的含义。
编码工具及环境
为了提高编码的效率,保证程序的可靠性,我们经常使用一些编码工具。
首先要用的当然是编辑工具了。选用合适的编辑工具可以大大方便编程,提高效率。编译程序的好坏也会影响编码的效率。一方面,好的编译程序应该是程序员的好助手,能够帮助程序员及时准确地诊断出程序中的差错,减少程序开发的成本。另一方面,编译程序还应该能够生成高效率的机器代码,也就是代码优化。
现在的软件系统往往是集体开发,一个大的软件系统往往包含许多模块,这些不同的模块可能分散在几个不同的文件或库里。为了得到最终的可执行代码,必须先将各个模块分别进行编译,然后再进行连接。由于模块的数量很多,而且这些模块往往都是相互影响和制约的,如果某个模块的源代码改变了,那么受此模块影响的所有其他模块都必须进行再编译、再连接。我们可以借助一些工具来完成这项工作。如UNIX的MAKE工具。
利用MAKE程序能保持模块间的协调关系。程序员将程序不同模块之间的依赖关系以及更新模块时必须进行的操作告诉MAKE程序。这样,MAKE程序就能够自动检索出那些过时了的模块、需要进行再编译的模块,并对所发现的过时模块执行说明信息中规定的更新操作,从而使目标文件永远保持最新的版本。
编译器提供者通常会提供程序的支持环境,也就是编写程序所使用的集成开发环境(Integrated Development Environment,简称IDE)。程序设计支持环境(Programming Support Environment,简称PSE)完成程序编辑、编译、调试、配置管理、项目管理等一组任务。好的PSE应该具有如下的特性:
(1)通用性。适用于不同的语言、不同的应用领域和开发方法。
(2)适应性。通过配置PSE,可以满足不同用户对界面和操作习惯的要求。
(3)开放性。能方便地增加新工具。
(4)支持复用。能支持可复用组件的查询、存储和使用。
(5)自控性。保证工具间的信息的一致性和完整性。
(6)自带数据库。提供数据库用于管理已开发的软件产品。
(7)保证质量。有助于提高所开发软件的质量。
(8)有大量的用户。
(9)有竞争力。PSE能有效地提高软件的生产率。
表现优秀的微软提供的Visual Studio.NET集成了上述全部的特性,该环境不仅包含了开发软件所需的全部工具,还包括项目安装部署工具,该工具有如下特点:
(1)通用性。Visual Studio.NET可以使用的语言包括C、C++、C#、Visual Basic.NET等,它不仅支持面向对象的开发方法,还支持面向过程的开发方法。
(2)适应性。Visual Studio.NET可以提供多种界面和操作风格以迎合具有不同编程背景的程序员。
(3)开放性。Visual Studio.NET提供了开放工具的方法。
(4)支持复用。Visual Studio.NET对组件的操作提供了完全的支持。
另外,Visual Studio.NET还内置了源代码控制工具,用于小组开发中源代码版本的控制等。
总之,现代软件编码的质量很大程度上与编码工具及相应的环境有关。所以,选择工具和环境是至关重要的。
6、软件测试
基本概念
一、软件测试的发展历史及趋势
Edward Kit在他的《Software Testing In The Real world:Improving The Process》(1995,ISBN:0201877562)一书中将整个软件测试的发展历史分为三个阶段:
第一个阶段是20世纪60年代及其以前,那时软件规模都很小、复杂程度低,软件开发的过程随意。开发人员发现Debug的过程被认为是唯一的测试活动。其实这并不是现代意义上的软件测试,而只是排查软件错误的调试过程,当然这一阶段也还没有专门的测试人员出现。
第二个阶段是20世纪70年代,这个阶段开发的软件仍然不复杂,但人们已开始思考开发流程问题,并提出软件工程的概念。但是这一阶段人们对软件测试的理解仅限于基本的功能验证和Bug搜寻,而且测试活动仅出现在整个软件开发流程的后期,虽然测试由专门的测试人员来承担,但测试人员都是行业和软件专业的入门新手。
第三个阶段是20世纪80年代及其以后,软件和IT行业进入了大发展时期,软件趋向大型化。与之相应,人们为软件开发设计了各种复杂而精密的流程和管理方法(比如CMM和MSF),并将“质量”的概念融入其中。软件测试已有了行业标准(IEEE/ANSI),它再也不是一个一次性的,而且只是开发后期的活动,而是与整个开发流程融合成一体。软件测试已成为一个专业,需要运用专门的方法和手段,需要专门人才和专家来承担。
在这一历史发展过程中,最值得注意的是测试与开发流程融合的趋势。比如测试活动的早期开展,让测试人员参与用户需求的验证,参加功能设计和实施设计的审核。再如测试人员与开发人员的密切合作,随着开发进展而逐步实施单元测试、模块功能测试和系统整合测试。的确,这些都是测试与开发融合的表现形式,而且初期的融合也只反映在这个层次上。20世纪90年代以后,软件的规模和复杂程度迅速提高,这种形式上的融合也迅速走向更深层次,更具实际意义。具体地说,这种融合就是整个软件开发活动对测试的依赖性。传统上认为,只有软件的质量控制依赖于测试,但是现代软件开发的实践证明,不仅软件的质量控制依赖于测试,开发本身离开测试也将无法推进,项目管理离开了测试也从根本上失去了依据。
二、有关软件测试的一些基本术语
1.软件测试
不同时期关于软件测试(Software Testing)的定义有:
①为了发现错误而执行程序的过程。
②确认程序做了它应该做的事情。
③确认程序正确实现了所要求的功能。
④以评价程序或系统的属性、功能为目的的活动。
⑤对软件质量的度量。
⑥验证系统满足要求,或确认实际结果与预期结果之间的差别。
定义①强调寻找错误是软件测试的目的,定义②③侧重于用户满意程度,定义④⑤强调评估软件质量,而定义⑥则将重点放在预期结果上。这些对软件测试的定义,只描述了其中的一个或几个方面的内容,并没有全面地描述软件测试。
关于软件测试的标准定义如下:
软件测试是使用人工或自动手段来运行或测试某个系统的过程,其目的在于检验它是否满足规定的需求或是弄清预期结果与实际结果之间的差别。
该定义是1983年IEEE(国际电子电气工程师协会)提出的软件工程标准术语中给软件测试下的定义,比较全面地反映了软件测试的重点与难点。
2.软件缺陷
对于软件存在的各种问题,我们都用“软件缺陷”这个术语,英文中用来描述软件缺陷的单词很多,比如Bug,Defect,Fault,Error,Failure,Incident,Va-riance等,不同的公司对其中的部分术语做了严格的定义,不过我们习惯统一使用Bug来描述软件缺陷。
关于软件缺陷的标准定义如下:
从产品内部看,软件缺陷是软件产品开发或维护过程中所存在的错误、毛病等各种问题;从外部看,软件缺陷是系统所需实现的某种功能的失效或违背。
该定义是IEEE软件工程(1983)的标准术语,比较全面,但缺乏指导性。Ron Patton从软件行业的角度,给出了以下五条规则作为软件缺陷的具体表现:
①软件未达到需求规格说明书中指明的功能。
②软件出现了需求规格说明书中指明不会出现的错误。
③软件功能超出了需求规格说明书中指明的范围。
④软件未达到需求规格说明书中虽未指出但应达到的目标。
⑤软件测试员认为软件难以理解、不易使用、运行速度缓慢,或者最终用户认为不好。
3.测试用例
测试用例(Testing Case)简单地来讲是指执行条件和预期结果的集合,完整地来说是针对要测试的内容所确定的一组输入信息,是为达到最佳的测试效果或高效地揭露隐藏的错误而精心设计的少量测试数据。
IEEE Standard 610(1990)给出的定义为:测试用例是一组测试输入、执行条件和预期结果的集合,目的是要满足一个特定的目标,比如执行一条特定的程序路径或检验是否符合一个特定的需求。
从以上定义来看,测试用例设计的核心有两方面,一是要测试的内容,即与测试用例相对应的测试需求;二是输入信息和对应的预期结果,即按照怎样的操作步骤,对系统输入哪些必要的数据,系统应该有哪些相应的响应。测试用例设计的难点在于如何通过少量测试数据来有效揭示软件缺陷。
三、软件测试的重要性
在软件业较发达的国家,软件测试不仅早已成为软件开发的一个有机组成部分,而且在整个软件开发的系统工程中占据着相当大的比重。统计表明,软件测试的工作量通常占软件开发总工作量的40%以上,开发费用的近l/2用在软件测试上,对于一些与人的生命安全相关的软件,如飞行控制、核反应堆监控软件等,其测试费用可能相当于软件工程其他步骤总成本的3~5倍。由此可见,软件测试在软件开发中是非常重要的一个过程,具体体现在以下几个方面。
1.寻找软件错误,以便进行修正
这是保证软件质量的重要一环,也是测试人员需要持续不断地做的工作。特别是通过回归测试可以防止无意识的行为引入一些将来可能出现的错误。
2.证明软件符合要求,是可用的
这主要是指从开发方的角度出发,希望通过测试向用户证明,其所开发的软件正确地实现了用户需求,树立用户对软件质量的信心,为用户选择与接受软件提供有利的依据。这主要是指α测试。
3.验证软件是否符合用户要求
这主要是指从用户的角度出发,他们希望通过测试来核实开发方交付的软件是否符合自己的真实需求,系统运行在真实的应用环境下是否存在关键功能或关键性能上的缺陷。这主要是指β测试。
4.指导软件的开发过程
从宏观上看,不同的软件开发过程,有不同的测试模型与之对应,以此能够更好地指导开发与测试实践。从微观上看,测试可以保证对需求和设计的理解与表达的正确性、实现的正确性以及运行的正确性。通过分析缺陷的分布和产生原因可以帮助发现当前软件开发的缺陷,有助于过程改进;通过对测试结果的分析整理,可以进一步修正软件的开发规则。
5.提供软件的相关特征
软件测试是对软件质量的度量与评估。GB/T6583-ISO8404(1994版)将质量定义为“反映实体满足明确的和隐含的需要的能力的特性的总和”。测试可以提供测试数据及结果来对软件质量进行细化和量化,并为软件的可靠性分析提供依据。
四、软件测试的分类
1.按照是否需要查看代码分类
按照是否需要查看代码可将软件测试分为黑盒测试和白盒测试。
黑盒测试(Black-box Testing)是将被测软件看成一个黑盒子,只考虑系统的输入和输出,完全不考虑程序内部的逻辑结构和处理过程。黑盒测试的依据是开发各阶段的需求规格说明。
白盒测试(White-box Testing)是将黑盒子打开,研究源代码和程序内部的逻辑结构。白盒测试的依据是程序源代码。
2.按照是否需要执行被测软件分类
按照是否需要执行被测软件可将软件测试分为静态测试和动态测试。
静态测试(Static Testing)又称静态分析,是不实际运行被测软件,而是直接分析软件的形式和结构,查找缺陷。主要包括对源代码、程序界面和各类文档及中间产品(如产品规格说明书、技术设计文档等)所做的测试。
动态测试(Dynamic Testing)又称动态分析,是指需要实际运行被测软件,通过观察程序运行时所表现出来的状态、行为等发现软件缺陷,包括在程序运行时,通过有效的测试用例来分析被测程序的运行情况或进行跟踪对比,发现程序所表现的行为与设计规格或客户需求不一致的地方。
3.按照测试阶段分类
按照测试的阶段可将软件测试分为单元测试、集成测试、确认测试、系统测试、验收测试等。
单元测试(Unit Testing)又称模块测试,是指对软件中的最小可测试单元进行测试,目的是检查每个单元是否能够正确实现详细设计说明中的功能、性能、接口和设计约束等要求,发现各个模块内部可能存在的各种缺陷。
集成测试(Integration Testing)又称组装测试,是在单元测试的基础上,按照设计要求,将通过单元测试的单元组装成系统或子系统而进行的有序的测试,目的是检验不同程序单元或部件之间的接口关系是否符合概要设计的要求,能否正常运行。
确认测试又称有效性测试,此时经过集成测试后,单元模块组装成为完整的软件系统,消除了接口的错误。主要由使用用户参加测试,检查软件能否按合同要求进行工作,即是否满足软件需求说明书中的确认标准,是保证软件质量的关键环节。
系统测试(System Testing)是为了验证系统是否达到其原始目标,而对集成的硬件和软件系统进行的测试,是在真实或模拟系统运行的环境下,检查完整的程序系统是否能和系统(包括硬件、外设、网络和系统软件、支持平台等)正确配置、连接,并满足用户需求。
验收测试(Acceptance Testing)又称接受测试,是一种正式的测试,以用户测试为主,或由测试人员等质量保证人员共同参与的测试,是一般由用户、客户或其他权威机构来决定是否可以接受一个产品(系统或组件)的测试过程。验收测试是软件正式交付给用户使用的最后一个测试环节,并决定用户是否最终验收签字和结清所有应付款。
4.按测试执行时是否需要人工干预分类
按照测试执行时是否需要人工干预可将软件测试分为手工测试和自动测试。
手工测试是完全由人工完成测试工作,包括测试计划的制订,测试用例的设计和执行,以及测试结果的检查和分析等。传统的测试工作都是由人工来完成的。
自动测试是指各种测试活动的管理和实施,都是使用自动化测试工具或自动化测试脚本来进行的测试,包括测试脚本的开发与执行等,以某种自动测试工具来验证测试需求。这类测试在执行过程中一般不需要人工干预,通常在功能测试、回归测试和性能测试中使用较为广泛。
5.其他测试类型
其他重要的测试类型包括冒烟测试、随机测试、回归测试和基线测试等。限于篇幅原因,请大家自行查找相关介绍资料。
五、软件测试的基本原则
人们为了提高测试的效率,在长期测试实验中积累了不少经验,下面列出了人们在实践中总结的主要基本原则:
1.尽早地并不断地进行软件测试
实际问题的复杂性、软件本身的复杂性与抽象性以及开发期间各层人员工作的配合关系等各种错综复杂的因素使得软件开发的各个阶段都可能存在错误及潜在的缺陷。所以,软件开发的各阶段都应当进行测试。错误发现得越早,后阶段耗费的人力、财力就越少,软件质量相对就高一些。
2.程序员或程序设计机构应避免测试自己设计的程序
测试是为了找错,而程序员大多对自己所编的程序存有偏见,总认为自己编的程序问题不大或无错误存在,因此很难查出错误。此外,设计机构在测试自己程序时,由于开发周期和经费等问题的限制,要采用客观的态度是十分困难的。从工作效率来讲,最好由与原程序无关的程序员和程序设计机构或者专门的测试人员进行测试。
3.测试用例中不仅要有输入数据,还要有与之对应的预期结果
测试前应当设定合理的测试用例。测试用例不仅要有输入数据,而且还要有与之对应的预期结果。如果在程序执行前无法确定预期的测试结果,由于人们的心理作用,可能把实际上是错误的结果当成是正确的。
4.测试用例的设计不仅要有合法的输入数据,还要有非法的输入数据
在设计测试用例时,不仅要有合法的输入测试用例,还要有非法的输入测试用例。在测试程序时,人们常忽视不合法的和预想不到的输入条件,倾向于考虑合法的和预期的输入条件。而在软件的实际使用过程中,由于各种因素的存在,用户可能会使用一些非法的输入,比如,常会按错键或使用不合法的命令。对于一个功能较完善的软件来说,不仅当输入是合法的时候能正确运行,而且当有非法输入时,也应当能对非法的输入拒绝接受,同时给出对应的提示信息,使得软件便于使用。
5.在对程序修改之后要进行回归测试
在修改程序的同时时常又会引起新的缺陷,因而在对程序修改完之后,还应使用以前的测试用例进行回归测试,有助于发现因修改程序而引起的新的缺陷。
6.程序中尚未发现的错误的数量通常与该程序中已发现的错误的数量成正比
经验表明:一段程序中若发现错误的数目越多,则此段程序中残存的错误数目也较多。例如,在美国的IBM/370的一个操作系统中,47%的错误(由用户发现的错误)仅与该系统的4%的程序模块有关。据此规律,在实际测验时,为了提高测试效率,要花较多的时间和代价来测试那些容易出错及出错多的程序段。而不要以为找到了几个错误,就认为问题已解决,不再需要继续测试了。
7.妥善保留测试计划、全部测试用例、出错统计和最终分析报告,并把它们作为软件的组成部分之一,为维护提供方便
设计测试用例要耗费相当大的工作量,若测试完随意丢弃,以后一旦程序改错后需重新测试时,将重复设计测试用例,这会造成很大的浪费,因而妥善保留与测试有关的资料,能为后期的维护工作带来方便。
8.应当对每一个测试结果做全面检查
这条重要的原则时常被人们忽视。不仔细、全面地检查测试结果,就会使得有错误征兆的输出结果被漏掉。
9.严格执行测试计划,排除测试的随意性
测试计划内容应包括:所测软件的功能、输入和输出、测试内容、各项测试的进度安排、资源要求、测试资料、测试工具、测试用例的选择、测试的控制方式和过程、系统组装方式、跟踪规程、调试规程、回归测试的规定以及评价标准等。
白盒测试技术
白盒测试,又称结构测试或逻辑驱动测试,是一种透明的测试技术,它是以程序的内部逻辑结构为基础来设计测试用例的。其原则是:
①保证程序中每一独立的路径至少执行一次。
②保证所有判定的每一个分支至少执行一次。
③保证每个判定表达式中每个条件的所有可能结果至少出现一次。
④保证每一循环都在边界条件和一般条件至少各执行一次。
⑤验证所有内部数据结构的有效性。
下面介绍逻辑覆盖、基本路径、循环覆盖等几种典型的白盒测试技术。
一、逻辑覆盖
逻辑覆盖是指设计测试用例对程序的内部分支逻辑结构进行部分或全部覆盖的技术,这里以图1给出的逻辑结构为例由弱到强介绍几种逻辑覆盖技术:
图1 程序流程图
1.语句覆盖
语句覆盖是指设计足够多的测试用例,使程序中的每条语句至少执行一次。
在图1所示的流程图中,如果令x=2,y=0,z=4作为测试数据,程序执行路径为abcde,使语句段1和2各执行一次,实现了语句覆盖。
但是这组数据只测试了判定表达式取真的情况,若实际输入的条件为假时,有错误则显然测试不出来。事实上,语句覆盖对程序的逻辑覆盖很少,语句覆盖只关心判定表达式的值,而没有分别测试判定表达式取不同值的情况。在上例中,为了执行abcde路径以测试每条语句,只需两个判定表达式(x>1)AND(y=0)和(x=2)OR(z>1)都取真值,上述测试数据足够满足要求。但是,若程序中第一个判断表达式中的逻辑运算符“AND”错写成“OR”,或把第二个判定表达式中的条件“z>1”误写成“z<l”,则上述测试数据就无法检测出来了。与后面所介绍的其他覆盖相比,语句覆盖是最弱的逻辑覆盖准则。
2.判定覆盖
判定覆盖是指设计足够多的测试用例,使每个判定的每种可能结果都至少出现一次,也就是使每个判定的每个分支都至少执行一次。因此,判定覆盖也称为分支覆盖。
考察图1,只要设计测试用例通过abcde和ace,或者通过abce和acde,就能满足判定覆盖。比如,可以设计如下两组数据以满足判定覆盖:
①x=3,y=0,z=l(执行路径abce)
②x=2,y=l,z=2(执行路径acde)
判定覆盖由于各个分支都要通过,则各个分支的语句也都测试了,因此,判定覆盖必然满足语句覆盖。但是,判定覆盖的覆盖程度仍然不高。比如,错把z>l写成了z<l,则上述测试用例仍无法检测出来,因为它只覆盖了全部路径的一半。可见判定覆盖仍然很弱,但比语句覆盖强。
3.条件覆盖
条件覆盖是指设计足够多的测试用例,使每个判定表达式中的每个条件的每种可能值都至少出现一次。
仍以图1的程序为例,其中共有4个条件:x>1、y=0、x=2、z>l。
条件覆盖要求设计测试用例覆盖第一个判定表达式的x>1,x≤1,y=0,y≠0等各种情况,并覆盖第二个判定表达式的x=2,x≠2,z>l,z≤1等各种情况。设计如下两组测试数据,就可以满足上述条件覆盖的标准:
①x=2,y=0,z=3(覆盖x>l,y=0,x=2,z>l,执行路径abcde)
②x=l,y=l,z=l(覆盖x≤1,y≠0,x≠2,z≤1,执行路径ace)
条件覆盖一般比判定覆盖强,因为条件覆盖使判定表达式中每个条件都取到了两个不同的结果,判定覆盖却只关心整个判定表达式的值。上述两组测试数据也同时满足判定覆盖标准。但是,也可能有相反情况:虽然每个条件都取到了两个不同的结果,判定表达式却始终只取一个值。例如,若使用以下两组测试数据,则只满足条件覆盖标准,却并不满足判定覆盖标准:
①x=2,y=0,z=1(覆盖x>l,y=0,x=2,z≤l,执行路径abcde)
②x=1,y=l,z=2(覆盖x≤1,y≠0,x≠2,z>l,执行路径acde)
上述测试数据的第二个判定表达式的值总为真,不满足判定覆盖的要求,为解决这一矛盾,需要对条件和分支兼顾。
4.判定/条件覆盖
既然判定覆盖和条件覆盖互相不一定包含,于是提出了一种同时满足这两种覆盖的逻辑覆盖,那就是判定/条件覆盖。它是指设计足够多的测试用例,使得判定表达式中的每个条件都取到所有可能的值,并使每个判定表达式也都取到所有可能的判定结果。仍以图1的程序为例,设计以下两组测试数据:
①x=2,y=0,z=3(覆盖x>1,y=0,x=2,z>l,执行路径abcde)
②x=l,y=l,z=l(覆盖x≤1,y≠0,x≠2,z≤l,执行路径ace)
该组测试数据既满足判定覆盖又满足条件覆盖,所以满足判定/条件覆盖。其实,该组测试用例也就是上述条件覆盖的第一个例子。
判定/条件覆盖也有缺陷。从表面上来看,它测试了所有条件的取值。但实际并不是这样。因为一些条件往往掩盖了另一些条件。对于条件表达式(x>1)AND(y=0)来说,只有(x>1)的测试为真,才需测试(y=0)的值来确定此表达式的值,但是若(x>1)的测试值为假时,不需再测(y=0)的值就可确定此表达式的值为假,因而(y=0)没有被检查。同理,对于(x=2)OR(z>1)这个表达式来说,只要(x=2)测试结果为真,不必测试(z>1)的结果就可确定表达式的值为真。所以,对于判定/条件覆盖来说,逻辑表达式中的错误不一定能够查得出来。
5.条件组合覆盖
条件组合覆盖是指设计足够多的测试用例,使得每个判定表达式中条件的各种可能值的组合都至少出现一次。这是一种较强的逻辑覆盖。仍以图1的程序为例,两个判定表达式中各自含有两个条件,共有以下2×2×2=8种条件组合:
①x>l,y=0 ②x>l,y≠0 ③x≤1,y=0 ④x≤1,y≠0
⑤x=2,z>1⑥x=2,z≤l⑦x≠2,z>l⑧x≠2,z≤l
可以设计以下4组测试数据以满足条件组合覆盖:
①x=2,y=0,z=3,覆盖条件组合①和⑤,执行路径abcde。
②x=2,y=1,z=1,覆盖条件组合②和⑥,执行路径acde。
③x=1,y=0,z=3,覆盖条件组合③和⑦,执行路径acde。
④x=1,y=1,z=1,覆盖条件组合④和⑧,执行路径ace。
显然,满足条件组合覆盖标准的测试数据,也一定满足判定覆盖、条件覆盖和判定/条件覆盖标准。因此,条件组合覆盖是前述几种覆盖标准中最强的。但是,满足条件覆盖标准的测试数据并不一定能使程序中的每条路径都执行到,如上述四组测试数据都没有测试到路径abce。
6.路径覆盖
路径覆盖是指设计足够多的测试用例,以覆盖测试程序中所有可能的路径(若程序图中存在环,则要求每个环至少经过一次)。仍以图1所示的程序为例,该程序共有4条路径。设计以下4组测试数据,就可以覆盖这4条路径:
①x=2,y=0,z=3,覆盖路径abcde,覆盖条件组合①和⑤。
②x=2,y=1,z=1,覆盖路径acde,覆盖条件组合②和⑥。
③x=1,y=l,z=1,覆盖路径ace,覆盖条件组合④和⑧。
④x=3,y=0,z=l,覆盖路径abce,覆盖条件组合①和⑧。
显然,该组测试数据虽然满足路径覆盖,但是它并没有满足条件组合覆盖。
路径覆盖相对来说是相当强的逻辑覆盖标准,测试数据暴露程序错误的能力比较强,有一定的代表性,它能够保证程序中每条可能的路径都至少执行一次。但是路径覆盖并没有检验判定表达式中条件的各种组合情况,而只考虑每个判定表达式的取值组合。若把路径覆盖和条件组合覆盖结合起来,可以设计出检错能力更强的测试数据。
二、基路径测试
我们已经知道,对于比较复杂的程序进行穷尽测试往往是不可能的。基路径测试方法可以较好地解决这类问题。其关注点在于条件判定节点与循环节点对程序路径带来的复杂度的提高,通过对程序执行路径的遍历来实现对程序的覆盖。其主要思想是根据软件详细设计的过程性描述或源代码中的控制流程导出程序图(也称为程序流图),并求出程序的环形复杂性度量,然后用此度量确定程序的基路径集合,并由此导出一组测试用例来覆盖该集合中的每一条独立路径,从而可以保证程序模块的所有独立执行路径至少测试一次。下面举例说明这种测试技术。
【例1】案例代码如下:
int pathtest(int a,int b,int c,int x)
{
1 //声明循环变量
2 int i;
3 //根据变量a,b的取值对x进行赋值
4 if((a>1)&&(b<2))
5 x=c+1;
6 //再次根据变量a,x的取值对x进行赋值
7 if((a==3)||(x>3)){
8 x=x+c;
9 i=0;
10 //通过循环对x进行赋值
11 while(i<b){
12 x=x+i*c;
13 i++;
14 }
15 }
16 else{
17 x=x-c;
18 }
19 printf(“a=%d,b=%d,c=%d,x=%d\n”,a,b,c,x);
20 return x;
}
针对以上代码,请选择用基路径测试进行白盒测试。
基路径测试的一般步骤如下:
1.被测程序代码生成程序图
程序图是进行基路径测试的分析基础。给定待测程序代码后,第一步就是要依据代码来构造该段代码的程序图。程序图可以看成是压缩的控制流图,或者是一种特殊形式的有向图。图中节点代表语句片段,边表示控制流。程序图的特点如下:
(1)不包含注释语句。
(2)不包含数据变量的声明语句。
(3)所有连续的串行语句压缩为一个节点。
图2 函数pathtest的程序图
以上述函数pathtest()为例,其程序图如图2所示。图中各字母节点代表的语句分别如下:
A:8,9
B:16,17,18
C:12,13,14
D:19,20
2.根据程序图计算环复杂度,确定基路径集合的大小
环复杂度就是McCabe复杂性度量,它是对基路径测试所得测试用例规模的粗略度量。在更多情况下,环复杂度被看成是对程序结构复杂情况的一种度量模型,该模型能够反映判定节点和循环的引入对程序结构以及执行路径数目带来的不利影响。基路径测试中,我们通过环复杂度来确定基路径集合的规模,即基路径的条数等于环复杂度的大小。
环复杂度V(G)的确定除了前面McCabe度量法中提到的方法,还有以下三种:
(1)观察法。环复杂度就是1加上程序图中封闭区域的个数。如图2所示,图中有3个明显的封闭区域。
①区域1:节点4、5、7所围成。
②区域2:节点7、A、11、15、D、B所围成。
③区域3:节点11、C所围成,且被包围在区域2内。
因此,图6.2所示的程序图环复杂度为4。
(2)公式法。利用公式来计算环复杂度,使利用工具来自动计算环复杂度成为现实。计算公式如下:
V(G)=e-n+2
其中,e表示图中边的数目,n表示图中节点的总数。
如图6.2所示,图中e=11,n=9,则环复杂度为4,这与直观观察的结果是一致的。
(3)判定节点法。利用代码中独立判定节点的数目来计算环复杂度,计算公式如下:
V(G)=P+1
其中,P表示图中独立判定节点的数目。
如图2所示,图中独立判定节点为4、7、11,共3个,所以根据这个公式计算得到的环复杂度也是4。
值得注意的是:以上计算方法仅适用于程序具有单入口、单出口的情况,如果是多出口,则需要通过修改代码,将多出口改为单出口的情况,或者在程序图中添加一个虚拟出口节点,同时将其他原始出口与该虚拟出口节点相连接,然后对修改后的程序图进行结构分析,这样才会得出正确结论。
3.利用“主路径+转向”的策略确定基路径集合
根据以上得到的环复杂度可确定基路径集合的大小,具体基路径的确定可按以下步骤:
(1)确定主路径。找到一条从程序的入口节点开始,到出口节点结束的路径,该路径应经过尽可能多的判定节点(包括循环节点),为后续的转向做准备。
(2)不断转向。每次以主路径为基础,每当碰到一个尚未转向的判定节点,就在该节点处转向一次。这里的转向是指在某个判定节点处执行一条从未执行过的判断分支,形成一条新的路径。注意:每次仅转向一个判定节点即可。这样不断转向下去,直至找到规定数量的路径。
如图2所示,程序图的环复杂度为4,包含3个判定节点4、7、11。则基路径集合如下所示:
●Path1:4,5,7,A,11,15,D(经过所有的判定节点)
●Path2:4,7,A,11,15,D(在判定节点4处发生转向)
●Path3:4,5,7,B,D(在判定节点7处发生转向)
●Path4:4,5,7,A,11,C,11,15,D(在判定节点11处发生转向)
为了证明得到的路径是相互独立的,表1给出了所有基路径及其通过的边。我们可以看到Path2、Path3和Path4都有唯一通过的边,说明这三条路径是其他路径不可替代的,必然相互独立。如果Path1依赖于其他路径,则可以用其他路径表示,但我们发现,若用其他三条路径表示Path1,则会引入新的边,也就是用灰色填充的单元格对应的边。所以可以证明Path1也是其他路径不可替代的。因此,可以得出,以上四条路径是一组相互独立的路径。
表1 基路径及其通过的边
4.剔除不可行路径,补充其他重要的路径
以程序图为基础得到的基路径一般都是逻辑上可行的路径,不过有时候由于程序员设计程序时存在缺陷,有的会导致得到的路径实际上完全不可能执行,这种情况主要发生在判定节点串联,且前后判定节点涉及的变量之间存在一定的依赖关系的情况下。这时候我们就要结合程序分析将不可行路径剔除。同时,为了测试的完备性,我们在得到基路径集合之后,会根据以下原则补充一些重要的路径:
(1)补充执行概率较高的路径,这是最应该确保没有缺陷的路径。
(2)补充可能包含严重缺陷的路径,例如涉及空间的分配,这是从缺陷预防的角度来考虑的。
(3)补充涉及复杂算法的路径,这关系到程序最终的正确性。
以图6.2所示程序图为例,经分析,我们得到的基路径集合均是可执行路径。对于循环节点11的测试,我们在Path4的基础上补充一条路径,使得循环体执行2次。即:
Path5:4,5,7,A,11,C,11,C,11,15,D
5.根据路径集合确定测试用例,填入测试数据
根据以上的路径集合,针对每条路径设计一个测试用例,如表2所示。
表2 函数pathtest的基路径测试的测试用例
基路径测试是一种使用非常广泛的白盒测试方法,其思想可借鉴用于黑盒测试中,就测试阶段而言,在集成测试、系统测试和功能测试中均可用到该方法。
三、循环结构测试
循环结构是程序的主要逻辑控制结构。要对循环结构进行穷举测试往往是不可能的。一般采用以下方法对循环结构的程序进行测试:
1.对于最多为n次的单循环,可以设计测试用例实现下列测试
①跳过循环,即一次也不执行。
②仅循环1次。
③循环2次。
④循环m次,m<n。
⑤分别循环n-l次、n次、n+1次。
2.对于嵌套循环的测试
①从最内层循环开始测试,此时外层循环都取最小值,对内层进行单循环的测试。
②向外退一层进行测试,此时其内层循环取一些典型值,其外层循环仍取最小值。
③继续向外层扩展,直至测试完成。
黑盒测试技术
黑盒测试,又称功能测试或数据驱动测试,是在已知软件产品应该具有的功能的情况下,通过测试来检验是否每个功能都能正常使用的测试方法。对于软件测试而言,黑盒测试法把程序看成一个黑盒子,完全不考虑程序的内部结构和处理过程。黑盒测试主要测试的错误类型有:
①不正确或遗漏的功能。
②接口错误。
③性能错误。
④数据结构或外部数据访问错误。
⑤初始化或终止条件错误等。
需要指出的是,黑盒测试与白盒测试不能互相替代,它们检查的错误类型是不同的,因此,两者应该互为补充。在测试的不同阶段灵活选用,以便检测出不同类型的错误。下面主要介绍等价类划分、边界值分析、因果图法、错误推测法等几种常见的黑盒测试用例设计方法。
一、等价类划分
等价类划分是设计测试用例的一种常见技术。要实现穷尽的黑盒测试需要使用所有有效的和无效的输入数据来测试程序,但这往往是不现实的。因此,只能选取少量有代表性的输入数据,即选择一个测试子集,用较小的代价暴露尽可能多的错误。
等价类划分的思想是将输入数据按有效的(合理的)和无效的(不合理的)划分成若干个等价类,认为测试等价类中的一个代表值的结果就等于对该类其他值的测试。利用等价类划分的测试步骤如下:
1.划分等价类
从程序的功能说明中找出每个输入条件,然后将其划分成若干个有效和无效的等价类。等价类的划分没有一个完整的法则,下面给出等价类划分的几条经验性原则,以供参考。
(1)如果规定了输入值的范围,则可划分出一个有效等价类(输入值在此范围内)和两个无效等价类(输入值小于最小值或大于最大值)。
(2)如果规定了数据输入的个数,则可划分出一个有效等价类(输入值的个数符合规定)和两个无效等价类(输入值的个数少于或多于规定个数)。
(3)如果规定了输入数据的一组值,而且程序对不同的输入值进行不同的处理,则每个允许的输入值是一个有效等价类,此外还有一个无效等价类(规定的这一组值以外的值)。比如,规定教师职称只能是助教、讲师、副教授和教授4种职称之一,则有4个有效等价类和一个无效等价类(这4个职称以外的值,如工程师等其他职称)。
(4)如果规定了输入数据必须遵循的规则,则可划分出一个有效等价类(符合规则)和若干个无效等价类(从不同的角度违反规则)。
(5)如果输入条件规定了一个集合,则可划分出一个有效等价类(此集合)和一个无效等价类(此集合的补集)。
2.设计测试用例
(1)等价类划分应列表,并为每一个等价类编号。
(2)设计一个有效等价类的测试用例,使其尽可能多地覆盖尚未被覆盖过的有效等价类。如此重复,直到所有有效等价类都被覆盖为止。
(3)设计一个测试用例,仅覆盖一个无效等价类,如此重复,直到所有无效等价类都被覆盖为止。这是因为,在输入中有多个错误存在时,往往只能对其中的一个错误进行显示而屏蔽掉其他错误的显示,因此,设计不合理的测试用例时,每个测试用例只能覆盖一个无效等价类。
【例1】设有一个档案管理系统,要求用户输入以年月表示的日期。假设日期限定在1990年1月至1999年12月,并规定日期由6位数字字符组成,前4位表示年,后2位表示月。现用等价类划分法设计测试用例,来测试程序的“日期检查功能”。
解:(1)划分等价类并编号。等价类划分的结果如表1所示,即划分成3个有效等价类和7个无效等价类。
表1 输入条件“日期”的等价类划分
2)设计测试用例,以便覆盖所有的有效等价类。在表1中列出了3个有效等价类,编号分别为①⑤⑧,设计的测试用例如表2:
表2 有效等价类测试用例
(3)为每一个无效等价类设计一个测试用例,设计结果如表3:
表3 无效等价类测试用例
二、边界值分析
边界值分析是指设计测试用例,使程序在输入或输出的边界值或者边界值左右的值执行。实践表明,大多数错误都发生在边界值左右。
边界值分析方法可以单独设计测试用例,也可以作为等价类划分方法的补充,即在各个等价类中主要是选择边界上及其左右的值。例如,例6.2中对月份范围的测试就可以选择00、0l、02、11、12、13等数据作为测试数据。边界值分析方法设计测试用例的经验性原则是:
(1)如果输入条件指定了范围[a,b],则a、b以及紧挨a、b左右的各个值都应作为测试数据。比如,学生成绩为[0,100],应取-1、0、1、99、100、101共6个值作为测试数据。
(2)如果输入条件指定了输入数据的个数范围,则按最大、最小个数及其左右的个数各设计一个测试用例。比如,一个输入文件包括1~255个记录,则应分别设计输入0个、1个、2个、254个、255个、256个记录的测试数据。
(3)将规则(1)和(2)应用于输出条件,即设计测试用例使输出值达到边界值及其左右的值。
三、因果图法
因果图是设计测试用例的一种工具,它主要检查各种输入条件的组合。等价类划分、边界值分析的测试用例设计方法还不能考虑到组合输入条件可能引起的软件错误,而因果图法则弥补了这个不足之处。
因果图的测试用例设计步骤如下:
(1)分析规格说明中的输入作为因,输出作为果。
(2)依据因果的处理语义画出因果图。
(3)标出因果图的约束条件。
(4)将因果图转换为因果图所对应的判定表。
(5)根据判定表设计测试用例。
在因果图中出现的基本符号,如图1所示。
图1 因果图定义符号
其中:
(a)图表示恒等:表示原因与结果之间是一对一的对应关系。若原因出现,则结果出现。若原因不出现,则结果也不出现。
(b)图表示非:表示原因与结果之间的一种否定关系。若原因出现,则结果不出现。若原因不出现,则结果出现。
(c)图表示或:表示若几个原因中有一个出现,则结果出现,只有当这几个原因都不出现时,结果才不出现。
(d)图表示与:表示若几个原因都出现,结果才出现。若几个原因中有一个不出现,结果就不出现。
(e)图表示异约束:表示a,b两个原因不会同时成立,两个中最多有一个可能成立。
(f)图表示或约束:表示a,b两个原因中至少有一个必须成立。
(g)图表示唯一约束:表示a和b原因当中必须有一个,且仅有一个成立。
(h)图表示要求约束:表示当a出现时,b必须也出现,不可能a出现,b不出现。
(i)图表示强制约束:它表示当a出现时,b必须不能出现,而当a不出现时,b的值不定。
【例2】某规格说明:“第一列字符必须是A或者B,第二列字符必须是一个数字,第一、二两列都满足时修改文件,第一列不正确时给出信息L,第二列不正确时给出信息M。”请用因果图法设计测试用例。
解:(1)分析规格说明并编号。
因:第一列字符是A①
第一列字符是B②
第二列字符是数字③
其中条件①和②是E约束,最多只有一个出现,我们用④表示该中间结果。
果:修改文件⑤
给出信息L⑥
给出信息M⑦
(2)根据题目中的信息,可以画出如图2所示的因果图。
(3)将因果图转换为判定表(如表4所示):遇到E约束记为X;条件和输出结果成立时记为1,否则记为0;表中每一列视为测试规则。
表4 判定表
(4)根据判定表的3~8列编写测试用例,如表5所示:
表5 根据判定表编写的测试用例
四、错误推测法
错误推测法是指测试人员根据经验或直觉推测程序中可能存在的各种错误,从而有针对性地设计检查这些错误的测试用例的一种测试方法。
错误推测法的基本思想是:软件测试人员通过已经掌握的测试理论和实际测试中积累的经验,推测软件在哪些情况下可能发生错误,并将可能发生错误的情况列出。然后为每一个可能发生错误的情况分别设计相应的测试用例。常见的经验方法如下:
(1)零作为测试数据往往容易发现程序中的错误。
(2)分析规格说明书中的漏洞,编写测试数据。
(3)根据尚未发现的软件错误与已发现的软件错误成正比的统计规律,进一步测试时需重点测试已发现错误的程序段。
(4)等价类划分与边界值分析容易忽略组合的测试数据,因此可采用判定表列出测试数据。
(5)与人工代码审查相结合,两个模块中共享的变量已被修改的,可用来做测试用例。因为对一个模块测试出错,同样会引起另一模块的错误。
错误推测法无固定步骤,主要依据测试经验的不断积累。例如,测试一个对线性表(比如数组)进行排序的程序,可推测出以下几项需要特别测试的情况:
①输入的线性表为空表。
②表中只含有一个元素。
③输入表中的所有元素已排好序。
④输入表已按逆序排好。
⑤输入表中部分或全部元素相同。
针对以上列出的每一种情况,可以各设计一个测试用例进行测试。
软件测试阶段
从宏观的角度讲,软件测试阶段一般可以划分为单元测试、集成测试、系统测试和验收测试等几个主要测试阶段。每一个测试阶段都应包含制订测试计划、设计测试用例、测试实施和测试结果的收集评估等。其中,测试计划应包括具体的测试步骤、工作量、进度安排和资源等。在测试的各个阶段,应适宜地选择黑盒测试和白盒测试方法,由开发人员和独立的测试小组单独、分别或共同完成测试任务,必要时还应有用户参加。下面着重讨论各个测试阶段的测试任务、测试步骤等内容。
一、单元测试
单元测试的测试对象是经过软件设计并编码的一个个程序模块。单元测试的主要依据是程序代码和详细设计文档。为了尽可能多地发现并纠正程序模块中的错误,单元测试多采用白盒测试技术,有时也结合黑盒测试技术。一般来说,单元测试可以并行进行。
1.单元测试的任务
单元测试的任务主要包括模块接口测试、模块局部数据结构的测试、模块中所有独立路径的测试、模块中各条错误处理路径的测试和模块边界条件的测试等。
(1)模块接口测试。模块接口测试是测试数据能否正确地流入、流出模块,它是实现其他单元测试任务的基础。模块接口测试的主要测试内容有:
①输入的实际参数与本模块的形式参数在个数、类型、顺序、量纲上是否一致。
②调用其他模块时所给实际参数与被调模块的形式参数在个数、类型、顺序、量纲上是否一致。
③调用预定义函数时所用参数的个数、类型、顺序、返回值的类型等是否正确。
④是否存在与当前入口点无关的参数引用。
⑤是否修改了只读型参数。
⑥各个模块对全局变量的定义和引用是否保持一致。
⑦是否把某些约束作为参数传递。
⑧如果模块内包括外部输入输出,还应测试文件属性是否正确、文件打开/关闭语句是否正确、格式说明与输入输出语句是否匹配、缓冲区大小与记录长度是否匹配、是否处理了文件尾、输出信息中是否存在文字错误等方面的内容。
(2)模块局部数据结构的测试。局部数据结构往往是错误的根源。因此,必须仔细设计测试用例对其进行全面检查,主要的测试内容有:
①不适合或不相容的类型说明。
②变量名不正确(拼写或截断错误等)。
③变量无初值或初始化、默认值有错。
④出现上溢出、下溢出、地址异常等。
(3)模块中所有独立路径的测试。单元测试的基本任务是保证每条语句、每条基路径、每个比较判断都至少执行一次。此时基路径测试、循环测试以及各种逻辑覆盖技术都是可以采用的有效测试技术。这种测试旨在找出因错误的计算、不正确的比较判断和不恰当的控制流等所造成的错误。其中:
①计算中常见的错误有:运算符优先级错误、混合运算类型匹配错误、变量初值错误、达不到精度、表达式不正确等。
②不正确的比较判断和不恰当的控制流常见的错误有:被比较的对象的类型不匹配、错误地使用逻辑运算符及其优先级、计算误差引起的判断错误、循环终止条件不合适、错误地修改了循环变量、迭代发散时不能退出循环等。
(4)模块中各条错误处理路径的测试。我们希望程序模块具有较强的容错能力,这就需要在设计时能预见各种出错条件,并预先设计好各种出错处理通路,以便在用户出现输入数据错误或操作错误时系统能给出恰当的提示而不使系统失效。出错处理路径测试主要测试的错误有:
①异常处理不当。
②在程序自定义的出错处理程序段运行之前系统已介入。
③显示的出错信息难以理解或未能提供足够的错误定位信息。
④显示的错误与实际发生的错误不符等。
(5)模块边界条件的测试。模块边界条件的测试是单元测试中最后也是最重要的一项测试任务,因为程序最容易在边界上出错。可以采用边界值分析技术针对边界值极其左右值设计测试用例,很有可能检测出新的错误。
2.单元测试的步骤
通常,单元测试在编码阶段进行。在源代码编制完成并经过编译检查和评审后,就可以开始进行单元测试。测试用例的设计应与评审工作相结合,根据测试计划和详细设计信息设计测试数据,并应给出对应的期望结果。
由于被测试的模块不是一个独立的程序,而是处于整个软件结构的某一层位置上,即被测模块在被其上层模块调用时才能运行,在运行时又要调用其直接的下层各模块。于是需要为被测模块编制一个驱动模块(Driver Module)和若干个桩模块(Stab Module)。
驱动模块的作用是用来模拟被测模块的上级调用模块,相当于被测模块的主程序,用它接收测试用例的测试数据,把这些测试数据传送给被测模块,接收被测模块的测试结果并输出。
桩模块(也称为存根模块)用来代替被测模块所调用的子模块。桩模块的作用是为被测模块提供所需要的信息,因此,桩模块越简单越好,不需要模拟子模块的所有功能。
例如,一个被测软件的结构图如图1所示,对其中的模块A进行单元测试的测试环境如图2所示。其中的驱动模块模拟调用模块M,桩模块l、2、3分别模拟被调用模块C、D、E。
图1 软件层次结构图
图2 模块A的单元测试环境
由上述可见,为了进行单元测试,一般需要编写测试程序(指驱动模块和桩模块),并且不把它们作为软件产品的一部分提交给用户。
有些模块用单元测试的方法不能进行充分的测试,为了减少开销,这些模块可以在集成测试的过程中完成详尽的测试。提高模块的内聚度可以简化单元测试过程。如果每个模块只完成一个功能,则所需测试用例的数目将显著减少,模块中的错误也更容易预测和发现。
二、集成测试
集成测试是在单元测试的基础上,按照系统设计要求把通过单元测试的单元模块逐步组装与测试,最后组装成一个完整的软件系统的测试过程。因此,集成测试又称为组装测试或综合测试。集成测试的主要依据是单元测试的单元及概要设计文档,旨在发现与接口有关的错误。这些错误包括:
①数据通过接口时会丢失。
②一个模块的功能对另一个模块产生了不利影响。
③几个子功能组合起来没有实现主功能。
④全局数据结构出现错误。
⑤误差的不断积累达到不能接受的程度等。
集成测试有两种集成方式,即非增量集成方式和增量集成方式。
1.非增量集成方式
非增量集成方式是将经过单元测试的所有模块一次性全部组装起来,然后进行整体测试,最后得到所要求的软件系统。这种集成方式容易出现混乱,因为开始可能遇到一大堆错误,而且出错的模块往往并不是错误隐藏的模块,加之模块众多,于是为每个错误定位非常困难。何况在改正一个错误时,还可能引入新的错误,新旧错误交织在一起,会使测试变得更加困难。因此,一般不应采用这种集成方式。
2.增量集成方式
增量集成方式是将经过单元测试的模块逐步组装成较大的系统,一边组装,一边进行测试,以便发现模块间与接口有关的问题。这种方式实际上可以同时完成单元测试和集成测试。根据组装方法的不同,可以分为自顶向下集成和自底向上集成两种方法。
(1)自顶向下集成。自顶向下集成方式是从主控模块开始,按照软件的控制层次结构,以深度优先或广度优先的策略,逐步把各个模块组装在一起。
自顶向下集成测试的具体步骤如下:
①对主控模块进行集成测试,所有被其直接调用的下属模块都用桩模块代替。
②依据所选用的集成策略(深度优先或广度优先)所规定的次序,每次只用一个实际模块替代一个对应的桩模块,并用新的桩模块代替新加入的模块,与已测模块或子系统构成新的子系统,进行测试。
③为确保新加入的模块未引入新的缺陷,每次都需要进行回归测试(即部分或全部执行以前做过的测试)。
④重复执行步骤②③,每重复一次,增加一个模块,直至所有模块都已集成到系统中。
如图3所示的被测软件系统结构图,如果采用深度优先策略,则以M1作为主控模块,集成测试步骤如下:
①首先测试M1模块,被调用模块M2、M3、M4分别用桩模块S2、S3、S4代替,然后对子系统M1、S2、S3、S4组成的子系统测试一遍。
②将模块M2加入其中,被调用模块M5、M6分别以桩模块S5、S6代替,于是对由M1、M2、S3、S4、S5、S6组成的子系统测试一遍。
③用M5代替S5,并用桩模块S8代替M8,对由M1、M2、M5、S3、S4、S8、S6组成的子系统再测试一遍。
④以此类推,以后依次集成的模块为M8、M6、M3、M7、M4。直至所有模块集成为完整的系统测试一遍。
一般来说,深度优先策略应首先把主控制路径上的模块集成起来,而主控制路径的选择应根据问题的特性来确定,有时可能带有一定的随意性。
而广度优先策略是沿控制层次结构水平地向下移动,按一层一层的顺序将模块一个个地集成起来。如图3所示的软件结构图,集成的顺序为M1、M2、M3、M4、M5、M6、M7、M8,在集成的过程中也需要桩模块的配合。
自顶向下集成的特点是不需要驱动模块,但需要大量的桩模块。其优点是能够尽早地验证程序的主要控制和判断机制,可以较早发现此类错误,从而减少以后的返工。其缺点是在测试较高层模块时,低层模块采用较简单的桩模块来代替,不能反映实际情况,测试可能不充分。
图3 被测软件系统结构图
(2)自底向上集成。即从程序结构的最底层模块开始组装和测试。由于模块是按照自底向上的顺序进行组装的,当要测试某一给定层次的模块时,其所有下属模块都已组装测试完毕。因此,这种测试需要一定数量的驱动模块,而不需要桩模块。
自底向上集成测试的具体步骤如下:
①把低层模块组织成实现某个特定的软件子功能的模块群(Module Cluster)。
②为每一个模块群开发一个驱动模块,控制测试数据的输入和测试结果的输出。
③对每个模块群进行测试。
④去掉测试用的驱动模块,用较高层模块将几个模块群组成新的更大的模块群。
上述的②③④步骤重复执行,直至整个程序构造完毕。
例如,图4给出了自底向上的集成过程。首先,把软件结构图中的最底几层模块按照子功能划分为三个模块群,并为每一个模块群设计一个驱动模块进行测试。如模块群l、2、3的驱动模块分别为Dl、D2、D3。三个模块群测试完成后,去掉D1、D2两个驱动模块,用实际模块M2驱动模块群1和2,再为M2设计一个驱动模块D4,对由模块群l、2和M2组成的大模块群进行测试。同样,去掉D3,将M3和模块群3连起来组成较大的模块群,并设计一个驱动模块D5来驱动模块M3,对M3和模块群3组成的较大模块群进行测试,最后去掉D4和D5,直接用M1驱动M2和M3,于是构成一个完整的系统,进行最后的集成测试。
图6.8 自底向上集成过程
自底向上集成的测试方法只需要设计驱动模块,不需要设计桩模块,测试用例的设计也相对简单,同时,由于涉及复杂算法和直接输入输出的模块最先得到组装和测试,可以在早期解决这些最容易出问题的部分。还有,自底向上集成可以实施多个模块并行测试。其缺点是只有程序最后一个模块(即主模块)加入后才具有整体形象,对其高层控制与判断进行测试的时间较晚,如果到测试的后期才发现整体存在较严重问题,就不得不进行较大的返工,此时的代价将是巨大的。
(3)混合式测试及重点测试。根据前面对自顶向下和自底向上两种集成测试方法的介绍,我们知道两种测试方法的优缺点刚好相反,因此,可以将两种测试策略结合起来使用,即对于上层模块采用自顶向下的方法,而对于下层模块采用自底向上的方法。这样不仅可以大大减少开发驱动模块和桩模块的数量,还可以充分发挥各自的优点。这种方法又称为三明治集成方法。
此外,在集成测试过程中应对关键模块进行重点测试。关键模块是指具有如下一个或多个特征的模块:
①对应多条需求。
②具有高层控制功能。
③复杂、易出错。
④有特殊性能要求。
对关键模块应尽早测试,并应进行反复的回归测试。
三、确认测试
确认测试又称有效性测试,任务是验证软件的功能和性能及其他特性是否与用户的要求一致。对软件的功能和性能要求在软件需求规格说明书中已经明确规定,它包含的信息就是软件确认测试的基础。通过集成测试之后,软件已完全组装起来,接口方面的错误也已排除,确认测试应检查软件能否按合同要求进行工作,即是否满足软件需求说明书中的确认标准。
1.确认测试标准
实现软件确认要通过一系列黑盒测试。确认测试同样需要制订测试计划和过程,测试计划应规定测试的种类和测试进度,测试过程则定义一些特殊的测试用例,旨在说明软件与需求是否一致。无论是计划还是过程,都应该着重考虑软件是否满足合同规定的所有功能和性能,文档资料是否完整、准确,人机界面和其他方面(例如,可移植性、兼容性、错误恢复能力和可维护性等)是否令用户满意。
确认测试的结果有两种可能:一种是功能和性能指标满足软件需求说明的要求,用户可以接受;另一种是软件不满足软件需求说明的要求,用户无法接受。项目进行到这个阶段才发现严重错误和偏差,一般很难在预定的工期内改正,因此必须与用户协商,寻求一个妥善解决问题的方法。
2.配置复审
确认测试的另一个重要环节是配置复审。复审的目的在于保证软件配置齐全、分类有序,两者要一致,并且包括软件维护所必需的细节。
3.α测试和β测试
事实上,软件开发人员不可能完全预见用户实际使用程序的情况。例如,用户可能错误地理解命令,或提供一些奇怪的数据组合,亦可能对设计者已认明了的输出信息迷惑不解,等等。因此,软件是否真正满足最终用户的要求,应由用户进行一系列“验收测试”。这种测试既可以是非正式的测试,也可以有计划、有系统地测试。有时,这个测试过程长达数周甚至数月,不断暴露错误,导致开发延期。一个软件产品拥有众多用户,不可能由每个用户验收,此时多采用被称为α、β测试的过程,以期待发现那些似乎只有最终用户才能发现的问题。
α测试是指软件开发公司组织内部人员模拟各类用户对即将面市的软件产品(称为α版本)进行测试,试图发现错误并修正。α测试的关键在于尽可能逼真地模拟实际运行环境和用户对软件产品的操作并尽最大努力涵盖所有可能的用户操作方式。
经过α测试调整的软件产品称为β版本。紧随其后的β测试是指软件开发公司组织各方面的典型用户在日常工作中实际使用β版本,并要求用户报告异常情况,提出批评意见,然后软件开发公司再对β版本进行改错和完善。
四、系统测试
系统测试是将通过确认测试的软件,作为整个基于计算机系统的一个元素,与计算机硬件、外设、某些支持软件、数据和人员等其他系统元素结合在一起,在实际运行(使用)环境下,对计算机系统进行一系列的组装测试和确认测试。在系统测试实施之前,软件工程师应完成以下工作:为测试软件系统的输入信息设计出错处理通路;设计测试用例,模拟错误数据和软件界面可能发生的错误,记录测试结果,为系统测试提供经验和帮助;参与系统测试的规划和设计,保证软件测试的合理性。
系统测试实质上是由一系列不同测试组成的,其主要目的是充分运行系统,验证系统各个部件是否都能正常工作并完成所分配的功能。以下,我们将讨论用于系统的几种软件系统测试类型。
1.恢复测试
恢复测试主要检查系统的容错能力。当系统出错时,能否在指定的时间间隔内修正错误并重新启动系统。恢复测试首先要采用不同的方式强迫系统出现故障,然后验证系统是否能尽快恢复。如果恢复是自动的(由系统自身完成),则重新初始化、检测点设置、数据恢复以及重新启动等都是对其正确性的评价。若恢复需人工干预,则需估算出修复的平均时间,确定其是否在可接受的限制范围以内。
2.安全性测试
系统的安全性测试是要检验在系统中已存在的系统安全性措施、保密性措施是否发挥作用,有无漏洞。在安全性测试过程中,测试人员应扮演非法入侵者,采用各种办法试图突破防线。如,想方设法截取或破译口令;专门定做软件破坏系统的保护机制;故意导致系统失败,企图趁系统恢复之机非法进入;试图通过浏览非保密数据,推导所需信息等等。系统安全设计的准则是使非法入侵的代价超过被保护信息的价值。这样,非法入侵者就会无利可图。
3.强度测试
强度测试是要检查在系统运行环境不正常到发生故障的时间内,系统可以运行到何种程度的测试。强度测试是在要求一个非正常数量、频率或容量资源方式下运行一个系统。如,当平均速度有一种或两种时,可以设计每秒产生十个中断的特殊测试;定量地增长数据输入率,检查输入子功能的反应能力;运行需要最大内存或其他资源的测试用例;运行可能导致虚拟操作系统崩溃或磁盘剧烈抖动的测试用例等。
4.性能测试
性能测试就是测试软件在被组装进系统的环境下运行时的性能。性能测试应覆盖测试过程的每一步。即使在单元层,单个模块的性能也可以通过白盒测试来评价,而不是等到所有系统元素全部组装以后,再确认系统的真正性能。性能测试有时是与强度测试联系在一起的,常常需要硬件和软件的测试设备。
五、验收测试
验收测试是软件开发结束后,软件产品投入实际应用以前进行的最后一次质量检验活动,是以用户为主,软件开发人员和质量保证人员也应参加的测试。它要回答开发的软件产品是否符合预期的各项要求,以及用户能否接受的问题。由于不只是检验软件某个方面的质量,而是要进行全面的质量检验,并且要确定软件是否合格,因此验收测试是一项严格的正式测试活动。需要根据事先制订的计划,进行软件配置评审、功能测试、性能测试等多方面检测。
用户验收测试可以分为两个大的部分:软件配置审核和可执行程序测试。其大致顺序可分为:文档审核、源代码审核、配置脚本审核、测试程序或脚本审核、可执行程序测试。
要注意的是,在开发方将软件提交用户方进行验收测试之前,必须保证开发方本身已经对软件的各方面进行了足够的正式测试(当然,这里的“足够”,本身是很难准确定量的)。
用户验收测试的每一个相对独立的部分,都应该有目标(本步骤的目的)、启动标准(着手本步骤必须满足的条件)、活动(构成本步骤的具体活动)、完成标准(完成本步骤要满足的条件)和度量(应该收集的产品与过程数据)。在实际验收测试过程中,收集度量数据,不是一件容易的事情。
1.软件配置审核
对于一个外包的软件项目而言,软件承包方通常要提供如下相关的软件配置内容:
(1)可执行程序、源程序、配置脚本、测试程序或脚本。
(2)主要的开发类文档:《需求分析说明书》《概要设计说明书》《详细设计说明书》《数据库设计说明书》《测试计划》《测试报告》《程序维护手册》《程序员开发手册》《用户操作手册》《项目总结报告》。
(3)主要的管理类文档:《项目计划书》《质量控制计划》《配置管理计划》《用户培训计划》《质量总结报告》《评审报告》《会议记录》《开发进度月报》。
需要注意的是,在开发类文档中,容易被忽视的文档有《程序维护手册》和《程序员开发手册》。《程序维护手册》的主要内容包括:系统说明(包括程序说明)、操作环境、维护过程、源代码清单等,编写目的是为将来的维护、修改和再次开发工作提供有用的技术信息。《程序员开发手册》的主要内容包括:系统目标、开发环境使用说明、测试环境使用说明、编码规范及相应的流程等,实际上就是程序员的培训手册。
不同大小的项目,都必须具备上述的文档内容,只是可以根据实际情况进行重新组织。对上述的提交物,最好在合同中规定阶段提交的时间,以免发生纠纷。
通常,正式的审核过程分为5个步骤:计划、预备会议(可选)、准备阶段、审核会议和问题追踪。预备会议是对审核内容进行介绍并讨论。准备阶段就是各责任人事先审核并记录发现的问题。审核会议是最终确定工作产品中包含的错误和缺陷。
审核要达到的基本目标是:根据共同制定的审核表,尽可能地发现被审核内容中存在的问题,并最终得到解决。在根据相应的审核表进行文档审核和源代码审核时,还要注意文档与源代码的一致性。
在实际的验收测试执行过程中,常常会发现文档审核是最难的工作,一方面由于市场需求等方面的压力使这项工作常常被弱化或推迟,造成持续时间变长,加大文档审核的难度;另一方面,文档审核中不易把握的地方非常多,每个项目都有一些特别的地方,而且也很难找到可用的参考资料。
2.可执行程序的测试
在文档审核、源代码审核、配置脚本审核、测试程序或脚本审核都顺利完成后,就可以进行验收测试的最后一个步骤——可执行程序的测试,它包括功能、性能等方面的测试,每种测试也都包括目标、启动标准、活动、完成标准和度量等五部分。
要注意的是不能直接使用开发方提供的可执行程序用于测试,而要按照开发方提供的编译步骤,从源代码重新生成可执行程序。
在真正进行用户验收测试之前一般应该已经完成了以下工作(也可以根据实际情况有选择地采用或增加):
(1)软件开发已经完成,并全部解决了已知的软件缺陷。
(2)验收测试计划已经过评审并批准,并且置于文档控制之下。
(3)对软件需求说明书的审查已经完成。
(4)对概要设计、详细设计的审查已经完成。
(5)对所有关键模块的代码审查已经完成。
(6)对单元、集成、系统测试计划和报告的审查已经完成。
(7)所有的测试脚本已完成,并至少执行过一次,且通过评审。
(8)使用配置管理工具且代码置于配置控制之下。
(9)软件问题处理流程已经就绪。
(10)已经制定、评审并批准验收测试完成标准。
具体的测试内容通常可以包括:安装(升级)、启动与关机、功能测试(用例、重要算法、边界、时序、反例、错误处理)、性能测试(正常的负载、容量变化)、压力测试(临界的负载、容量变化)、配置测试、平台测试、安全性测试、恢复测试(在出现断电、硬件故障或切换、网络故障等情况时,系统是否能够正常运行)、可靠性测试等。
性能测试和压力测试一般情况下是在一起进行,通常还需要辅助工具的支持。在进行性能测试和压力测试时,测试范围必须限定在那些使用频度高和时间要求苛刻的软件功能上集中。由于开发方已经事先进行过性能测试和压力测试,因此可以直接使用开发方的辅助工具,也可以通过购买或自己开发来获得辅助工具。具体的测试方法可以参考相关的软件测试书籍。
如果执行了所有的测试案例、测试程序或脚本,用户验收测试中发现的所有软件问题都已解决,而且所有的软件配置均已更新和审核,可以反映出软件在用户验收测试中所发生的变化,用户验收测试就完成了。
软件测试文档
软件测试是比较复杂和困难的过程,为了很好控制测试的复杂性和困难性,需要认真编写完整的测试文档。
一、测试计划文档
下面给出一个系统测试计划模板供参考。
1. 介绍
1.1 目的
说明文档的目的。
1.2 范围
说明文档覆盖的范围。
1.3 缩写说明
定义文档中所涉及的缩略语(若无则填写无)。
1.4 术语定义
定义文档中所使用的特定术语(若无则填写无)。
1.5 引用标准
列出文档制定所依据、引用的标准(若无则填写无)。
1.6 参考资料
列出文档制定所参考的资料(若无则填写无)。
1.7 版本更新信息
记录文档版本修改的过程,具体版本更新记录如表1所列。
表1 版本更新记录
2. 测试项目
对被测试对象进行描述。
3. 测试方法
分析和描述本次测试采用的测试方法和技术。
4. 测试标准
描述测试通过的标准以及测试审批的过程。测试挂起/恢复的条件。
5. 系统测试交付物
测试完成后提交的所有产品。
6. 测试任务
7. 环境需求
7.1 硬件需求
7.2 软件需求
7.3 测试工具
7.4 其他
8. 角色和职责
9. 人员及培训
10. 系统测试进度
二、测试设计
测试设计主要是根据相应的依据(需求、概要设计、详细设计等)设计测试方案、测试的覆盖率以及设计测试用例等。表2的测试覆盖表和表3的测试用例表可以作为参照。注意,不同类型的测试用例表有所不同,表3所示一般用于功能测试。
表2 测试用例覆盖表
表3 测试用例表
三、测试开发
测试开发主要是按照测试设计编写脚本,这个脚本可以是文字描述的测试过程,或者采用编程语言编写的测试脚本,可以使用工具来生成测试脚本。当然,不需要编写测试脚本的时候,测试执行过程按照测试设计的测试用例执行就可以了。
四、测试执行
测试执行过程中需要填写测试执行后的测试用例表(意外事件要记录在用例表的备注中),最好给出测试用例执行情况的跟踪图。
五、测试跟踪
可以使用测试工具跟踪测试结果,如表4所示就是一个缺陷跟踪记录表。目前市场上存在很多缺陷跟踪的商用工具软件。
表4 缺陷跟踪记录表
六、测试总结
下面给出一个测试总结报告模板供参考(可以视具体情况裁剪)。
1. 介绍
1.1 目的
说明文档的目的。
1.2 范围
说明文档覆盖的范围。
1.3 缩写说明
定义文档中所涉及的缩略语(若无则填写无)。
1.4 术语定义
定义文档中所使用的特定术语(若无则填写无)。
1.5 引用标准
列出文档制定所依据、引用的标准(若无则填写无)。
1.6 参考资料
列出文档制定所参考的资料(若无则填写无)。
1.7 版本更新信息
记录文档版本修改的过程,具体版本更新记录如表1所列。
表1 版本更新记录
2. 测试时间、地点和人员
3. 测试环境描述
4. 测试数据度量
4.1 测试用例执行度量
4.2 测试进度和工作量度量
4.3 缺陷数据度量
4.4 综合数据分析
计划进度偏差=[(实际进度—计划进度)/计划进度]×100%
用例执行效率=执行用例总数/执行总时间(小时)
用例密度=(用例总数/规模)×100%
缺陷密度=(缺陷总数/规模)×100%
用例质量=(缺陷总数/用例总数)×100%
请画出缺陷严重程度分布饼图和缺陷类型分布饼图。
5. 测试评估
5.1 测试任务评估
5.2 测试对象评估
6. 遗留缺陷分析
7. 审批报告
提交人签字: 日期:
开发经理签字: 日期:
产品经理签字: 日期:
8. 附件
附件1 测试用例执行表
附件2 测试覆盖率报告
附件3 缺陷分析报告
7、软件维护
软件维护概述
一、软件维护概念
软件维护主要是指根据需求变化或硬件环境的变化对应用程序进行部分或全部的修改,修改时应充分利用源程序.修改后要填写程序登记表,并在程序变更通知书上写明新旧程序的不同之处。
二、软件维护的内容
1.正确性维护
正确性维护是指改正在系统开发阶段已发生而系统测试阶段尚未发现的错误。这方面的维护工作量要占整个维护工作量的17%~21%。所发现的错误有的不太重要,不影响系统的正常运行,其维护工作可随时进行;而有的错误非常重要,甚至影响整个系统的正常运行,其维护工作必须制订计划,进行修改,并且要进行复查和控制。
2.适应性维护
适应性维护是指使用软件适应信息技术变化和管理需求变化而进行的修改。这方面的维护工作量占整个维护工作量的18%~25%。由于目前计算机硬件价格的不断下降,各类系统软件层出不穷,人们常常为改善系统硬件环境和运行环境而产生系统更新换代的需求;企业的外部市场环境和管理需求的不断变化也使得各级管理人员不断提出新的信息需求。这些因素都将导致适应性维护工作的产生。进行这方面的维护工作也要像系统开发一样,有计划、有步骤地进行。
3.完善性维护
这是为扩充功能和改善性能而进行的修改,主要是指对已有的软件系统增加一些在系统分析和设计阶段中没有规定的功能与性能特征。这些功能对完善系统功能是非常必要的。另外,还包括对处理效率和编写程序的改进,这方面的维护占整个维护工作的50%~60%,比重较大,也是关系到系统开发质量的重要方面。这方面的维护除了要有计划、有步骤地完成外,还要注意将相关的文档资料加入到前面相应的文档中去。
4.预防性维护
为了改进应用软件的可靠性和可维护性,为了适应未来的软硬件环境的变化,应主动增加预防性的新的功能,以使应用系统适应各类变化而不被淘汰。例如,将专用报表功能改成通用报表生成功能,以适应将来报表格式的变化。这方面的维护工作量占整个维护工作量的4%左右。
软件维护过程
一、主要任务
软件维护的主要任务包括以下两点:
1.维护组织机构
对于大型软件系统,建立一个专门的维护组织机构是必需的。即使是对较小的软件系统,也要委派一个人专门负责软件维护工作,特别是收集、保存、整理维护活动的文档资料的工作是必须随时要做的。
在维护活动开始之前必须明确维护活动的审批制度。每个维护要求都要通过维护管理员转交给系统管理员去评价。系统管理员对维护申请作出评价后,由主管部门(人)决定是否进行软件修改。接到审批的维护申请报告后,将维护任务下达给指定的维护人员,并监控维护活动有条不紊地开展。合理的组织机构和精干的维护人员是保障维护活动顺利实施的基础。
2.维护报告
任何维护申请都应该按规范化的方式提出。通常要求用户填写维护申请表。表中必须完整地描述每个错误发生的环境,包括输入数据、输出结果等有关信息。对于适应性或完善性的维护要求,还应该提出一份修改说明书,提出用户希望的修改。维护申请表交维护组织后,经有关人员认真分析,并根据分析结果制订软件修改报告,内容应包括:
①维护要求的性质。
②维护活动的优先顺序。
③计算满足维护申请表中提出的软件变更所需要的工作量。
④预计软件变更后的状况。
二、维护阶段
软件维护的六个阶段如下:
(1)实施阶段包括软件准备和过度活动,比如,维护计划的构思与创建,为解决开发过程中所发现的问题而做的准备,以及后续的产品配置管理。
(2)一旦维护团队正式接手应用,就进入了问题与修改分析阶段。维护人员必须分析每个请求,对其进行确认(通过重建应用环境)并验证其有效性,调查并提出解决方案,记录请求和解决方案,最后获取所有需要的授权来应用修改。
(3)该阶段为实施修改的阶段。
(4)在接受修改阶段,由请求的提交者进行检查,以确保所作的修改解决了相应的请求。
(5)迁移阶段(如平台迁移)是个特例,并不会在所有的维护任务中都出现。如果软件必须在功能没有任何改变的情况下迁移到另一个平台上,那么这个阶段将被实施,通常这项任务会被指派给一个维护工程团队。
(6)最后一个维护阶段,也并不会在每个软件中都发生,就是对软件某一部分的弃用。
三、维护问题
软件维护的绝大多数问题与软件定义和软件开发阶段所采用的设计方法、指导思想、技术手段、开发工具等有直接的关系,同时与维护工作的性质也有一定的关系。
主要问题是:
(1)理解别人写的程序通常非常困难,而且困难程度随着软件配置成分的减少而迅速增加。如果仅有源代码而没有相关的文档,问题会更加严重。
(2)严格按规范化方法开发的软件系统一般不需要大的维护活动,而需要维护的软件系统却往往因为没有必需的文档或文档残缺不全,使得维护活动进展非常艰难。
(3)当需要对软件进行维护时,很难指望熟悉软件系统的原开发人员能全力以赴地亲临现场参与维护活动。
(4)绝大多数软件在设计时没有考虑将来的修改。
(5)软件维护不是一项吸引人的工作。最出色的、成功的维护也只不过是保证他人开发的系统能正常运行,而且维护别人开发的软件经常受挫,使得维护人员无成就感。
软件的可维护性
软件的维护是十分困难的,为了使软件能易于维护,必须考虑使软件具有可维护性。
一、可维护性的定义
软件可维护性的定义:软件能够被理解、校正、适应及增强功能的容易程度。
软件的可维护性、可使用性、可靠性是衡量软件质量的几个主要特性,也是用户十分关心的几个问题。
软件的可维护性是软件开发阶段的关键目标。影响软件可维护性的因素较多,设计、编码及测试中的疏忽和低劣的软件配置,缺少文档等都对软件的可维护性产生不良影响。软件可维护性可用以下七个质量特性来衡量,即可理解性、可测试性、可修改性、可靠性、可移植性、可使用性和效率。对于不同类型的维护,这七种特性的侧重点也不相同。
二、可维护性的度量
目前有若干对软件可维护性进行综合度量的方法,但要对可维护性作出定量度量还是比较困难的。还没有一种方法能够使用计算机对软件的可维护性进行综合性的定量评价。
下面是在度量一个可维护的软件的七种特性时常采用的方法,即质量检查表、质量测试、质量标准。
质量检查表是用于测试程序中某些质量特性是否存在的一个问题清单。
质量测试与质量标准则用于定量分析和评价程序的质量。由于许多质量特性是相互抵触的,要考虑几种不同的度量标准去度量不同的质量特性。
三、提高可维护性的方法
从下面五个方面来阐述如何提高软件的可维护性。
1.建立明确的软件质量目标
如果要程序满足可维护性七个特性的全部要求,那么要付出很大的代价,甚至是不现实的,但有些可维护性是相互促进的,因此要明确软件所追求的质量目标。
2.利用先进的软件开发技术和工具
利用先进的软件开发技术能大大提高软件质量和减少软件费用。面向对象的软件开发方法就是一个非常实用而强有力的软件开发方法,用面向对象方法开发出来的软件系统,稳定性好,比较容易修改,比较容易理解,易于测试和调试,因此,可维护性好。
3.建立明确的质量保证
质量保证是指为提高软件质量所做的各种检查工作。质量保证检查是非常有效的方法,不仅在软件开发的各阶段中得到了广泛应用,而且在软件维护中也是一个非常主要的工具。为了保证可维护性,以下四类检查是非常有用的:
①在检查点进行检查。
②验收检查。
③周期性的维护检查。
④对软件包的检查。
4.选择可维护的语言
程序设计语言的选择对维护影响很大。低级语言很难掌握,很难理解,因而很难维护。一般来说,高级语言比低级语言更容易理解,第四代语言更容易理解,容易编程,程序容易修改,改进了可维护性。
5.改进程序的文档
程序文档是对程序功能、程序各组成部分之间的关系、程序设计策略、程序实现过程的历史数据等的说明和补充。程序文档对提高程序的可阅读性有重要作用。为了维护程序,人们必须阅读和理解程序文档。
软件再工程
软件再工程概念
软件再工程是指对既存对象系统进行调查,并将其重构为新形式代码的开发过程。最大限度地重用既存系统的各种资源是再工程的最重要特点之一。从软件重用方法学来说,如何开发可重用软件和如何构造采用可重用软件的系统体系结构是两个最关键问题。不过对再工程来说前者很大一部分内容是对既存系统中非可重用构件的改造。
软件再工程模型是一个循环模型,该模型中的每个活动均可能被重复。软件再工程模型如图1所示。
图1 软件再工程模型
一、库存目录分析(Inventory Analysis)
每个软件组织应该保存所有应用的库存目录。该目录可能仅仅是包含如下信息的一个电子表格模型:应用系统的名字,最初构建它的日期,已做过的实质性修改次数,过去18个月报告的错误,用户数量,安装它的机器数量,它的复杂程度,文档质量,整体可维护性等级,预期寿命,在未来36个月内的预期修改次数,业务重要程度等。
二、文档重构(Document Restructuring)
文档重构主要是实现了同一概念信息的聚集,是更接近于人类进行信息查找的思维方法。
贫乏的文档是很多遗产系统的注册商标。但是,我们对此可做些什么呢?
选项1:建立文档是非常耗费时间的。系统正常运作,我们将保持现状。在某些情况下,这是一个正确的方法,不可能为数百个计算机程序重新建立文档。如果一个程序是相对静止的,正在走向其有用生命的末端,并且可能不会再经历什么变化,那么,让它保持现状。
选项2:文档必须更新,但是,我们只有有限的资源。我们将使用“使用时建文档”的方法。可能不需要对某应用全部重构文档,而是对系统中当前正在进行改变的那些部分建立完整文档。随时间流逝,将得到一组有用的和相关的文档。
选项3:系统是业务关键的,而且必须完全地重构文档。即使在此情形,明智的方法是设法将文档工作减少到必需的最小量。
每个选项均是可行的,软件组织必须选择最适合于每种情形的方法。
三、逆向工程(Reverse Engineering)
术语“逆向工程”源于硬件领域,一个公司分解某竞争者的硬件产品去了解竞争者的设计和制造“秘密”。如果得到了竞争者的设计和制造规约,则这些秘密可以很容易地理解。但是,这些文档是别人专有的,对做逆向工程的公司来说是不可得到的。本质上,成功的逆向工程通过检查产品的实际样本导出一个或多个关于产品的设计和制造规约。
软件的逆向工程是相当类似的。然而,在大多数情况下,被逆向工程的程序不是来自于竞争者,而是公司自己的软件(经常是很多年以前的)。将被理解的“秘密”是未开发过的相关规约所带来的模糊不明。因此,软件的逆向工程是分析程序在比源代码更高的抽象层次上创建程序的某种表示的过程。逆向工程是一个设计恢复过程,逆向工程工具从现存的程序中抽取数据、体系结构和过程的设计信息。
四、代码重构(Code Restructuring)
最常见的再工程类型(实际上,在这里使用术语“再工程”是有疑问的)是代码重构。某些遗产系统具有相对完整的程序体系结构,但是,个体模块被以难于理解、测试和维护的方式编码。在这样的情形下,在可疑模块内的代码可被重构。
为了完成该活动,用重构工具去分析源代码,标注出和结构化程序设计概念相背的部分,然后重构代码(此工作可以自动进行)。复审和测试生成的重构代码以保证没有引入异常和不规则。
五、数据重构(Data Restructuring)
数据体系结构差的程序将难于进行适应性修改和增强。事实上,对很多应用来说,数据体系结构比源代码本身对程序的长期生存力有更大影响。
和代码重构不同,数据重构发生在相当低的抽象层次上,它是一种全范围的再工程活动。在大多数情况下,数据重构以逆向工程活动为开始,当前的数据体系结构被分解。必要时,数据重构还可以定义数据模型,标识数据对象和属性,并从质量的角度复审现存的数据结构。当数据结构较差时(例如,平坦文件正被实现,而关系型方法将大大地简化处理),数据被再工程。因为数据体系结构对程序体系结构及其中的算法有很强的影响,对数据的改变将总是会导致体系结构或代码层的改变。
六、正向工程(Forward Engineering)
在理想的情况下,可以使用自动的“再工程引擎”来重建应用,旧的程序被输入引擎,分析、重构,然后以展示软件质量最佳方面的形式重新生成。但是,在短期内,这样的“引擎”还不可能出现。
正向工程也称为革新或改造,不仅可以从现存软件中恢复设计信息,还可以使用该信息去改变或重构现存系统,以改善系统整体质量。在大多数情况下,被再工程的软件可以重新实现现存系统的功能,并且加入新功能或改善整体性能。
逆向工程
逆向工程是对产品设计过程的一种描述。逆向工程产品设计可以认为是一个从产品到设计的过程。简单地说,逆向工程产品设计就是根据已经存在的产品,反向推出产品设计数据的过程。
随着计算机技术在各个领域的广泛应用,特别是软件开发技术的迅猛发展,基于某个软件,以反汇编阅读源码的方式去推断其数据结构、体系结构和程序设计信息成为软件逆向工程技术关注的主要对象。
逆向工程过程及用于实现该过程的工具的抽象层次是指可以从源代码中抽取出来的设计信息的精密程度。在理想情况下,抽象层次应该尽可能高,即逆向工程过程应该能够导出过程的设计表示(一种低层的抽象);程序和数据结构信息(稍高一点层次的抽象);数据和控制流模型(一种相对高层的抽象);以及实体—关系模型(一种高层抽象)。随着抽象层次的增高,软件工程师获得更有助于理解程序的信息。
逆向工程过程的完整性是指在某抽象层次上提供的细节程度。在大多数情况下,随着抽象层次的增高,完整性就降低。
交互性是指为了建立一个有效的逆向工程过程,人和自动工具“集成”的程度。在大多数情况下,随着抽象层次增高,交互必须增加,而完整性将遭受损害。如果逆向工程过程的方向是单向的,所有从源程序中抽取的信息被提供给软件工程师,然后工程师可以在任何维护活动中使用这些信息。如果逆向工程过程的方向是双向的,则信息就会被输入到再工程工具中,以试图重构或生成旧程序。
逆向工程的核心是被称为抽取抽象的活动,工程师必须评价旧程序并从源代码(经常是无文档的)中抽取出被执行的处理、被应用的用户界面以及被使用的程序数据结构或数据库的有意义的规约。
一、处理的逆向工程
第一个真正的逆向工程活动是从理解然后抽取源代码所表示的过程抽象的启图开始。为了理解过程抽象,要在不同的抽象层次分析代码:系统、程序、模块、模式和语句。
在进行更细节的逆向工程前必须理解整个系统的整体功能。这为进一步地分析建立语境,并提供对系统中应用间的互操作问题的洞悉。构成应用系统的每一个程序代表了在高详细层次的功能抽象,建立表示这些功能抽象间的交互的块图;每个模块执行某种子功能并表示某种定义的过程抽象,为每个模块建立处理叙述。在某些情况下,系统、程序和模块规约已经存在,在此情况下,复审这些规约以保持和现存代码的相符性。
当考虑模块中的代码时,事情变得更复杂。工程师需寻找表示类属过程模式的代码段。几乎在每个模块中,一个代码段为处理(在模块内)准备数据,一个不同的代码段完成处理工作,而另一个代码段准备处理的结果以从模块中输出。在每个代码段中,我们可能遇到更小的模式(例如,数据确认和范围检查经常出现在为处理准备数据的代码段中)。
对大型系统,通常用半自动方法来完成逆向工程。
二、数据的逆向工程
数据的逆向工程发生在不同的抽象层次。在程序层中,作为整体再工程努力的一部分内部的程序数据结构必须被逆向工程。在系统层中,全局数据结构(如,文件、数据库)经常被再工程以便符合新的数据库管理范型,当前全局数据结构的逆向工程设置了引入新的系统范围数据库的场所。
1.内部数据结构
针对内部程序数据的逆向工程技术着重于对象的类的定义。这是通过意图组合相关程序变量的对程序代码的检查来完成的。在很多情况下,在代码中的数据组织标识了抽象数据类型,例如,记录结构、文件、列表和其他数据结构经常提供类的最初指示。
Breuer和Lano建议了逆向工程类的下列方法:
(1)标识程序中记录关于全局数据结构(如,文件或数据库)的重要信息的标记和局部数据结构。
(2)定义在标记和局部数据结构与全局数据结构间的关系。例如,在文件空时某标记可能被设置,或某局部数据结构可能作为一个缓冲区,它包含从某中心数据库获取的最后100个记录。
(3)对每个表示一个数组或文件的变量(在程序中),列出所有与其有逻辑联系的其他变量。
这些步骤使得软件工程师能够在和全局数据结构交互的程序中标识类。
2.数据库结构
不管其逻辑组织和物理结构,一个数据库允许数据对象的定义并支持在对象间建立关系的方法。因此,将一个数据库模式再工程到另一个模式需要对现存对象和它们的关系的理解。
可运用下面步骤将现存数据模型定义为再工程一个新的数据库模型的先驱:
(1)通过复审在平坦文件数据库中的记录或在关系模式中的表建立一个初始的对象模型。可以获得被定义为模型的一部分的类。包含在记录或表中的项成为类的属性。
(2)确定候选键。检查属性,确定它们是否被用于指向另一个记录或表,那些作为指针的属性成为候选键。
(3)精化实验性的类。确定是否相似的类可被组合到某单个类中。
(4)定义一般化。检查具有很多相似属性的类,确定是否应该构造一个,将一般化类放在其头部的类层次。
(5)发现关联。建立类间的关联。
一旦知道了定义在上面步骤中的信息,则可以应用一系列变换将旧的数据库结构映射为新的数据库结构。
三、用户界面的逆向工程
随着高级的GUI对每种类型的基于计算机的产品和系统成为必需的,用户界面的重新开发已经成为最常见的再工程活动类型之一。但是,在用户界面可以被重建之前,应该进行一个逆向工程活动。
为了我们能完全地理解某现存的用户界面(UI),必须刻画界面的结构和行为。Merlo及其同事提出了三个基本问题,它们必须在UI的逆向工程开始前被回答:
(1)什么是界面必须处理的基本动作——例如,击键和按鼠标?
(2)什么是系统对这些动作的行为反应的简洁描述?
(3)“替代者”意味着什么?或更精确地说,什么界面的等价概念是相关的?
行为建模符号可以提供一种表示上面提出的头两个问题的答案的工具,对创建行为模型必需的多数信息可以通过观察现存界面的外部表现而得到,但是,对创建行为模型必需的附加信息必须从代码中抽取。
在描述发生在界面中的进程时,一个关键的不寻常点是某些对象必须准备对用户输入作出反应,这些输入不能被控制,只能被拒绝。这样,人们必须抓住这个概念,至少有两种并发的活动实体:系统和用户。第二,人们必须能够表达一定的“外部”选择,这些选择不在用户的控制之下。
对界面的描述使用代理和动作。一个代理是完成系统某方面功能的某种东西,动作允许代理间相互通信。
重构
重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。通常,重构并不修改整体的程序体系结构,它趋向于关注个体模块的设计细节以及定义在模块中的局部数据结构。如果重构扩展到模块边界之外并涉及软件体系结构,则重构变成了正向工程重构的三次法则:事不过三,三则重构。意思是说,一件事情,第一次只管去做,第二次做类似的事情会产生反感,但无论如何还是做了,第三次再做类似的事情,就应该重构。所以,应该在添加新功能时进行重构、在修改缺陷时进行重构以及在代码复审时进行重构。
一、代码重构
代码重构指对软件代码做任何更改以增加可读性或者简化结构而不影响输出结果。
重构既不修正错误,又不增加新的功能性。反而它是用于提高代码的可读性或者改变代码内部结构与设计,并且移除死代码,使其在将来更容易被维护。重构代码可以是结构层面抑或是语意层面,不同的重构手段在施行时,可能是结构的调整或是语意的转换,但前提是不影响代码在转换前后的行为。特别是在现有的程序的结构下,给一个程序增加一个新的行为可能会非常困难,因此开发人员可能先重构这部分代码,使加入新的行为变得容易。
二、数据重构
为取得正确的数据模式和作出正确的预测,在数据挖掘过程中必须将经过处理的数据恢复原来的分布特征称为数据重构。
在开始数据重构前,必须先进行分析源代码的逆向工程活动。评估所有包含数据定义、文件描述、I/O以及接口描述的程序设计语言语句,目的是抽取数据项和对象,获取关于数据流的信息,以及理解现存的已实现的数据结构。该活动有时被称为数据分析。
一旦数据分析已完成,则开始数据重设计。作为其最简单的形式,数据记录标准化澄清数据定义以达到在现存数据结构或文件格式中的数据项名或物理记录格式间的一致性。另一种重设计形式称为数据名合理化,保证所有数据命名约定遵从局部标准并且当数据在程序中流动时别名被删除。
当重构超出标准化和合理化的范畴时,对现存数据结构进行物理修改以使得数据设计更为有效。这可能意味着从一种文件格式到另一种文件格式的转换,或在某些情况下,从一种类型的数据库到另一种类型数据库的转换。
8、面向对象方法
面向对象方法学概述
一、从传统软件工程到面向对象软件工程
传统软件工程方法在面向对象软件工程方法出现以前,一直是标准的和最常用的软件工程方法,目前软件开发大部分仍然沿用传统的软件工程方法。传统软件工程方法主要指结构化软件工程方法,以下所说传统软件工程方法均指结构化软件工程方法,传统软件工程方法对应的软件过程模型(或者软件生命周期模型)通常是瀑布模型,可以划分为以下阶段:
①问题定义。
②可行性研究。
③需求分析。
④总体设计。
⑤详细设计。
⑥编码(编程)和单元测试。
⑦综合测试。
⑧软件维护。
目前通常将通用的传统软件工程方法划分为以下阶段:
①需求分析。
②总体设计。
③详细设计。
④编程。
⑤测试。
⑥软件维护。
目前较新的部分文献则将传统软件工程方法划分为以下阶段:
①系统工程。
②分析。
③设计(包括总体设计、详细设计和编程)。
④测试。
⑤软件维护。
从上述对传统软件工程方法的划分可以看出,传统软件工程方法的前期工作主要集中在分析(需求分析)和设计(包括总体设计和详细设计)阶段,实际上分析和设计是传统软件工程方法中最为重要的阶段。传统软件工程方法以功能、数据和数据流进行分析,实际上传统软件工程方法是基于数据(包括数据存储和数据处理,软件的功能也是针对数据进行处理)的。在传统软件工程方法的分析阶段,所使用的主要工具有数据字典(DD)、实体—关系图(ERD)、数据流图(DFD)和状态—变换图(STD);在传统软件工程方法的设计阶段,所使用的主要工具有针对总体设计的模块结构图(MSD)和针对详细设计的流程图,在目前实际的软件开发中,通常不使用流程图,而直接通过编程完成详细设计,所以较新的部分文献中,设计阶段包括了总体设计、详细设计和编程阶段,之间的界限较模糊。
传统软件工程方法的主要缺点是在分析阶段以功能、数据和数据流进行分析,对问题域的认识和描述不是以问题域中的固有事物(这就相当于面向对象软件工程方法中的对象)作为基本单位并保持它们的原貌,这些方法的分析结果不能直接地映射到问题域;在设计阶段分析的结果——数据流图和设计的结果——模块结构图是两种不同的表示体系,从分析到设计的转换实际上没有可靠的转换规则,带有人为的随意性,导致为软件开发带来隐患和设计结果与问题域偏差大。应该说这些缺点为现代软件开发带来了很多问题,目前从导向上看来,面向对象软件工程方法大有取代传统软件工程方法的趋势。
面向对象软件工程方法(OOSE)是面向对象(OO)方法在软件工程领域的全面应用,面向对象软件工程方法目前有多种(流派),但目前面向对象软件工程方法对应的软件过程模型也可以划分为以下阶段:
①面向对象的分析(OOA)。
②面向对象的设计(OOD)。
③面向对象的编程(OOP)。
④面向对象的测试(OOT)。
⑤面向对象的软件维护(OOSM)。
OOA直接针对问题域中客观存在的各项事物设立OOA模型中的对象,用对象的属性和服务(方法)分别描述事物的静态特征和行为,OOA模型能够很好地映射问题域;OOA针对问题域运用OO方法,OOA模型独立于具体的实现,OOD则是针对系统的一个具体实现运用OO方法,OOA模型直接搬到OOD中,作为OOD的一个部分,针对具体实现补充一些与实现有关的部分。OOA到OOD不存在转换。从理论上说,面向对象软件工程方法更加适合现代软件开发。
二、了解面向对象的概念
面向对象(OO)其实是认识事物的一种方法,准确地说,它是一种以对象为中心的思维方式。图1是现实生活中对青蛙的认识方法,其实这个认识过程就是一个面向对象的过程。所以说,面向对象是贴近自然、贴近人们的认识过程的。
图1 用面向对象方法来认识青蛙
这一节介绍面向对象的几个主要概念:对象、类、封装、继承、多态性、消息、关联。
对象
在现实世界中,每个实体都是对象,对象是现实世界中一个个实际存在的事物,是构成客观世界的独立单位,它可以是有形的,也可以是无形的。每个对象都有它的静态的属性和动态的行为。例如,一位学生是一个对象,他有学号、姓名、性别、出生日期等属性。又如开会,那么这个“会”也是一个对象,它是抽象的、看不到的,但却是实际存在的。可见,对象是问题域中某个实体的抽象。
1.对象的定义
对象是系统中用于描述客观事物的实体,是构成系统的基本单位。每个对象由名字、一组属性和对这组属性进行操作的一组服务(或称为方法,Method)来定义。
属性和服务是构成对象的两个主要因素,即对象=属性+服务。属性是用于描述对象的静态特征的数据项,在C++语言中属性称为数据成员,一般通过封装在对象内部的数据存储来定义。如果对象的数据存储都赋了值,那么这个对象的状态就确定了。服务描述了对象执行的功能,用于描述对象的动态行为,在C++语言中服务称为成员函数。若通过消息传递,还可以为其他对象使用。对象属性的值只能通过执行对象的服务来改变。
2.对象的特点
(1)对象以数据为中心。所有操作都与对象的属性相关,而且操作的结果往往与当时所处的状态(数据的值)有关。
(2)对象是消息处理的主体。它与传统的数据有本质区别,对象不是被动地等待对它进行处理,而是进行处理的主体。为了完成某个操作,必须通过对象的公共接口向对象发送消息,请求公共接口执行对象的某个操作,以处理对象的私有数据,而不能从外部直接加工对象的私有数据。
(3)对象具有数据封装性。对象是一个黑盒,它的私有数据完全被封装在盒子中,对外隐蔽,对私有数据的访问只能通过公有操作进行。为了使用对象内部的私有数据,只需知道数据的取值范围和可以访问该数据的操作,无须知道数据的具体数据结构和操作的实现算法。
(4)模块独立性好。对象是由数据及可以对这些数据施加的操作所组成的统一体,故对象内部的各种成分彼此相关,联系紧密,内聚性强。又由于完成对象功能所需的数据和操作基本上都被封装在对象内部,它与外界的联系较小,因此,对象之间的耦合通常比较松散。
(5)对象具有并行性。不同的对象各自独立地处理自身的数据,彼此通过发送消息、传递消息来完成通信。
类
根据抽象的原则对客观事物进行归纳和划分,并且只关注与当前目标相关的特征,从而就把具有相同特征的事物归为了一个类。事实上,类是一个抽象的概念。
准确地说,类是具有相同属性和相同操作(服务)的对象的集合。它包括属性和操作(注:类的服务和操作只是叫法上的区别)。
例(图2):客观世界中的每一匹马都属于动物类,其中的一匹马就是动物类的一个实例,即一个动物对象。
图2 类的实例
封装
封装是指按照信息屏蔽的原则,把对象的属性和操作结合在一起,构成一个独立的对象。
在面向对象的方法中,最重要的一个思想是:外部对象不能直接操作对象的属性,只能使用对象提供的服务。而封装就是实现这一思想的途径。
封装的作用在于,它保护了类的具体实现,隐藏了用户无需关心的细节,同时对用户体现出来相同的接口(即操作方法),从而提高了可复用性。
例如,我们使用电视机,由于良好的封装,我们就无需关心电视机是怎么接收视频信号的、怎么样播放出来的。另外,由于这一封装特性,无论电视机是通过有线通信,还是通过微波通信来接收节目,我们看电视时的操作都是相同的。
继承
继承是面向对象描述类之间相似性的重要机制,体现了类的层次关系。继承是在现存类定义的基础上定义新类的技术,现存类称为父类(一般类、基类),新类称为子类(特殊类、派生类)。面向对象软件技术中许多突出的优点和强有力的功能都来源于把类组成一个层次结构的系统(类等级)。一个类的上层可以有父类,下层可以有子类,这种层次结构系统的一个重要性质是继承性,一个子类直接继承其父类的全部描述。图6-1描述了实现继承机制的原理。
图3中以X、Y两个类为例,其中Y类是从X类派生出来的子类,它除了继承父类的特性外,还可以具有自己定义的特性。当创建X类的实例xl时,xl以X类为样板建立实例变量(在内存中分配所需空间),但是它并不从X类中复制所定义的方法。当创建Y类的实例yl时,既要以Y为样板建立实例变量,又要以X类为样板建立实例变量,yl所能执行的操作既有Y类中定义的方法,又有X类中定义的方法,这就是继承。当然,如果Y类中又定义了和X类中同名的数据或操作,则yl仅使用Y类中定义的这个数据或操作;除非采用特别措施,否则X类中与之同名的数据或操作在yl中就不能使用。
图3 继承机制的原理
继承具有传递性,如果Z类继承Y类,Y类继承X类,则Z类继承X类。因此,一个子类实际上继承了在它上层的全部基类的所有描述,也就是说,属于某类的对象除了具有该类所描述的性质外,还具有类等级中该类上层全部基类描述的一切性质。当一个类只允许有一个父类时,也就是说,当类等级为树形结构时,类的继承是单继承;当允许一个类有多个父类时,类的继承是多重继承。多重继承的类可以组合多个父类的性质构成所需的性质,因此功能更强,但是使用多重继承的类时要注意避免二义性。
继承性使得相似的对象可以共享程序代码和数据结构,从而大大减少了程序中的冗余信息。使用从原有类派生出新的子类的办法,使得对软件的修改变得比过去容易得多。当需要扩充原有的功能时,派生类的方法可以调用基类的方法,并在此基础上添加必要的程序代码;当需要完全改变原有操作的算法时,可以在派生类中实现一个与基类方法同名而算法不同的方法;当需要增加新的功能时,可以在派生类中实现一个新的方法。有了继承性,还可以用把已有的一般性的解加具体化的办法,来达到软件重用的目的。
消息
消息就是一个对象向另一个对象传递的信息。通常,一个消息由接收消息对象的标识、消息名、零个或多个变元组成。当一个消息发送给某个对象时,包含要求接收对象去执行某些活动的信息。接收到信息的对象经过解释,然后予以响应,这种通信机制称为消息传递。发送消息的对象不需要知道接收消息的对象如何响应请求。
例如,MyCircle是一个半径为3cm、圆心坐标为(200,200)的Circle类的对象,也就是Circle类的一个实例,当要求它以红色在屏幕上显示时,在C++语言中应该向它发送下列消息:
MyCircle.Show(RED);其中,MyCircle是接收消息的对象的名字,Show是消息名,RED是消息的变元。当MyCircle接收到该消息后,将执行在Circle类中定义的Show操作。
多态性
多态性是指一般类中定义的属性和服务,在特殊类中不改变其名字,但通过各自不同的实现后,可以具有不同的数据类型或具有不同的行为。
例如,一个绘图系统中类的多态性(图4):
图4 多态性
当向图形对象发送消息进行绘图服务请求后,图形对象会自动判断自己的所属类然后执行相应的绘图服务。
结构与连接
一个系统一般由很多对象组成,对象之间并不是互相孤立的,而是存在着各种各样的关系。
一般而言,这些关系可以分为:部分与整体的关系、一般与特殊的关系、实例连接的关系、消息连接的关系。
一、部分与整体的关系
对象之间存在部分与整体的结构关系。例如,CPU是PC机的一个部分,PC机由CPU、内存、硬盘、显示器、键盘等组成。在这里,PC就是整体,CPU就是这个整体的一个部分(图5)。
图5 部分与整体的关系
在部分与整体的关系中,部分构成整体存在两种构建方式:组合和聚合。
在组合关系中,一个部分对象只能属于唯一的一个整体对象。上面PC机就是一个组合的例子:CPU只能属于一个PC机。组合关系中部分和整体的关系非常紧密。
相对而言,在聚合关系中,则比较松散,它的特点是:一个部分对象可以属于几个整体对象。
二、一般与特殊的关系
对象之间存在着一般和特殊的结构关系,也就是说它们存在继承关系,这一点我们已经做过讨论。很多时候也将这种关系称为泛化与特化的关系。
三、实例连接关系
实例连接表现了对象之间的静态联系,它通过对象的属性来表现出对象之间的依赖关系。对象之间的实例连接称为链接,对象类之间的实例连接称为关联。
四、消息连接的关系
消息连接表现了对象之间的动态联系,它表现了这样一种联系:一个对象发送消息请求另一个对象的服务,接收消息的对象响应消息,执行相应的服务。
统一建模语言UML
UML是软件界第一个统一的建模语言,该方法结合了Booch方法,OMT方法和OOSE方法的优点,统一了符号体系,并从其他的方法和工程实践中吸收了许多经过实际检验的概念和技术。UML是OMG(对象管理组织,官方网站是http://www.omg.org)的公开标准之一。UML的官方网站是http://www.uml.org。
UML是在多种面向对象建模方法的基础上发展起来的建模语言,主要用于软件密集型系统的建模。它的演化,可以按其性质划分为以下几个阶段:最初的阶段是专家的联合行动,由三位面向对象方法学家Booch、Rumbaugh、Jacobson将他们各自的方法结合在一起,形成UML 0.9;第二阶段是公司的联合行动,由十几家公司组成的“UML伙伴组织”将各自的意见加入UML,形成UML 1.0和UML 1.1,并向OMG申请成为建模语言规范的提案;第三阶段是在OMG控制下的修订与改进,OMG于1997年11月正式采纳UML 1.1作为建模语言规范,然后成立任务组进行不断的修订,并产生了UML 1.2、UML 1.3、UML 2.0、UML 2.2、UML 2.3等版本。
UML的特点
1.统一标准
UML统一了Booch方法、OMT方法、OOSE方法等方法中的基本概念,已成为工业标准化组织OMG的正式标准,提供了标准的面向对象的模型元素的定义和表示法,有标准的语言工具可用。
2.面向对象
UML支持面向对象的主要概念,提供了一批基本的模型元素的表示图形和方法,能简明地表达面向对象的各种概念和模型元素。
图1 UML的发展历史
3.可视化、表达能力强
系统的逻辑模型或实现模型都能用UML模型清晰的表示,可用于复杂软件系统的建模。
4.易掌握、易用
UML的概念明确,建模表示法简洁明了,图形结构清晰,易于掌握使用。着重学习三个方面的主要内容:UML的基本模型元素、组织模型元素的规则、UML语言的公共机制。
5.独立于过程
UML是系统建模语言,不依赖特定的开发过程。
6.与编程语言的关系
用C++、Java等编程语言可以实现一个系统。支持UML的一些CASE工具(如Rose)可以根据UML所建立的系统模型自动产生C++、Java等代码框架,还支持这些程序的测试和配置管理等环节的工作。
UML的模型元素
UML的模型元素主要包括三个方面,即基本构造块(Basic Building Blocks)、支配这些构造块放在一起的规则(Rules)和一些运用于整个UML的公共机制(Common Mechanisms)。其中,UML基本构造块包含事物(Things)、关系(Relationships)和图(Diagrams)三种类型。
1.事物
UML中将各种事物归纳为四类,即结构事物、行为事物、分组事物和注释事物。
1)结构事物
结构事物是UML模型的静态部分,主要用于描述概念元素或物理元素,包括类、接口(Interface)、主动类(Active Class)、用例(Use Case)、参与者(Actor)、协作(Collaboration)、构件(Component)、节点(Node)和制品(Artifact)。
(1)类(Class)
类由三部分组成,即类名、属性和操作。UML中的类用矩形表示顶部区域显示类名,中间区域列出类的属性,底部区域列出类的操作。此外,绘制类元素时,可根据建模实际情况隐藏类的属性或操作部分。
(2)接口
接口描述了一个类或构件的一组外部可用的服务(操作)集。接口定义的是一组操作的描述,而不是操作的实现。一般把接口画成从实现它的类或构件引出的棒糖形。接口体现了使用与实现分离的原则。接口很少单独存在,而是依附于实现接口的类或构件。
(3)主动类
主动类是其对象拥有一个或多个进程或线程的类。它是一种特殊的类,UML引入主动类的目的是在实际开发中需要一些类能够起到启动控制活动的作用。主动类的对象所表现的元素行为与其他元素的行为并发。在图形上,为了与普通类区分,UML 2.x中主动类用两侧加边框的矩形表示。UML 1.x中主动类用外边框为粗线条的矩形表示。
(4)用例
用例表示系统想要实现的行为,不关心这些行为是怎样实现的。在图形上,用例用一个仅包含其名字的实线椭圆表示。
(5)参与者
参与者也称为行动者或角色。参与者定义了一组与系统有信息交互关系的人、事、物,在图形上用一个简化的人形符号表示。
(6)协作
协作完成某个特定任务的一组类及其关联的集合,用于对用例的实现建模。在UML中,协作用虚线椭圆表示。可根据需要决定在椭圆内部是否画出参与协作的角色结构分栏,在实际应用中,协作就是某个用例的实现。
(7)构件
构件也称为组件,是系统中物理的、可替代的部件。它通常描述一些逻辑元素的物理包。在图形上,构件用一个带有小方框的矩形来示。
(8)节点
节点是系统在运行时存在的物理元素,代表一种计算资源,通常具有存储空间和执行能力,如一台服务器、打印机等。在UML中,节点用一个立方体表示。
2)行为事物
行为事物是UML模型的动态部分,包括交互(Interaction)、状态机(State Machine)两种,它们通常与各种结构元素如类、协作等相关。
(1)交互
交互由在特定的上、下文环境中共同完成一定任务的一组对象之间传递的消息组成。在UML中,交互的消息画成一条有向直线,并在上面标有操作名。交互涉及的元素包括消息、动作序列(由一个消息所引起的行为)和链(对象间的连接)。对象是类的实例,在使用时需要在其名字下边加下画线。在UML中,对象的表示分成有名对象和匿名对象。
(2)状态机
状态机描述了一个对象或一个交互在生存周期内响应事件所经历的状态序列。状态机涉及的元素包括状态、转换、事件活动等。其中,状态用圆角矩形来表示。
3)分组事物
分组事物是UML模型的组织部分,它的作用是降低模型的复杂性。
包(Package)是把模型元素组织成组的机制,结构事物、行为事物甚至其他分组事物都可以放进包内。包不像构件(仅在运行时存在),它纯粹是概念上的(仅在开发时存在)。
4)注释事物
注释事物是依附于一个元素或一组元素之上,对其进行约束或解释的简单符号。在UML中,主要的注释事物称为注释(Note)。
2.关系
UML中有4种关系:依赖(Dependency)、关联(Association)、泛化(Generalization)、实现(Realization)。这4种关系是UML模型中可以包含的基本关系,如图1所示。
图1 UML中的关系
1)依赖
依赖是两个事物间的语义关系,其中一个事物(独立事物)发生变化会影响另一个事物(依赖事物)的语义。它用一个虚线箭头表示。虚线箭头的方向从源事物指向目标事物,表示源事物依赖于目标事物。
(2)关联
关联是一种结构关系,它描述了两个或多个类的实例之间存在语义上的联系。在UML中,关联关系使用一条直线表示。
关联关系中还有两个特殊的关系,即聚集(Aggregation)和组合(Composition),它们都表示两个类之间的“整体-部分”关系,差别在于聚集中的部分可以独立于整体存在,而组合中的整体销毁时部分也将不复存在。在UML中,使用带空心菱形的直线表示聚集,使用带实心菱形的直线表示组合,并且菱形都指向整体类。
(3)泛化
泛化是一般(Generalization)类和特殊(Specialization)类之间的继承关系。泛化关系用带空心箭头的实线表示,箭头指向父元素。
(4)实现
实现是规格说明和它的实现之间的关系,也是类之间的语义关系,通常实现关系会在以下两种情况出现:一种是在接口和实现它们的类或构件之间;另一种是在用例和实现它们的协作之间。在UML中,实现关系用一条带空心箭头的虚线表示,箭头指向提供规格说明的元素。
3.图
UML 1.x中定义了9种图,UML 2.x标准将其进行了扩充,增加了3种新的图,表1对这些图做了简要说明。
表1 UML 2.x的正式图
4.UML规则
UML的语法和语义规则,主要体现在以下几个方面。
(1)命名(Name):为事物、关系、图起名字。
(2)范围(Scope):使名字具有特定含义的语境。UML 2.x中指属性或操作的静态标记。
(3)可见性(Visibility):这些名字以何种方式让其他成分看见并使用。UML中定义了public、protected、private、package四种可见性,如表2所示。
(4)完整性(Integrity):事物以何种方式正确、持续地互相联系。
(5)执行(Execution):解释运行或模拟动态模型的含义。
表2 UML可见性规则
5.通用机制
UML具有四种通用机制(Common Mechanism),即规格说明(Specifications)、修饰(Adornments)、通用划分(Common Divisions)和扩展机制(Extensibility Mechanisms)。通用机制使得建模过程更容易掌握,模型更容易理解和扩充。
1)规格说明
UML不仅是一种图形语言,在它的图形表示法的每个部分后面还有一个规则说明,用于对构造块的语法和语义进行文字叙述。UML的图形表示法用于对系统进行可视化,规格说明用于说明系统细节。把图形和规格说明分离,可以进行增量式的建模。首先画图,然后对该模型进行规格说明,或者直接创建规格说明;也可以对一个已存在的系统工程进行逆向工程,然后再创建作为这些规格说明的投影图。目前,很多UML建模工具,如Rose、Enterprise Architect等已经将这些功能集成。
2)修饰
UML中的大多数元素都有唯一和直接的图形表示符号,这些图形符号对元素最重要的方面提供了可视化的表示。但很多元素又包含了一些更具体的细节。为了更好地表示这些细节,可以把各种图形修饰符添加到元素的基本符号上,为模型元素增加语义。例如:类名用斜体字表示它是抽象类,+表示可见性为public。
3)通用划分
UML遵循面向对象系统建模中的一些共同的划分方法。主要包括以下两方面。
(1)型—实例的划分
该划分描述了一个通用描述符和单个元素项之间的对应关系。通用描述符称为型元素,它是元素的类目,含有类目名称和对内容的描述;单个元素项是类目的实例。一个型元素可以对应多个实例元素。典型的型—实例的划分就是类和对象。类是一种抽象,对象是类的一个具体实例;一个类可以产生多个对象,类定义了基本的属性和方法,每个对象具有不同的属性值。UML中采用与类相同的图形符号表示对象,但是对象名有下画线。类似的型—实例的划分还有用例和用例实例、节点和节点实例、构件和构件实例等。
(2)接口和实现的分离
接口声明了一个合约,而实现表示对该合约的具体实施,它负责如实地实现接口的完整语义。在UML中可以对接口和它的实现进行建模。
4)扩展机制
UML提供了构造型(Stereotype)、标记值(Tagged Value)和约束(Constraint)三种扩展机制。
(1)构造型
构造型又称为版型,它扩展了UML的词汇表,可用于创造新的构造块。该构造块必须从UML中已有的基本构造块上派生,解决特定问题。它只是在已有元素上增加新的语义,而不是增加新的文法结构,它能使UML具有更强大和灵活的表示能力。构造型可应用于所有类型的模型元素,如类、构件、节点、关系、包、操作等。UML预定义了一些版型,如接口是类的构造型,参与者是版型化的类,子系统是包的构造型等。
(2)标记值。
标记值是一个名称—值对,它代表UML定义信息以外的附加特性信息,通常用于存储项目管理信息,如元素作者、创建日期等。每个标记值用“tag=value”的方式显示,其中,tag是标记名,value是标记值。标记值可使用“{}”括起来直接放在UML元素中,也可和其他特性关键词一起放在一个注释符号中与元素相连。
(3)约束。
约束是用某种文本语言的陈述句表达模型元素的语义或限制,它使用“{}”括起来的字符串表示,一般放在相关元素旁边。约束内容可用自由文本表示,也可用对象约束语言(OCL)精确定义。
UML视图
UML利用若干视图从不同角度来观察和描述一个软件系统的体系结构,从某个角度观察到的系统就构成系统的一个视图。视图由多个图构成,它不是一个图表,而是在某一个抽象层上对系统的抽象表示。如果要为系统建立一个完整的模型图,需要定义一定数量的视图,每个视图表示系统的一个特殊的方面。另外,视图还把建模语言和系统开发时选择的方法或过程连接起来。
1.“4+1”视图
“4+1”视图如图1所示,其中,用例视图是核心。
图1 “4+1”视图
1)用例视图
作用:描述系统的功能需求,即被外部参与者所能观察到的功能,找出用例和参与者。
适用对象:用户、分析人员、设计人员、开发人员、测试人员。
表现图形:用例图、活动图、交互图、状态图。
2)设计视图
作用:表示系统的概念设计和子系统结构等,描述了用例视图中提出的系统功能的实现。它关注系统内部系统的静态结构和系统内部的动态协作关系。
适用对象:分析人员、设计人员、开发人员。
表现图形:类图、对象图、活动图、交互图、状态图。
3)交互视图
作用:描述系统不同部分之间的控制流,包括可能的并发机制和同步机制,主要针对系统性能、可伸缩性和吞吐量。交互视图对应于《UML用户指南》(第1版)中的进程视图(Process View)。
适用对象:开发人员、系统集成人员。
表现图形:与设计视图相同,但是侧重于主动类和它们之间流动的消息。
4)实现视图
作用:描述系统代码构件组织和实现模块及它们之间的依赖关系,即装配和发布系统的物理构件和文件。
适用对象:开发人员、设计人员。
表现图形:构件图、交互图、状态图、活动图。
5)部署视图
作用:描述组成系统的物理部件的分布、交付和安装,包含形成系统物理拓扑结构的节点。
适用对象:开发人员、系统集成人员、测试人员等。
表现图形:部署图、交互图、状态图、活动图。
2.UML视图与图
“4+1”视图最早由Philippe Kruchten提出,并将其作为软件体系结构的表示方法,由于比较合理,因此被广泛接受。但需要说明的是,UML中的视图并不是只有这5个视图,如果认为这些视图不能完全满足需要,用户可以自定义视图。在《UML用户指南》(第2版)中,将UML图划分为四大领域九种视图,如表1所示。
表1 UML图的四大领域和九种视图
3.UML图形分类
从使用角度可以将UML的13种图划分为结构模型(也称为结构图、静态模型)和行为模型(也称为行为图、动态模型)两大类,如图2所示。
图2 UML图形分类
就使用频率和重要性而言,类图(Class Diagram)、用例图(Use Case Diagram)和顺序图(Sequence Diagram)是UML图形中最关键的图。
类图
在面向对象系统的建模中所建立的最常见的图就是类图。类图从系统的逻辑视图展现了一组类、接口、协作和它们之间的关联、依赖和泛化等关系,反映系统的静态结构。主动类的类图给出了系统的静态进程视图。如图1所示,类图中通常包括下述内容。
图1 UML类图
1.类图中的建模元素
在类图中,UML建模元素包括:①类及其结构和行为;②接口;③协作;④关联、依赖、泛化关系;⑤多重性和导航指示符;⑥角色名字。
1)关联名
关联可以有名称,用于描述关联的性质和作用。通常,关联名是一个动词或动词短语。在类图中,不需要给每个关联都加上关联名。只有在需要明确地给关联提供角色名,或一个模型存在多个关联且要查阅、区别这些关联时才给出关联名。
图2中类Company和Person之间的关联如果不使用关联名,可以有多种解释。如果在关联上加了Works for关联名,则表示Company和Person之间是雇佣关系。此外,可以提供一个阅读导向箭头(实心三角形)指示阅读关联名称的方向,但并不表示可见性或关联的导航方向。
图2 类图中的关联名
2)多重性
若类A和类B之间有关联关系,多重性(Multiplicity)定义了类A有多少个实例可以和类B的一个实例关联。常用的多重性表示方法如表1所示。
表1 常用的多重性表示法
说明:UML中用*表示不确切的最大数,Rose中用n表示。
3)角色名字
当一个类与另一个类发生关联关系时,每个类通常在关联中都扮演某种角色。角色是关联关系中一个类对另一个类所表现出来的职责。角色的名称是名词或名词短语。如果在关联上没有标出角色名字,则隐含地用类名作为角色名。图3中Person类以Employee的角色、Company类以Employer的角色参与关联。
图3 关联的角色名字
2.关联类
关联本身也可以有特性。如图3所示,在Company和Person之间的雇主和雇员关系中,有一个描述该关联特性的Contract类,它只应用于一对Company和Person。salary 和startDate是Contract类的属性,描述的是Company类和Person类之间的关联关系,而不是描述Company类或Person类的属性。在UML中,把这种情况建模为关联类。关联类可以进一步描述关联关系的属性、操作及其他信息。关联类是一种具有关联特性和类特性的建模元素,可以把它看成是具有类特征的关联或是具有关联特征的类。关联类通过一条虚线与相应的关联连接。
3.限定关联
存在限定符(Qualifier)的关联称为限定关联(Qualifier Association),限定关联用于多重性为一对多或多对多的关联。其目的是把多重性从*降为1或0..1。限定符画成一个内标限制内容的小方块,链接在它所限定的类上。限定符用于从规模较大的相关对象集合中,依据限定符的值选择一个或多个对象。受限定值选中的对象是目标对象,如图4所示。
图4 关联类
图5 限定关联
图5表示一个Company中有许多Person,给出限定符employeeNo值后,就可以对应一个Person值或Null。受限定的对象是Company,目标对象是Person,多重性表示Person和(Company,employeeNo)之间的关系,而不是Person和Company之间的关系。
4.模板类
模板类又称为参数化的类,在诸如C++、Java语言中支持模板类,图6中给出了模板类OmnipotenceArray。
图6 模板类
用例图
用例图被称为系统的外部用户所能观察到的系统功能的模型图,呈现了参与者和用例,以及它们之间的关系,主要用于对系统、子系统或类的功能行为进行建模。用例图用于从用户角度描述系统功能,并指出各功能的操作者。对于系统开发人员来说,用例图是一个非常有价值的工具,用于从用户的观察视角来收集系统的需求是非常有效的。用例图是由参与者、用例及它们之间的关系构成的用于描述系统功能的视图。
用例建模的基本步骤是:确定参与者;确定用例;用例描述;用例正确性和完整性检查。
1.确定参与者
参与者是在系统外部与系统交互的人或事物,它以某种方式参与系统内用例的执行。它既可以是使用该系统的用户,也可以是与系统交互的其他外部系统、硬件设备或组织机构,甚至是时间等。在UML中,参与者用一个人形符号来表示,并具有唯一的名字。参与者之间可以存在泛化关系。
参与者的特性包含:参与者位于系统(边界)之外,而不是系统的一部分;参与者表示人或事物与系统交互时所扮演的角色,而不是特定的人或事物。
在获取用例之前,先要确定系统的参与者。在寻找过程中,可以询问如下问题帮助确定参与者。
(1)谁使用系统的主要功能?
(2)谁改变系统的数据?
(3)谁从系统获取数据?
(4)谁负责支持和维护系统?
(5)谁对系统运行结果感兴趣?
(6)谁需要系统的支持以完成日常工作任务?
(7)系统需要和哪些外部系统交互?
(8)系统需要控制哪些外部资源或硬件设备?
通常,凡是直接使用系统的人都可以确定为参与者。另外,在对参与者的建模过程中,除了需要关注参与者的特性外,还应注意:每个参与者应有简短的描述,从业务角度描述参与者是什么;参与者可以有属性和操作。
2.确定用例
1)用例需考虑的问题
接下来检查所有的参与者,并为每个参与者确定用例。用例需考虑以下问题:
(1)参与者希望系统提供什么样的功能?
(2)系统存储信息吗?参与者将要创建、读取、更新或删除什么信息?
(3)系统是否需要把自身内部状态的变化通知给参与者?
(4)系统必须知道哪些外部事件?参与者将怎么通知系统有这些事件?
(5)其他需要考虑的用例包括启动、关闭、诊断、安装、培训和改变业务过程。
2)用例的特点
用例定义了一组用例实例,其中每个实例都是系统执行的一系列动作,这些动作最终对参与者产生有价值的可观察结果。用例的特点如下。
(1)用例由一组实例组成。
(2)用例从使用系统的角度描述系统中的信息,即站在系统外部察看系统功能,而不考虑系统内容对此功能的具体实现。
(3)用例描述了用户提出的一些可见需求,对应一个具体的用户目标,即用例的执行结果对参与者是有意义的。例如,登录系统是一个有效的用例,但输入密码却不是,因为单纯的输入密码是没有意义的。
(4)用例是对系统行为的动态描述,属于动态建模部分。
(5)用例总是由参与者发起的,参与者的愿望和需求是用例存在的原因,不存在没有参与者的用例。
3)用例之间的关系
在UML中,用例用一个椭圆来表示,并具有唯一的名字。用例名使用动宾结构或主谓结构命名,描述其功能或服务的含义。用例之间可以存在泛化关系、包含关系和扩展关系。
(1)一个用例可以被列举为一个或多个子用例,这些特性称为用例泛化。泛化关系的三角箭头由子用例指向父用例。
(2)包含关系是指当多个用例中存在相同事件流时,可以把这些公共事件流抽象成为公共用例,这个公共用例称为抽象用例,而原始用例称为基本用例。基本用例和抽象用例之间是包含关系。在UML中,包含关系表示为虚线箭头加版型《include》。箭头从基本用例指向抽象用例。
(3)扩展关系表示基本用例在由扩展用例间接说明的一个位置上隐式地合并了另一个用例(扩展用例)的行为。在UML中,扩展关系表示为虚线箭头加版型《extend》。箭头从扩展用例指向基本用例。
3.用例描述
采用文本描述用例的详细内容。用例描述应当包括以下项目。
(1)简要说明:简要介绍该用例的作用和目的。
(2)事件流:包括基本流和备选流,事件流应该表示所有的场景。
(3)用例场景:包括成功场景和失败场景,场景主要是由基本流和备选流组合而成的。
(4)前置条件:执行用例之前系统必须所处的状态。
(5)后置条件:用例执行完毕后系统可能处于的一组状态。
4.用例正确性和完整性检查
一个确认用例划分的简单方法是运用“WAVE”测试。
W(What to do):用例是否描述了应该做什么,而不是如何做?
A(Actor’s point of view):用例的描述是否采取了参与者的视角?
V(Value for the actor):用例是否对参与者有价值?
E(Entire scenario):用例描述的事件流是否是一个完整场景?
顺序图
顺序图(Sequence Diagram,又称为时序图)描述了一组对象之间的交互方式,它表示完成某项行为的对象之间传递消息的时间顺序。顺序图由对象、生命线、控制焦点、消息等组成。其中,生命线是一条垂直的虚线,表示对象存在的时间;控制焦点是一个细长的矩形,表示对象执行一个操作所经历的时间段;消息是对象之间的一条水平箭头线,表示对象之间的通信。
顺序图的重点是显示对象之间发送消息的时间顺序。它也显示对象之间的交互,就是在系统执行时,某个指定时间点将发生的事情。顺序图由多个水平排列的对象组成,图中时间从上向下推移,并且顺序图显示的对象之间随着时间的推移而交换消息或函数。消息是用带消息箭头的直线表示的,并且它位于垂直对象生命线之间。时间说明及其他注释放到一个脚本中,并将其放置在顺序图页边的空白处。
顺序图的构造步骤如下。
(1)把参加交互的对象放在图的上方,横向排列。通常把发起交互的对象放在左边,较下级的对象依次放在右边。
(2)把这些对象发送和接收的消息纵向按时间顺序从上向下放置。这样就提供了控制流随时间推移的清晰的可视化轨迹。
协作图
协作图是一种交互图,强调的是发送和接收消息的对象之间的组织结构。一个合作图显示了一系列的对象和在这些对象之间的联系,以及对象间发送和接收的消息。对象通常是命名或匿名的类的实例,也可以代表其他事物的实例,如协作、组件和节点。使用协作图来说明系统的动态情况,对在一次交互中有意义的对象和对象之间的链建模。在UML中,协作图用几何排列来表示交互作用中的对象和链,附在链上的箭头代表消息,消息的发生顺序用消息箭头处的编号来说明。
顺序图与协作图都表示对象之间的交互作用,只是它们在语义上是完全等价的,而且可以没有任何语义损失的相互转化,但是顺序图和协作图两者所表示的侧重点是不同的:顺序图描述了对象交互过程中的时间顺序,但没有明确地表达对象之间的关系。而协作图描述了对象之间的关系,但时间顺序必须从顺序号中获得。顺序图着重体现对象间消息传递的时间顺序,协作图着重于哪些对象间有消息传递,表达了对象之间的静态连接关系。顺序图和协作图是同构的,它们相互之间可以转化而不损失信息,依靠工具协作图和顺序图可以互相转换。
状态图
状态图(State Diagram)用于描述对象对外部事件所做出响应的状态序列。状态图侧重于描述某个对象生命周期中的动态行为,包括对象在各个不同状态间的转移及触发这些状态转移的外部事件,即从状态到状态的控制流。状态图的组成元素包括状态、转换、活动和动作。
状态图通过对类对象的生存周期建立模型来描述对象随时间变化的动态行为。每一个对象都被看做是通过对事件进行探测并做出回应来与外界其他部分通信的独立的实体。事件表示对象可以探测到的事物的一种运动变化,如接收到从一个对象到另一个对象的调用或信号、某些值的改变或一个时间段的终结。任何影响对象的事物都可以是事件,真实世界所发生的事物的模型通过从外部世界到系统的信号来建造。
状态是给定类的对象的一组属性值,这组属性值对所发生的事件具有相同性质的反应。换言之,处于相同状态的对象对同一事件具有同样方式的反应,所以当给定状态下的多个对象在接收到相同事件时会执行相同的动作,然而处于不同状态下的对象会通过不同的动作对同一事件做出不同的反应。例如,当自动答复机处于处理事务状态或空闲状态时会对取消键做出不同的反应。
状态图一般由起始点、终止点、状态、转换、事件和活动组成。
活动图
活动图(Activity Diagram)描述用例或对象内部的工作过程。活动图的最大优点是支持并促进并行,这使活动图已成为工作流建模及多线程编程的重要工具。
图1 ATM系统的状态图
活动图常用的模型事物包括活动(Activity)、起始点(Start)、终止点(End)、控制流(Transition)、对象(Object)、条件判定(Decision)、分岔(Fork)、同步(Synchronization)、信息流和泳道(Swimlane)。
活动描述的是系统要完成的一个任务或要进行的一个过程,用一个圆角的矩形表示,并标上活动名;起始点描述活动图的开始状态,与状态图类似,用一个黑色的实心圆表示,活动图可以有多个起始点;终止点描述活动图的终止状态,用一个加圈的黑色实心圆表示,活动图可以有多个终止点;控制流描述活动之间的转换,用带箭头的实线段表示,箭头指向转移的活动;对象是活动图中参与的对象,它可以发送信号给活动或是接收活动的信号,也可以表示活动的输入/输出结果,对象的表示和对象图中的表示相同;条件判定描述分支,只有单个进入控制流和多个else控制流,条件判定用一个菱形表示;分岔描述并行行为,有一个进入控制流和多个输出控制流,在激活进入控制流时,所有输出控制流都并行进行;当存在并行行为时便需要同步,同步有多个输入控制流和一个输出控制流,并且在所有输入控制流都到达时才会产生输出,分岔和同步必须匹配,它们都用一条较粗的水平的或是垂直的实线段表示;信息流描述活动和对象的交互关系,对象可以作为活动的输入/输出,也可以作为一个实体,接收活动的信号或向活动发送信号,信息流用带箭头的虚线段表示,箭头标识信息流的方向;泳道描述的是活动图中的活动的分组,通常可以将活动按照某种标准分组,泳道把活动安排成一些用垂直线隔开的垂直区,每一区代表一个特定对象的所有职责。
图2是ATM系统的一个活动图,这个活动图以顾客插入卡作为开始,以顾客取卡作为结束。
图2 ATM系统取款活动图
构件图
构件图(Component Diagram)显示构件及它们之间的依赖关系。构件图专注于系统的静态实现视图。它与类图相关,通常把构件映射为一个或多个类、接口或协作。
一般来说,构件就是一个实际文件,可以有以下几种类型。
(1)源代码构件:一个源代码文件或与一个包对应的若干个源代码文件。
(2)二进制构件:一个目标码文件、一个静态的或动态的库文件。
(3)可执行构件:在一台处理器上可运行的一个可执行的程序单位。
构件图可以用于显示编译、链接或执行时构件之间的依赖关系,以及构件的接口和调用关系。
构件图包含的事物有构件、接口及其关系。图1是ATM系统的构件图。
图1 ATM系统的构件图
部署图
部署图(Deployment Diagram)描述了运行时处理节点(Node)和在这些节点上制品(Artifact)的配置。部署图显示了系统的硬件、安装在硬件上的软件,以及用于连接异构计算机之间的中间件。
部署图包含的事物有节点、包、构件、接口及它们之间的关系等。图1是ATM系统的部署图。
图1 ATM系统的部署图