本篇复习笔记对应的课本是《软件工程导论(第六版)》 张海藩
导读
主要内容
软件工程概述、软件危机现象及产生的原因、软件工程的概念、软件过程
教材中的页码:
教材第1页----第19页
课件中的页码
PPT第1页----第82页
名词术语
软件危机、软件工程、软件生命周期
重点
软件工程的概念
软件工程的基本原理
软件生命周期
瀑布模型
常用的软件开发模型
难点
软件工程的概念
软件工程方法学的三要素
思考题
在今后的开发中应如何做,才能避免软件危机
作业题
教材32页第一章习题1的第一题、第三题、第六题、第七题
“软件作坊” 加剧了软件危机的出现
⭐️软件危机
是指在计算机软件的开发和维护过程中所遇到的一系列严重问题。
软件危机包含下述两方面的问题:
(1) 对软件开发成本和进度的估计常常很不准确。为了赶进度和节约成本所采取的一些权宜之计又往往损害了软件产品的质量,从而不可避免地会引起用户的不满。
(2) 用户对“已完成的”软件系统不满意的现象经
常发生。软件开发人员和用户之间的信息交流往往很不充分,有行业盲区,常常导致最终产品不符合用户的实际需要
(3) 软件产品的质量往往靠不住。
软件的特性决定了软件的可靠性和质量保证是一个模糊的概念。
(4) 软件常常是不可维护的。
很多程序中的错误是非常难改正的,不太可能使这些程序适应新的硬件环境。
(5) 软件通常没有适当的文档资料。
软件不仅仅是程序,还应该有一整套文档资料。对于维护人员而言,这些文档资料更是必不可少的。缺乏必要的文档资料或者文档资料不合格,必然给软件开发和维护带来许多严重的困难和问题。
(6) 软件成本在计算机系统总成本中所占的比例逐
年上升。硬件成本逐年下降,而软件成本却持续上升。
(7) 软件开发生产率提高的速度,远远跟不上计算
机应用迅速普及深入的趋势。软件产品“供不应求”的现象使人类不能充分利用现代计算机硬件提供的巨大潜力。
在软件开发和维护的过程中存在这么多严重问题,一方面与软件本身的特点有关,另一方面也和软件开发与维护的方法不正确有关。
软件的逻辑特性决定了软件的开发、管理和控制过程都相当困难。软件的维护过程通常意味着改正或修改原来的设计,这就在客观上使得软件较难维护。
⭐️ 软件生命周期
一个软件从始到终,要经历一定时期,通常把软件
经历的这个时期称为软件生命周期。
IEEE对软件下的定义:计算机程序、方法、规则、相关的文档资料以及在计算机上运行程序时所必需的数据。其中,方法和规则通常是在文档中说明并在程序中实现的。
一个软件必须由一个完整的配置组成,事实上,软件是程序、数据及相关文档的完整集合。其中,程序是能够完成预定功能和性能的可执行的指令列;数据是使程序能够适当地处理信息的数据结构;文档是开发、使用和维护程序所需要的图文资料。更重要的是,必须充分认识到软件开发是一种组织良好、管理严密、各类人员协同配合、共同完成的工程项目。必须充分吸取和借鉴各种工程项目所积累的原理、概念、技术和方法,特别要吸取几十年来人类从事计算机硬件研究和开发的经验教训
为了解决软件危机,既要有技术措施(方法和工具),又要有必要的组织管理措施。软件工程正是从管理和技术两方面研究如何更好地开发和维护计算机软件的一门新兴学科。
⭐️ 软件工程的定义
NATO在1968年给出了软件工程的一个定义:“软件工程就是为了经济地获得可靠的且能在实际机器上有效地运行的软件,而建立和使用完善的工程原理。”
IEEE 在1993年给出了一个更全面更具体的定义:“软件工程是: ①把系统的、规范的、可度量的途径应用于软件开发、运行和维护过程,也就是把工程应用于软件; ②研究①中提到的途径。”
⭐️ 软件工程的本质特征
软件工作者提出了软件工程的7条基本原理。这7条原理是确保软件产品质量和开发效率的原理的最小集合。
软件工程包括技术和管理两方面的内容,是技术与管理紧密结合所形成的工程学科。所谓管理就是通过计划、组织和控制等一系列活动,合理地配置和使用各种资源,以达到既定目标的过程。
通常把在软件生命周期全过程中使用的一整套技术方法的集合称为方法学(methodology),也称为范型(paradigm)。
软件工程方法学包含3个要素:方法、工具和过程。其中,方法是完成软件开发的各项任务的技术方法,回答“怎样做”的问题;工具是为运用方法而提供的自动的或半自动的软件工程支撑环境;过程是为了获得高质量的软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。目前使用得最广泛的软件工程方法学,分别是传统方法学和面向对象方法学
⭐️ 软件生命周期
由软件定义、软件开发和软件维护3个时期组成,每个时期又划分成若干个阶段。
软件过程是为了获得高质量软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。
在完成开发任务时必须进行一些开发活动,并且使用适当的资源,在过程结束时将把输入转化为输出。
因此,ISO 9000把过程定义为:使用资源将输入转化为输出的活动所构成的系统。
“系统”的含义是广义的:“系统是相互关联或相互作用的一组要素。”
过程定义了运用方法的顺序、应该交付的文档资料、为保证软件质量和协调变化所需要采取的管理措施, 以及标志软件开发各个阶段任务完成的里程碑。没有一个适用于所有软件项目的任务集合。通常,一个任务集合包括一组软件工程任务、里程碑和应该交付的产品。使用生命周期模型描述软件过程。生命周期模型规定了把生命周期划分成哪些阶段及各个阶段的执行顺序,也称为过程模型。
快速原型是快速建立起来的可以在计算机上运行的程序,它所能完成的功能往往是最终产品能完成的功能的一个子集。如图1.4所示,快速原型模型的第一步是快速建立一个能反映用户主要需求的原型系统,让用户在计算机上试用它,通过实践来了解目标系统的概貌。
通常,用户试用原型系统之后会提出许多修改意见,开发人员按照用户的意见快速地修改原型系统,然
后再次请用户试用……一旦用户认为这个原型系统确实能做他们所需要的工作,开发人员便可据此书写规格说明文档,根据这份文档开发出的软件可以满足用户的真实需求。原型模型是不带反馈环的, 软件产品的开发基本上是线性顺序进行的。
能做到基本上线性顺序开发的主要原因如下:
(1) 原型系统已经通过与用户交互而得到验证,据此产生的规格说明文档正确地描述了用户需求,因此,在开发过程的后续阶段不会因为发现了规格说明文档的错误而进行较大的返工。
(2) 开发人员通过建立原型系统已经了解许多,因此,在设计和编码阶段发生错误的可能性也比较小。
快速原型的本质是“快速”。开发人员应该尽可能快地建造出原型系统,以加速软件开发过程,节约软件开发成本。原型的用途是获知用户的真正需求,一旦需求确定了,原型将被抛弃
增量模型也称为渐增模型,如图1.5所示。使用增量模型开发软件时,把软件产品作为一系列的增量构件来设计、编码、集成和测试。每个构件由多个相互作用的模块构成,并且能够完成特定的功能。使用增量模型时,第一个增量构件往往实现软件的基本需求,提供最核心的功能。第二个增量构件提供更完善的编辑和文档生成功能;第三个增量构件实现拼写和语法检查功能;第四个增量构件完成高级的页面排版功能。
把软件产品分解成增量构件时,应该使构件的规模适中,规模过大或过小都不好。采用瀑布模型或快速原型模型开发软件时,目标都是一次就把一个满足所有需求的产品提交给用户。增量模型则与之相反,它分批地逐步向用户提交产品,整个软件产品被分解成许多个增量构件,开发人员一个构件接一个构件地向用户提交产品。从第一个构件交付之日起,用户就能做一些有用的工作。能在较短时间内向用户提交可完成部分工作的产品,是增量模型的一个优点。增量模型的另一个优点是,逐步增加产品功能可以使用户有较充裕的时间学习和适应新产品,从而减少一个全新的软件可能给客户组织带来的冲击。使用增量模型的困难是,在把每个新的增量构件集成到现有软件体系结构中时,必须不破坏原来已经开发出的产品。必须把软件的体系结构设计得便于按这种方式进行扩充,向现有产品中加入新构件的过程必须简单、方便,软件体系结构必须是开放的。从长远观点看,具有开放结构的软件拥有真正的优势,这样的软件的可维护性明显好于封闭结构的软件
软件风险是任何软件开发项目中都普遍存在的实际问题,项目越大,承担该项目所冒的风险也越大。软件风险可能在不同程度上损害软件开发过程和软件产品质量。因此,在软件开发过程中必须及时识别和分析风险,并且采取适当措施以消除或减少风险的危害。构建原型是一种能使某些类型的风险降至最低的方法。螺旋模型的基本思想是,使用原型及其他方法来尽量降低风险。
螺旋模型有许多优点:对可选方案和约束条件的强调有利于已有软件的重用,也有助于把软件质量作为软件开发的一个重要目标;减少了过多测试(浪费资金)或测试不足(产品故障多)所带来的风险。螺旋模型主要适用于内部开发的大规模软件项目。如果进行风险分析的费用接近整个项目的经费预算,则风险分析是不可行的。事实上,项目越大,风险也越大,因此,进行风险分析的必要性也越大
本章首先介绍了软件危机产生的原因及其现象,然后介绍了软件工程的概念,并用生命周期方法学把软件生命周期划分为若干个相对独立的阶段,每个阶段完成一些确定的任务,交出最终的软件配置的一个或几个成分(文档或程序); 基本上按顺序完成各个阶段的任务,在完成每个阶段的任务时采用结构化技术和适当的软件工具;在每个阶段结束之前都进行严格的技术审查和管理复审。
把软件生命周期划分成问题定义、可行性研究、需求分析、总体设计、详细设计、编码和单元测试、综合测试以及运行维护等8个阶段。软件过程是为了获得高质量的软件产品所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。由于没有一个适用于所有软件项目的任务集合,科学、有效的软件过程应该定义一组适合于所承担的项目特点的任务集合。
使用软件过程模型简洁地描述软件过程,本章介绍了4种典型的软件过程模型。
瀑布模型历史悠久、广为人知,它的优势在于它是规范的、文档驱动的方法。
快速原型模型通过快速构建起一个可在计算机上运行的原型系统,让用户试用原型并收集用户反馈意见的办法,获取用户的真实需求。
增量模型具有可在软件开发的早期阶段使投资获得明显回报和较易维护的优点,但是,要求软件具有开放的结构是使用这种模型时固有的困难。
风险驱动的螺旋模型适用于内部开发的大型软件项目,但是,只有在开发人员具有风险分析和排除风险的经验及专门知识时,使用这种模型才会获得成功
1-1 什么是软件危机?它有哪些典型表现?为什么会出现软件危机?
软件危机是指计算机软件开发和维护过程中所遇到的一系列严重问题。
典型表现有:
(1)对软件开发成本和进度的估计常常很不准确
(2)软件产品的质量往往靠不住
(3)用户对已完成的软件系统不满意的现象经常发生
(4)软件常常是不可维护的
(5)软件中没有适当的文档资料
(6)软件成本在计算机系统总成本所占的比例逐年上升
(7)软件开发生产率提高的速度,往往跟不上计算机应用迅速普及深入的趋势
原因:
(1)软件本身独有的特点确实给开发和维护带来了困难
(2)与软件开发和维护的许多错误认识和做法的形成有关
(3)程序只是完整软件的一个组成部分
(4)轻视是一个最大的错误
1-3 什么是软件工程?它有哪些本质特性?怎样用软件工程消除软件危机?
软件工程概念:采用工程的概念、原理、技术和方法来开发和维护软件,把经过时间考验而验证正确的管理技术和当前能够得到的最好的技术方法结合起来,以经济的开发处高质量的软件并有效地维护它
本质特征:
(1)软件工程关注大型程序的构造
(2)软件工程的中心课题是控制复杂性
(3)软件经常变化
(4)开发软件的效率非常重要
(5)和谐的合作是开发软件的关键
(6)软件必须有效地支持它的用户
(7)在软件工程领域中往往是由具有一种文化背景的人替具有另一种文化背景的人创建产品
途径:
(1)彻底消除在计算机系统早期发展阶段形成的“软件就是程序”的错误观念
(2)必须组织良好、严格管理、各类人员协同配合,共同完成
(3)应该推广使用在实践中总结出来的开发软件的成功的技术和方法,并且研究探索更好的更有效的技术与方法
(4)应该开发和使用更好的开发工具
(5)既要有技术措施,又要有好的管理组织措施
1-6 什么是软件过程?它与软件工程方法学有何关系?
答:软件过程是指为了获得高质量软件产品,在软件工具支持下,由软件人员完成的一系列软件工程活动的框架。软件过程与软件工程方法学的关系:
软件过程:是一个为了获得高质量软件所需完成的任务的框架,也就是说软件过程规定了软件产品开发时完成各项任务的一系列工作步骤,包括中间产品、资源、角色及过程中采取的方法、工具等范畴。
软件工程方法学:通常把在软件生命周期的全过程中的一整套技术方法的集合称为方法学,也称范型。软件工程方法学包含三个要素:方法、工具和过程。
从这些两个定义可以看出,软件过程是软件工程方法学的一个要素而已!软件过程是软件工程方法学的3个重要组成部分之一。
1-7什么是软件生命周期模型?试比较瀑布模型、快速原型模型、增量模型和螺旋模型的优缺点,说明每种模型的适用范围
答:软件生命周期模型是跨越整个生存期的系统开发、运作和维护所实施的全部过程、活动和任务的结构框架。
瀑布模型
优点:它提供了一个模板,这个模板使得分析、设计、编码、测试和支持的方法可以在该模板下有一个共同的指导。虽然有不少缺陷但比在软件开发中随意的状态要好得多。
缺点:(1) 实际的项目大部分情况难以按照该模型给出的顺序进行,而且这种模型的迭代是间接的,这很容易由微小的变化而造成大的混乱。
(2) 经常情况下客户难以表达真正的需求,而这种模型却要求如此,这种模型是不欢迎具有二义性问题存在的。
(3) 客户要等到开发周期的晚期才能看到程序运行的测试版本,而在这时发现大的错误时,可能引起客户的惊慌,而后果也可能是灾难性的。
快速原型模型
优点:使用户能够感受到实际的系统,使开发者能够快速地构造出系统的框架。
缺点:产品的先天性不足,因为开发者常常需要做实现上的折中,可能采用不合适的操作系统或程序设计语言,以使原型能够尽快工作。
增量模型
优点:(1) 人员分配灵活,刚开始不用投入大量人力资源,当核心产品很受欢迎时,可增加人力实现下一个增量。
(2) 当配备的人员不能在设定的期限内完成产品时,它提供了一种先推出核心产品的途径,这样就可以先发布部分功能给客户,对客户起到镇静剂的作用。
缺点:(1) 至始至终开发者和客户纠缠在一起,直到完全版本出来。
(2) 适合于软件需求不明确、设计方案有一定风险的软件项目。
该模型具有一定的市场。
螺旋模型
优点:对于大型系统及软件的开发,这种模型是一个很好的方法。开发者和客户能够较好地对待和理解每一个演化级别上的风险。
缺点:(1) 需要相当的风险分析评估的专门技术,且成功依赖于这种技术。
(2) 很明显一个大的没有被发现的风险问题,将会导致问题的发生,可能导致演化的方法失去控制。
(3) 这种模型相对比较新,应用不广泛,其功效需要进一步的验证。
该模型适合于大型软件的开发
导读
主要内容
软件过程、软件开发模型、可行性研究
教材中的页码
教材第19页----第54页
课件中的页码
PPT第1章剩余部分,PPT第2章
名词术语
软件开发模型、可行性研究
重点
可行性研究的任务
可行性研究的过程
数据流图
数据字典
难点
数据流图的使用
数据字典
思考题
可行性研究在软件开发中的作用有多重要?
作业题
教材53页第二章习题2的第一题、第五题
可行性研究的目的不是解决问题,而是确定问题是否值得去解决。分析几种主要的可能解法的利弊,从而判断原定的系统规模和目标是否现实,系统完成后所能带来的效益是否大到值得投资开发这个系统的程度。因此,可行性研究实质上是要进行一次大大压缩简化了的系统分析和设计的过程,也就是在较高层次上以较抽象的方式进行的系统分析和设计的过程。首先需要进一步分析和澄清问题定义。在问题定义阶段初步确定规模和目标,如果对目标系统有任何约束和限制,也必须把它们清楚地列举出来。在澄清了问题定义之后,分析员应该导出系统的逻辑模型,研究若干种可供选择的主要解法。对每种解法都应该仔细研究它的可行性。一般说来,至少应该从下述四方面研究每种解法的可行性
(1) 技术可行性
(2) 经济可行性
(3) 操作可行性
(4) 市场可行性
必要时还应该从法律、社会效益等更广泛的方面研究每种解法的可行性。分析员应该为每个可行的解法制定一个粗略的实现进度。
可行性研究最根本的任务是对以后的行动方针提出建议。如果问题没有可行的解,分析员应该建议停止这项开发工程,以避免浪费;如果问题值得解,分析员应该推荐一个较好的解决方案,并且为工程制定一个初步的计划。可行性研究可行性研究需要的时间长短取决于工程的规模。一般说来,可行性研究的成本只是预期的工程总成本的5%~10%。
系统流程图是概括地描绘物理系统的传统工具。它的基本思想是用图形符号以黑盒子形式描绘组成系统的每个部件(程序,文档,数据库,人工过程等)。系统流程图表达的是数据在系统各部件之间流动的情况,而不是对数据进行加工处理的控制过程。
某装配厂有一座存放零件的仓库,仓库中现有的各种零件的数量以及每种零件的库存量临界值等数据记录在库存清单主文件中。当仓库中零件数量有变化时,应该及时修改库存清单主文件,如果哪种零件的库存量少于它的库存量临界值,则应该报告给采购部门以便定货,规定每天向采购部门送一次定货报告。
该装配厂使用一台小型计算机处理更新库存清单主文件和产生定货报告的任务。零件库存量的每一次变化称为一个事务,由放在仓库中的CRT终端输入到计算机中;系统中的库存清单程序对事务进行处理,更新存储在磁盘上的库存清单主文件,并且把必要的定货信息写在磁带上。最后,每天由报告生成程序读一次磁带,并且打印出定货报告。图2.3 的系统流程图描绘了上述系统的概貌。图中每个符号用黑盒子形式定义了组成系统的一个部件,箭头确定了信息通过系统的逻辑路径。系统流程图的习惯画法是使信息在图中从顶向下或从左向右流动。
面对复杂的系统时,一个比较好的方法是分层次地描绘这个系统。首先用一张高层次的系统流程图描绘系统总体概貌,表明系统的关键功能;然后分别把每个关键功能扩展到适当的详细程度。
数据流图(DFD)是一种图形化技术,它描绘信息流和数据从输入到输出的过程中所经受的变换。在数据流图中没有任何具体的物理部件,它只是描绘数据在软件中****流动和被处理的逻辑过程。数据流图是分析员与用户之间极好的通信工具。此外,设计数据流图时只需考虑系统必须完成的基本逻辑功能,所以是进行软件设计很好的工具。
数据流图有四种基本符号:
正方形(或立方体)表示数据的源点或终点;
圆角矩形(或圆形)代表变换数据的处理;
开口矩形(或两条平行横线)代表数据存储;
箭头表示数据流,即特定数据的流动方向。
数据存储和数据流都是数据,仅仅所处的状态不同。
数据存储是处于静止状态的数据,数据流是处于运动中的数据。通常在数据流图中忽略出错处理,也不包括诸如打开或关闭文件之类的内务处理。数据流图的基本要点是描绘“做什么”而不考虑“怎样做”
假设一家工厂的采购部每天需要一张定货报表,报表按零件编号排序,表中列出所有需要再次定货的件。对于每个需要再次定货的零件应该列出下述数据:零件编号,零件名称,定货数量,目前价格,主要供应者,次要供应者。零件入库或出库称为事务,通过放在仓库中的CRT终端把事务报告给定货系统。当某种零件的库存数量少于库存量临界值时就应该再次定货。数据流图有4种成分:源点或终点,处理,数据存储和数据流。因此,第一步可以从问题描述中提取数据流图的4种成分: 首先考虑数据的源点和终点,从上面对系统的描述可以知道“采购部每天需要一张定货报表”,“通过放在仓库中的CRT终端把事务报告给定货系统”,所以采购员是数据终点,而仓库管理员是数据源点。接下来考虑处理,再一次阅读问题描述,“采部需要报表”,显然他们还没有这种报表,因此必须有一个用于产生报表的处理。事务的后果是改变零件库存量,然而任何改变数据的操作都是处理,因此对事务进行的加工是另一个处理。
接下来应该对功能级数据流图中描绘的系统主要功能进一步细化。考虑通过系统的逻辑数据流:当发
生一个事务时必须首先接收它;随后按照事务的内容修改库存清单;最后如果更新后的库存量少于库存量临界值时,则应该再次定货,也就是需要处理定货信息。因此,把“处理事务”这个功能分解为下述3个步骤,这在逻辑上是合理的:“接收事务”、“更新库存清单”和“处理定货”(图2.7)。当对数据流图分层细化时必须保持信息连续性,也就是说,当把一个处理分解为一系列处理时,分解前和分解后的输入输出数据流必须相同。
数据流图中每个成分的命名是否恰当,直接影响数据流图的可理解性。
画数据流图的基本目的是利用它作为交流信息的工具。分析员把他对现有系统的认识或对目标系统的设想用数据流图描绘出来,供有关人员审查确认。数据流图应该分层,并且在把功能级数据流图细化后得到的处理超过9个时,应该采用画分图的办法,也就是把每个主要功能都细化为一张数据流分图,而原有的功能级数据流图用来描绘系统的整体逻辑概貌。
数据流图的另一个主要用途是作为分析和设计的工具。分析员在研究现有的系统时常用系统流程图表达他对这个系统的认识,这种描绘方法形象具体,比较容易验证它的正确性;数据流图着重描绘系统所完成的功能而不是系统的物理实现方案。当用数据流图辅助物理系统的设计时,以图中不同处理的定时要求为指南,能够在数据流图上画出许多组自动化边界,每组自动化边界可能意味着一个不同的物理系统,因此可以根据系统的逻辑模型考虑系统的物理实现。例如,考虑图2.7,事务随时可能发生,因此处理1.1(“接收事务”)必须是联机的;采购员每天需要一次定货报表,因此处理2(“产生报表”)应该以批量方式进行。问题描述并没有对其他处理施加限制,例如,可以联机地接收事务并放入队列中,然而更新库存清单、处理定货和产生报表以批量方式进行(图2.8)。当然,这种方案需要增加一个数据存储以存放事务数据
改变自动化边界,把处理1.1,1.2和1.3放在同一个边界内(图2.9),这个系统将联机地接收事务、更新库存清单和处理定货及输出定货信息;然而处理2将以批量方式产生定货报表。还能设想出建立自动化边界的其他方案吗?如果把处理1.1和处理1.2放在一个自动化边界内,把处理1.3和处理2放在另一个边界内,意味着什么样的物理系统呢?
数据字典是关于数据的信息的集合,也就是对数据流图中包含的所有元素的定义的集合。
数据字典的作用是在软件分析和设计的过程中给人提供关于数据的描述信息。数据流图和数据字典共同构成系统的逻辑模型,没有数据字典数据流图就不严格,然而没有数据流图数据字典也难于发挥作用。只有数据流图和对数据流图中每个元素的精确定义放在一起,才能共同构成系统的规格说明。
一般说来,数据字典应该由对下列3类元素的定义组成:
(1) 数据流
(2) 数据流分量(即数据元素)
(3) 数据存储
除了数据定义之外,数据字典中还应该包含关于数据的一些其他信息。典型的情况是,在数据字典中记录数据元素的下列信息: 一般信息(名字,别名,描述等等),定义(数据类型,长度,结构等等),使用特点(值的范围,使用频率,使用方式——输入、输出、本地,条件值等等),控制信息(来源,用户,使用它的程序,改变权,使用权等等)和分组信息(父结构,从属结构,物理位置——记录、文件和数据库等等)。
数据元素的别名就是该元素的其他等价的名字,出现别名主要有下述3个原因:
(1) 对于同样的数据,不同的用户使用了不同的名字;
(2) 一个分析员在不同时期对同一个数据使用了不同的名字;
(3) 两个分析员分别分析同一个数据流时,使用了不同的名字。
虽然应该尽量减少出现别名,但是不可能完全消除别名
数据字典中的定义就是对数据自顶向下的分解。一般说来,当分解到不需要进一步定义,每个和工程有关的人也都清楚其含义的元素时,这种分解过程就完成了。由数据元素组成数据的方式有下述四种类型:
(1) 顺序 即以确定次序连接两个或多个分量;
(2) 选择 即从两个或多个可能的元素中选取一个;
(3) 重复 即把指定的分量重复零次或多次。
(4) 可选 即一个分量是可有可无的(重复零次或一次)。
为了说明重复次数,重复算符通常和重复次数的上下限同时使用(当上下限相同时表示重复次数固定)。
为了更加清晰简洁,建议采用下列符号:
=
意思是等价于(或定义为);
+
意思是和(连接两个分量);
[ ]
意思是或(即,从方括弧内列出的若干个分量中选择一个),通常用“|”号隔开供选择的分量;
{ }
意思是重复(即,重复花括弧内的分量);
( )
意思是可选(即,圆括弧里的分量可有可无)。
常常使用上限和下限进一步注释表示重复的花括弧。一种注释方法是在开括弧的左边用上角标和下角标分别表明重复的上限和下限;另一种注释方法是在开括弧左侧标明重复的下限,在闭括弧的右侧标明重复的上限。下面举例说明上述定义数据的符号的使用方法:某程序设计语言规定,用户说明的标识符是长度不超过8个字符的字符串,其中第一个字符必须是字母字符,随后的字符既可以是字母字符也可以是数字字符。使用上面讲过的符号,我们可以像下面那样定义标识符:
标识符 = 字母字符 + 字母数字串
字母数字串 = 0{字母或数字}7
字母或数字 = [字母字符 | 数字字符]
字母字符 = a … z
数字字符 = 0 … 9
数据字典最重要的用途是作为分析阶段的工具。在数据字典中建立的一组严密一致的定义很有助于改进分析员和用户之间的通信,将消除许多可能的误解。对数据的这一系列严密一致的定义也有助于改进在不同的开发人员或不同的开发小组之间的通信。如果要求所有开发人员都根据公共的数据字典描述数据和设计模块,则能避免许多接口问题。数据字典是开发数据库的第一步,而且是很有价值的一步。
目前,数据字典几乎总是作为CASE“结构化分析与设计工具”的一部分实现的。在开发大型软件系统的过程中,数据字典的规模和复杂程度迅速增加,人工维护数据字典几乎是不可能的。如果在开发小型软件系统时暂时没有数据字典处理程序,可采用卡片形式书写数据字典,每张卡片上保存描述一个数据的信息。这样做更新和修改起来比较方便,而且能单独处理描述每个数据的信息。每张卡片上主要应该包含下述这样一些信息:名字、别名、描述、定义、位置。
开发一个软件系统是一种投资,期望将来获得更大的经济效益。经济效益通常表现为**减少运行费用或(和)增加收入。**但是,投资开发新系统往往要冒一定风险,系统的开发成本可能比预计的高,效益可能比预期的低。效益分析的目的正是要从经济角度分析开发一个特定的新系统是否划算,从而帮助客户组织的负责人正确地作出是否投资于这项开发工程的决定。为了对比成本和效益,首先需要估计它们的数量。
软件开发成本主要表现为人力消耗(乘以平均工资则得到开发费用)。成本估计不是精确的科学,因此应该使用几种不同的估计技术以便相互校验。下面简单介绍3种估算技术。
成本/效益分析的第一步是估计开发成本、运行费用和新系统将带来的经济效益。运行费用取决于系统的操作费用(操作员人数,工作时间,消耗的物资等等)和维护费用。系统的经济效益等于因使用新系统而增加的收入加上使用新系统可以节省的运行费用。因为运行费用和经济效益两者在软件的整个生命周期内都存在,总的效益和生命周期的长度有关,所以应该合理地估计软件的寿命。
虽然许多系统在开发时预期生命周期长达10年以上,但是时间越长系统被废弃的可能性也越大,为了保险起见,以后在进行成本/效益分析时一律假设生命周期为5年。应该比较新系统的开发成本和经济效益,以便从经济角度判断这个系统是否值得投资,但是,投资是现在进行的,效益是将来获得的,不能简单地比较成本和效益,应该考虑货币的时间价值。
可行性研究进一步探讨问题定义阶段所确定的问题是否有可行的解。在对问题正确定义的基础上,通过分析问题,导出试探性的解,然后复查并修正问题定义,再次分析问题,改进提出的解法……。经过定义问题、分析问题、提出解法的反复过程,最终提出一个符合系统目标的高层次的逻辑模型。然后根据系统的这个逻辑模型设想各种可能的物理系统,并且从技术、经济和操作等各方面分析这些物理系统的可行性。最后,系统分析员提出一个推荐的行动方针,提交用户和客户组织负责人审查批准。在表达分析员对现有系统的认识和描绘未来的物理系统的设想时,系统流程图是一个很好的工具。系统流程图实质上是物理数据流图,它描绘组成系统的主要物理元素以及信息在这些元素间流动和处理的情况。
数据流图的基本符号只有4种,它是描绘系统逻辑模型的极好工具。通常数据字典和数据流图共同构成系统的逻辑模型。成本/效益分析是可行性研究的一项重要内容,是客户组织负责人从经济角度判断是否继续投资于这项工程的主要依据。
2-1 在软件开发的早期阶段为什么要进行可行性研究?应该从哪些方面研究目标系统的可行性?
进行可行性研究的原因:在现实生活中,并不是所有的问题都能有显而易见的解决方法,生活中有许多的问题是不能在预定的系统规模或预定时间期限下得到解决的。如果问题没有可行的解决方法,那么在这项工程上花费的人力、物力、财力、时间、资源,都是一种无谓的浪费。而可行性研究的出现,解决了这种让很多人都头疼的问题。可行性研究的目的就是在付出最小的代价的前提下确定问题是否能在尽可能短的时间内得到解决。如果答案是肯定的,那么就能着手开始这项工程;否则,就不用做费力不讨好的事情。
研究系统可行性的方面:1)技术可行性。根据技术的可行性首先排除一些不现实的系统,最后剩下一组技术上可行的方案。2)操作可行性。根据上一步确定出来的技术可行的方案,从中去掉一些用户难以接受的操作方式。3)经济可行性。在技术和操作都得以通过的方案中,最后要考虑经济上的可行性,如果一个制作出来的系统不仅成本高,而且效益低,那么这个系统就不值得考虑,因为,只有投资预计能带来效益的系统才值得采用
2-5 北京某高校可用的电话号码有以下几类:校内电话号码由4位数字组成,第 1位数字不是0 ;校外电话又分为本市电话和外地电话两类,拨校外电话需先拨0 ,若是本市电话则再接着拨8位数字(第 1位不是 0),若是外地电话则拨 位区码再拨 位电话号码(第 1 位不是 0)用2. 5. 2小节讲述的定义数据的方法,定义上述的电话号码
电话号码=[校内电话号码|校外电话号码]
校内电话号码=非零数字+3位数字
校外电话号码=[本市电话|外地电话]
本市号码=数字零+8位数字
外地号码=数字零+3位数字+8位数字
非零数字=[1|2|3|4|5|6|7|8|9]
数字零=0
3位数字=3{数字}3
8位数字=非零数字+7位数字
7位数字=7{数字}7
数字=[0|1|2|3|4|5|6|7|8|9]
导读
主要内容
需求分析、软件需求规格说明、验证软件需求
教材中的页码
教材第55页----第74页(可不看书中3.5小节)
课件中的页码
PPT第3章(不包括PPT中31、32页)
名词术语
需求、需求分析、软件需求规格说明书、功能需求、性能需求、E—R图、状态转换图
重点
需求分析
需求分析的任务
获取需求的方法
验证软件需求
常用的表示工具
难点
将需求转变成需求分析
如何收集完整的需求
验证软件需求的一致性、完整性、有效性
思考题
除了书中所介绍的方法,还有哪些获取需求的手段?
为什么要验证软件需求?
如何能保证验证需求的一致性、完整性?
作业题
教材73页第三章习题3的第一、第二题、第六题
需求分析是软件定义时期的最后一个阶段,它的基本任务是准确地回答“系统必须做什么?”这个问题。需求分析的任务还不是确定系统怎样完成它的工作,而仅仅是确定系统必须完成哪些工作,也就是对目标系统提出完整、准确、清晰、具体的要求。在需求分析阶段结束之前,系统分析员应该写出软件需求规格说明书,以书面形式准确地描述软件需求。
在分析软件需求和书写软件需求规格说明书的过程中,分析员和用户都起着关键的、必不可少的作用。用户必须把他们对软件的需求尽量准确、具体地描述出来;分析员在需求分析开始时他们对用户的需求并不十分清楚,必须通过与用户沟通获取用户对软件的需求
需求分析和规格说明是一项十分艰巨复杂的工作。用户与分析员之间需要沟通的内容非常多,在双方交流信息的过程中很容易出现误解或遗漏,也可能存在二义性。因此,不仅在整个需求分析过程中应该采用行之有效的方法,必须严格审查验证需求分析的结果。
目前有许多需求分析的结构化分析方法,这些分析方法都遵守下述准则:
(1) 必须理解并描述问题的信息域,根据这条准则应该建立数据模型。
(2) 必须定义软件应完成的功能,这条准则要求建立功能模型。
(3) 必须描述作为外部事件结果的软件行为,这条准则要求建立行为模型。
(4) 必须对描述信息、功能和行为的模型进行分解,用层次的方式展示细节。
任何一个软件系统本质上都是信息处理系统,系统必须处理的信息和系统应该产生的信息在很大程度
上决定了系统的面貌。分析系统的数据要求通常采用建立数据模型的方法。为了提高可理解性,常常利用图形工具辅助描绘数据结构。常用的图形工具有层次方框图和Warnier图。
综合上述两项分析的结果可以导出系统的详细的逻辑模型,通常用数据流图、实体-联系图、状态转换图、数据字典和主要的处理算法描述这个逻辑模型。
根据在分析过程中获得的对系统的更深入更具体的了解,可以比较准确地估计系统的成本和进度,修正以前制定的开发计划。
访谈是最早开始使用的获取用户需求的技术,也是迄今为止仍然广泛使用的需求分析技术。访谈有两种基本形式,分别是正式的和非正式的访谈。正式访谈时,系统分析员将提出一些事先准备好的具体问题。在非正式访谈中,分析员将提出一些用户可以自由回答的开放性问题,以鼓励被访问人员说出自己的想法。
当需要调查大量人员的意见时,向被调查人分发调查表是一个十分有效的做法。经过仔细考虑写出的书面回答可能比被访者对问题的口头回答更准确。分析员仔细阅读收回的调查表,然后再有针对性地访问一些用户,以便向他们询问在分析调查表时发现的新问题。在访问用户的过程中使用情景分析技术往往非常有效。所谓情景分析就是对用户将来使用目标系统解决某个具体问题的方法和结果进行分析。
结构化分析方法就是面向数据流自顶向下逐步求精进行需求分析的方法。通过可行性研究已经得出了目标系统的高层数据流图,需求分析的目标之一就是把数据流和数据存储定义到元素级。为了达到这个目标,通常从数据流图的输出端着手分析,这是因为系统的基本功能是产生这些输出,输出数据决定了系统必须具有的最基本的组成元素。
沿数据流图从输出端往输入端回溯,能够确定每个数据元素的来源,与此同时也就初步定义了有关的算法。
通常把分析过程中得到的有关数据元素的信息记录在数据字典中,把对算法的简明描述记录在IPO图中。通过分析而补充的数据流、数据存储和处理,应该添加到数据流图的适当位置上。
从输入端开始,分析员借助数据流图、数据字典和IPO图向用户解释输入数据是怎样一步一步地转变成输出数据的。这些解释集中反映了通过前面的分析工作分析员所获得的对目标系统的认识。用户应该注意倾听分析员的报告,并及时纠正和补充分析员的认识。复查过程验证了已知的元素,补充了未知的元素,填补了文档中的空白。随着分析过程的进展,经过问题和解答的反复循环,分析员越来越深入具体地定义了目标系统,最终得到对系统数据和功能要求的满意了解。图3.1粗略地概括了上述分析过程
所谓模型,就是为了理解事物而对事物做出的一种抽象,是对事物的一种无歧义的书面描述。通常,模型由一组图形符号和组织这些符号的规则组成。结构化分析实质上是一种创建模型的活动。为了开发出复杂的软件系统,系统分析员应该从不同角度抽象出目标系统的特性,使用精确的表示方法构造系统的型,验证模型是否满足用户对目标系统的需求,并在设计过程中逐渐把和实现有关的细节加进模型中,直至最终用程序实现模型。根据结构化分析准则,需求分析过程应该建立3种模型,它们分别是数据模型、功能模型和行为模型。
需求分析阶段还应该写出软件需求规格说明书,它是需求分析得出的最主要的文档。通常用自然语言完整、准确、具体地描述系统的数据要求、功能需求、性能需求、可靠性和可用性要求、出错处理需求、接口需求、约束以及将来可能提出的要求。
为了把用户的数据要求清楚、准确地描述出来,系统分析员通常建立一个概念性的数据模型(也称为信息模型)。概念性数据模型是一种面向问题的数据模型,是按照用户的观点对数据建立的模型。它描述了从用户角度看到的数据,它反映了用户的现实环境,而且与在软件系统中的实现方法无关。数据模型中包含3种相互关联的信息:数据对象、数据对象的属性及数据对象彼此间相互连接的关系。
数据对象是对软件必须理解的复合信息的抽象。所谓复合信息是指具有一系列不同性质或属性的事物。数据对象可以是外部实体、事物、行为、事件、角色、单位、地点或结构等。总之,可以由一组属性来定义的实体都可以被认为是数据对象。
属性定义了数据对象的性质。必须把一个或多个属性定义为“标识符”,也就是说,当我们希望找到数据对象的一个实例时,用标识符属性作为“关键字”(简称为“键”)。应该根据对所要解决的问题的理解,来确定特定数据对象的一组合适的属性。
数据对象彼此之间相互连接的方式称为联系,也称为关系。联系可分为以下3种类型:
(1) 一对一联系(1∶1)
(2) 一对多联系(1∶N)
(3) 多对多联系(M∶N)
使用实体-联系图(entity-relationship diagram)来建立数据模型。用ER图描绘的数据模型称为ER模型。ER图中包含了实体(即数据对象)、关系和属性等3种基本成分,通常用矩形框代表实体,用连接相关实体的菱形框表示关系,用椭圆形或圆角矩形表示实体(或关系)的属性,并用直线把实体(或关系)与其属性连接起来。例如,图3.2是某学校教学管理的ER图。
软件系统经常使用各种长期保存的信息,这些信息通常以一定方式组织并存储在数据库或文件中,为减少数据冗余,避免出现插入异常或删除异常,简化修改数据的过程,通常需要把数据结构规范化。
通常用“范式(normal forms)”定义消除数据冗余的程度。第一范式(1 NF)数据冗余程度最大,第五范
式(5 NF)数据冗余程度最小。从实用角度看来,在大多数场合选用第三范式都比较恰当。
(1) 第一范式每个属性值都必须是原子值,即仅仅是一个简单值而不含内部结构。
(2) 第二范式满足第一范式条件,而且每个非关键字属性都由整个关键字决定(而不是由关键字的一部分来决定)。
(3) 第三范式符合第二范式条件,每个非关键字属性都仅由关键字决定,而且一个非关键字属性不能仅仅是对另一个非关键字属性的进一步描述(即一个非关键字属性值不依赖于另一个非关键字属性值)。
状态转换图(简称为状态图)通过描绘系统的状态及引起系统状态转换的事件,来表示系统的行为。此外,状态图还指明了作为特定事件的结果系统将做哪些动作(例如,处理数据)。因此,状态图提供了行为建模机制,可以满足分析准则的要求。
状态是任何可以被观察到的系统行为模式,一个状态代表系统的一种行为模式。状态规定了系统对事件的响应方式。系统对事件的响应,既可以是做一个(或一系列)动作,也可以是仅仅改变系统本身的状态,还可以是既改变状态又做动作。在状态图中定义的状态主要有:初态(即初始状态)、终态(即最终状态)和中间状态。在一张状态图中只能有一个初态,而终态则可以有0至多个。
状态图既可以表示系统循环运行过程,也可以表示系统单程生命期。当描绘循环运行过程时,通常并不关心循环是怎样启动的。当描绘单程生命期时,需要标明初始状态(系统启动时进入初始状态)和最终状态(系统运行结束时到达最终状态)。
事件是在某个特定时刻发生的事情,它是对引起系统做动作或(和)从一个状态转换到另一个状态的外界事件的抽象。例如,内部时钟表明某个规定的时间段已经过去,用户移动或点击鼠标等都是事件。简而言之,事件就是引起系统做动作或(和)转换状态的控制信息。
在状态图中,初态用实心圆表示,终态用一对同心圆(内圆为实心圆)表示。中间状态用圆角矩形表示,可以用两条水平横线把它分成上、中、下3个部分。上面部分为状态的名称,这部分是必须有的;中间部分为状态变量的名字和值,这部分是可选的;下面部分是活动表,这部分也是可选的。
活动表的语法格式如下:事件名(参数表)/动作表达式
其中,“事件名”可以是任何事件的名称。在活动表中经常使用下述3种标准事件:entry,exit和do。entry事件指定进入该状态的动作,exit事件指定退出该状态的动作,而do事件则指定在该状态下的动
作。需要时可以为事件指定参数表。活动表中的动作表达式描述应做的具体动作。
状态图中两个状态之间带箭头的连线称为状态转换,箭头指明了转换方向。状态变迁通常是由事件触发
的,在这种情况下应在表示状态转换的箭头线上标出触发转换的事件表达式;如果在箭头线上未标明事件,则表示在源状态的内部活动执行完之后自动触发转换。
事件表达式的语法如下:事件说明[守卫条件]/动作表达式
其中,事件说明的语法为:事件名(参数表)。守卫条件是一个布尔表达式。如果同时使用事件说明和守卫条件,则当且仅当事件发生且布尔表达式为真时,状态转换才发生。如果只有守卫条件没有事件说明,则只要守卫条件为真状态转换就发生。动作表达式是一个过程表达式,当状态转换开始时执行该表达式。
图3.3给出了状态图中使用的主要符号。
图中表明,没有人打电话时电话处于闲置状态;有人拿起听筒则进入拨号音状态,到达这个状态后,电话的行为是响起拨号音并计时;这时如果拿起听筒的人改变主意不想打了,他把听筒放下(挂断),电话重又回到闲置状态;如果拿起听筒很长时间不拨号(超时),则进入超时状态;……。
层次方框图用树形结构的一系列多层次的矩形框描绘数据的层次结构。
法国计算机科学家Warnier提出了表示信息层次结构的另外一种图形工具。Warnier图也用树形结构描绘信息。
IPO图是输入、处理、输出图的简称,它是IBM公司发展完善起来的一种图形工具,能够方便地描绘输入数据、对数据的处理和输出数据之间的关系。
IPO图的基本形式是在左边的框中列出有关的输入数据,在中间的框内列出主要的处理,在右边的框内列出产生的输出数据。处理框中列出处理的次序暗示了执行的顺序,在IPO图中用箭头清楚地指出数据通信的情况。图3.7是一个主文件更新的例子
一种改进的IPO图(也称为IPO表),这种图中包含某些附加的信息,在软件设计过程中将比原始的IPO图更有用。在需求分析阶段可以使用IPO图简略地描述系统的主要算法(即数据流图中各个处理的基本算法)
需求分析阶段的工作结果是开发软件系统的重要基础。为了提高软件质量,确保软件开发成功,降低软件开发成本,必须严格验证需求的正确性。一般说来,应该从下述4个方面进行验证:
(1) 一致性:所有需求必须是一致的,任何一条需求不能和其他需求互相矛盾。
(2) 完整性:需求必须是完整的,规格说明书应该包括用户需要的每一个功能或性能。
(3) 现实性:指定的需求应该是用现有的硬件技术和软件技术基本上可以实现的。
(4) 有效性:必须证明需求是正确有效的,确实能解决用户面对的问题。
为了更有效地保证软件需求的正确性,特别是为了保证需求的一致性,需要有适当的软件工具支持需求分析工作。这类软件工具应该满足下列要求:
(1) 必须有形式化的语法(或表),因此可以用计算机自动处理使用这种语法说明的内容;
(2) 使用这个软件工具能够导出详细的文档;
(3) 必须提供分析(测试)规格说明书的不一致性和冗余性的手段,并且应该能够产生一组报告指明对完整性分析的结果
(4) 使用这个软件工具之后,应该能够改进通信状况。
作为需求工程方法学的一部分,在1977年设计完成了RSL(需求陈述语言)。RSL中的语句是计算机可以处理的,处理以后把从这些语句中得到的信息集中存放在一个称为ASSM(抽象系统语义模型)的数据库中。有一组软件工具处理ASSM数据库中的信息以产生出用PASCAL语言书写的模拟程序,从而可以检验需求的一致性、完整性和现实性
1977年美国密执安大学开发了PSL/PSA(问题陈述语言/问题陈述分析程序)系统。这个系统是CADSAT(计算机辅助设计和规格说明分析工具)的一部分,它的基本结构类似于RSL。
传统软件工程方法学使用结构化分析技术,完成分析用户需求的工作。需求分析是发现、求精、建模、规格说明和复审的过程。需求分析的第一步是进一步了解用户当前所处的情况,发现用户所面临的问题和对目标系统的基本需求;接下来应该与用户深入交流,对用户的基本需求反复细化逐步求精,以得出对目标系统的完整、准确和具体的需求。具体地说,应该确定系统必须具有的功能、性能、可靠性和可用性,必须实现的出错处理需求、接口需求,必须满足的约束条件,并且预测系统的发展前景。
为了详细地了解并正确地理解用户的需求,必须使用适当方法与用户沟通。访谈是与用户通信的历史悠久的技术,至今仍被许多系统分析员采用。从可行性研究阶段得到的数据流图出发,在用户的协助下面向数据流自顶向下逐步求精,也是与用户沟通获取需求的一个有效的方法。为了促使用户与分析员齐心协力共同分析需求,人们研究出一种面向团队的需求收集法,称为简易的应用规格说明技术,现在这种技术已经成为信息系统领域使用的主流技术。实践表明,快速建立软件原型是最准确、最有效和最强大的需求分析技术。为了更好地理解问题,人们常常采用建立模型的方法,结构化分析实质上就是一种建模活动,在需求分析阶段通常建立数据模型、功能模型和行为模型。除了创建分析模型之外,在需求分析阶段还应该写出软件需求规格说明书,经过严格评审并得到用户确认之后,作为这个阶段的最终成果。通常主要从一致性、完整性、现实性和有效性等4个方面复审软件需求规格说明书。多数人习惯于使用实体-联系图建立数据模型,使用数据流图建立功能模型,使用状态图建立行为模型。读者应该掌握这些图形的基本符号,并能正确地使用这些符号建立软件系统的模型。数据字典描述在数据模型、功能模型和行为模型中出现的数据对象及控制信息的特性,给出它们的准确定义。因此,数据字典成为把3种分析模型粘合在一起的“粘合剂”,是分析模型的“核心”。
为了提高可理解性,还可以用层次方框图或Warnier图等图形工具辅助描绘系统中的数据结构。为了减少冗余、简化修改步骤,往往需要规范数据的存储结构。算法也是重要的,分析的基本目的是确定系统必须做什么。概括地说,任何一个计算机系统的基本功能都是把输入数据转变成输出信息,算法定义了转变的规则。因此,没有对算法的了解就不能确切知道系统的功能。IPO图是描述算法的有效工具
3-1 为什么要进行需求分析?通常对软件系统有哪些需求?
需求分析是一个发现、求精、建模、规格说明和复审的过程,我们进行需求分析是为了发现系统中可能存在的问题,以及确定系统必须完成的工作,为了更好的满足用户的真正需求。我们所做的一切都是为了能够真正的满足用户需求。
需求分析通常对软件系统有一下需求:
1.功能需求,即指定系统必须提供的服务
2.性能需求,即指定系统必须满足的定时约束或者容量约束
3.可靠性和可用性需求,即定量地指定系统的可靠性以及量化用户可以使用系统的程度
4.出错处理需求,即说明系统对环境错误应该怎样响应
5.接口需求,即描述应用系统与它的环境通信的格式
6.约束,即设计约束或实现约束在设计或实现应用系统时应遵守的限制条件
7.逆向需求,即说明软件系统不应该做什么
8.将来可能提出的要求,即明确的列出那些虽然不属于当前系统开发范畴,但是据分析将来很可能会提出来的要求
3-2 怎样与用户有效地沟通以获取用户的真实需求?
可采用以下四种方法获取真实需求:
(1)访谈;
(2)面向数据流自顶向下求精;
(3)简易的应用规格说明技术;
(4)快速建立软件原型。
3-6 复印机的工作过程大致如下:未接到复印命令时处于闲置状态,一旦接到复印命令则进入复印状态,完成一个复印命令规定的工作后又回到闲置状态,等待下一个复印命令;如果执行复印命令时发现没纸,则进入缺纸状态,发出警告,等待装纸,装满纸后进入闲置状态,准备接收复印命令;如果复印时发生卡纸故障,则进入卡纸状态,发出警告等待维修人员来排除故障,故障排除后回到闲置状态。请用状态转换图描绘复印机的行为.
答:从题中描述可知,复印机的状态主要有“闲置”“复印”“缺纸”和“卡纸”。引起状态转换的事件主要是“复印命令”“完成复印命令”“发现缺纸”“装满纸”“发生卡纸故障”和“排除卡纸故障”。故状态转换图,如图3-9所示。
导读
主要内容
总体设计、总体设计的过程、总体设计的原则
教材中的页码
教材第91页----第102页(把5.3小节当作总体设计的原则来讲解)
课件中的页码
PPT第5章第1页—第93页
名词术语
总体设计、模块、耦合、内聚、扇入、扇出、层次图
重点
总体设计的目的
总体设计的过程
模块化的思想
抽象的运用
逐步求精的过程
面向数据流的设计方法
面向数据流的设计方法示例
难点
总体设计的功能
模块的耦合性和内聚性
抽象的运用
面向数据流的设计方法逐步求精的过程
思考题
为什么不能越过总体设计过程的审查和复审步骤?
采用模块化的优点是什么?
作业题
教材115页第五章习题5的第一、第二、第四、第五题
总体设计的基本目的就是回答“概括地说,系统应该如何实现?”这个问题,因此,总体设计又称为概要设计或初步设计。通过这个阶段的工作将划分出组成系统的物理元素——程序、文件、数据库、人工过程和文档等等,但是每个物理元素仍然处于黑盒子级,这些黑盒子里的具体内容将在以后仔细设计。总体设计阶段的另一项重要任务是设计软件的结构,也就是要确定系统中每个程序是由哪些模块组成的,以及这些模块相互间的关系。
总体设计设计软件结构。设计出初步的软件结构后还要多方改进,从而得到更合理的结构,进行必要的数据库设计,确定测试要求并且制定测试计划。在详细设计之前先进行总体设计的必要性:可以站在全局高度上,花较少成本,从较抽象的层次上分析对比多种可能的系统实现方案和软件结构,从中选出最佳方案和最合理的软件结构,从而用较低成本开发出较高质量的软件系统。
总体设计过程通常由两个主要阶段组成:系统设计阶段,确定系统的具体实现方案;结构设计阶段,确定软件结构。典型的总体设计过程包括下述9个步骤:
模块是由边界元素限定的相邻程序元素(例如,数据说明,可执行的语句)的序列,而且有一个总体标识符代表它。按照模块的定义,过程、函数、子程序和宏等,都可作为模块。面向对象方法学中的对象是模块,对象内的方法也是模块。模块是构成程序的基本构件。模块化就是把程序划分成独立命名且可独立访问的模块,每个模块完成一个子功能,把这些模块集成起来构成一个整体,可以完成指定的功能满足用户
的需求
采用模块化原理可以使软件结构清晰,不仅容易设计也容易阅读和理解。因为程序错误通常局限在有关的模块及它们之间的接口中,所以模块化使软件容易测试和调试,因而有助于提高软件的可靠性。因为变动往往只涉及少数几个模块,所以模块化能够提高软件的可修改性。模块化也有助于软件开发工程的组织管理。
抽象是通过对现实世界中一类事物或概念经过高度概括总结抽取出其共性的过程。人类在认识复杂现象的过程中使用的最强有力的思维工具是抽象。人们在实践中认识到,在现实世界中一定事物、状态或过程之间总存在着某些相似的方面(共性)。把这些相似的方面集中和概括起来,暂时忽略它们之间的差异,这就是抽象。或者说抽象就是抽出事物的本质特性而暂时不考虑它们的细节。
由于人类思维能力的限制,如果每次面临的因素太多,是不可能做出精确思维的。处理复杂系统的惟一有效的方法是用层次的方式构造和分析它。一个复杂的动态系统首先可以用一些高级的抽象概念构造和理解,这些高级概念又可以用一些较低级的概念构造和理解,如此进行下去,直至最低层次的具体元素。
这种层次的思维和解题方式必须反映在定义动态系统的程序结构之中,每级的一个概念将以某种方式对应于程序的一组成分。考虑对任何问题的模块化解法时,可以提出许多抽象的层次。在抽象的最高层次使用问题环境的语言,以概括的方式叙述问题的解法;在较低抽象层次采用更过程化的方法,把面向问题的术语和面向实现的术语结合起来叙述问题的解法;最后在最低的抽象层次用可直接实现的方式叙述问题的解法。软件工程过程的每一步都是对软件解法的抽象层次的一次精化。在可行性研究阶段,软件作为系统的
一个完整部件;在需求分析期间,软件解法是使用在问题环境内熟悉的方式描述的;当由总体设计向详细设计过渡时,抽象的程度也就随之减少了;最后,当源程序写出来以后,也就达到了抽象的最低层。
逐步求精是人类解决复杂问题时采用的基本方法,也是许多软件工程技术的基础。可以把逐步求精定义为:“为了能集中精力解决主要问题而尽量推迟对问题细节的考虑。”求精方法的作用在于,它能帮助软件工程师把精力集中在与当前开发阶段最相关的那些方面上,而忽略那些对整体解决方案来说虽然是必要的,然而目前还不需要考虑的细节,这些细节将留到以后再考虑。抽象与求精是一对互补的概念。抽象使得设计者能够说明过程和数据,同时却忽略低层细节。求精则帮助设计者在设计过程中逐步揭示出低层细节。这两个概念都有助于设计者在设计演化过程中创造出完整的设计模型。
信息隐藏指出:应该这样设计和确定模块,使得一个模块内包含的信息对于不需要这些信息的模块来说,是不能访问的。局部化是指把一些关系密切的软件元素物理地放得彼此靠近。局部化有助于实现信息隐藏。局部化的概念和信息隐藏概念是密切相关的。
模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果。开发具有独立功能而且和其他模块之间没有过多的相互作用的模块,就可以做到模块独立。
为什么模块的独立性很重要呢?主要有两条理由:
第一,有效的模块化软件比较容易开发出来。这是由于能够分割功能而且接口可以简化,当许多人分工合作开发同一个软件时,这个优点尤其重要。
第二,独立的模块比较容易测试和维护。这是因为相对说来,修改设计和程序需要的工作量比较小,错误传播范围小,需要扩充功能时能够“插入”代码。总之,模块独立是良好设计的关键,而设计又是决定软件质量的关键环节。
模块的独立程度可以由两个定性标准度量,这两个标准分别称为内聚和耦合。
耦合衡量不同模块彼此间互相依赖(连接)的紧密程度;内聚衡量一个模块内部各个元素彼此结合的紧密程度。
根据模块间联系的紧密程度,把耦合分类为:数据耦合、控制耦合、特征耦合、公共环境耦合、内容耦合
如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,那么这种耦合称为数据耦合。
如果传递的信息中有控制信息,则这种耦合称为控制耦合。
数据耦合是低耦合。系统中至少必须存在这种耦合,因为只有当某些模块的输出数据作为另一些模块的
输入数据时,系统才能完成有价值的功能。一般说来,一个系统内可以只包含数据耦合。
控制耦合是中等程度的耦合,它增加了系统的复杂程度。控制耦合往往是多余的,在把模块适当分解之后通常可以用数据耦合代替它。
如果被调用的模块需要使用作为参数传递进来的数据结构中的所有元素,那么,把整个数据结构作为参数传递就是完全正确的。但是,当把整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元素时,就出现了特征耦合在这种情况下,被调用的模块可以使用的数据多于它确实需要的数据,这将导致对数据的访问失去控制。
当两个或多个模块通过一个公共数据环境相互作用时,它们之间的耦合称为公共环境耦合。公共环境
可以是全程变量、共享的通信区、内存的公共覆盖区、任何存储介质上的文件、物理设备等等。公共环境耦合的复杂程度随耦合的模块个数而变化,当耦合的模块个数增加时复杂程度显著增加。如果只有两个模块有公共环境,那么这种耦合有下面两种可能:
(1) 一个模块往公共环境送数据,另一个模块从公共环境取数据。这是数据耦合的一种形式,是比较松散的耦合。
(2) 两个模块都既往公共环境送数据又从里面取数据,这种耦合比较紧密,介于数据耦合和控制耦合之间。
如果两个模块共享的数据很多,都通过参数传递可能很不方便,这时可以利用公共环境耦合
最高程度的耦合是内容耦合。如果出现下列情况之一,两个模块间就发生了内容耦合:
一个模块访问另一个模块的内部数据;
一个模块不通过正常入口而转到另一个模块的内部;
两个模块有一部分程序代码重叠;
一个模块有多个入口(这意味着一个模块有几种功能)。
应该坚决避免使用内容耦合。事实上许多高级程序设计语言已经设计成不允许在程序中出现任何形式的内容耦合
总之,耦合是影响软件复杂程度的一个重要因素。应该采取下述设计原则:
尽量使用数据耦合,少用控制耦合和特征耦合,限制公共环境耦合的范围,完全不用内容耦合。
2. 内聚
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然扩展。简单地说,
理想内聚的模块只做一件事情。设计时应该力求做到高内聚,通常中等程度的内聚也是可以采用的,而且效果和高内聚相差不多;但是,低内聚不要使用。
根据模块内联系的紧密程度,把内聚分类为:
偶然内聚
逻辑内聚
时间内聚
过程内聚
通信内聚
顺序内聚
功能内聚
内聚和耦合是密切相关的,模块内的高内聚往往意味着模块间的松耦合。内聚和耦合都是进行模块化设计的有力工具,但是实践表明内聚更重要,应该把更多注意力集中到提高模块的内聚程度上。
低内聚有如下几类:
如果一个模块完成一组任务,这些任务彼此间即使有关系,关系也是很松散的,就叫做偶然内聚。有时在写完一个程序之后,发现一组语句在两处或多处出现,于是把这些语句作为一个模块以节省内存,这样就出现了偶然内聚的模块。
如果一个模块完成的任务在逻辑上属于相同或相似的一类,则称为逻辑内聚。
如果一个模块包含的任务必须在同一段时间内执行,就叫时间内聚。
在偶然内聚的模块中,各种元素之间没有实质性联系,很可能在一种应用场合需要修改这个模块,在另一种应用场合又不允许这种修改,从而陷入困境。在逻辑内聚的模块中,不同功能混在一起,合用部分程序代码,即使局部功能的修改有时也会影响全局。因此,这类模块的修改也比较困难。时间关系在一定程度上反映了程序的某些实质,所以时间内聚比逻辑内聚好一些。
中内聚主要有两类:
如果一个模块内的处理元素是相关的,而且必须特定次序执行,则称为过程内聚。使用程序流程图作为工具设计软件时,常常通过研究流程图确定模块的划分,这样得到的往往是过程内聚的模块。
如果模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据,则称为通信内聚。
高内聚也有两类:
如果一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行(通常一个处理元素的输出数据作为下一个处理元素的输入数据),则称为顺序内聚。根据数据流图划分模块时,通常得到顺序内聚的模块,这种模块彼此间的连接往往比较简单。
如果模块内所有处理元素属于一个整体,完成一个单一的功能,则称为功能内聚。功能内聚是最高程度的内聚。
耦合和内聚的概念是Constantine等人提出来的。按照他们的观点,如果给上述七种内聚的优劣评分,
将得到如下结果:
功能内聚 10分 时间内聚 3分
顺序内聚 9分 逻辑内聚 1分
通信内聚 7分 偶然内聚 0分
过程内聚 5分
设计时力争做到高内聚,并且能够辨认出低内聚的模块,有能力通过修改设计提高模块的内聚程度降低模块间的耦合程度,从而获得较高的模块独立性。
人们在开发计算机软件的长期实践中积累了丰富的经验,总结这些经验得出了一些启发式规则。
改进软件结构提高模块独立性
设计出软件的初步结构以后,应该审查分析这个结构,通过模块分解或合并,力求降低耦合提高内聚。
例如,多个模块公有的一个子功能可以独立成一个模块,由这些模块调用;有时可以通过分解或合并模块以减少控制信息的传递及对全程数据的引用,并且降低接口的复杂程度。
模块规模应该适中
经验表明,一个模块的规模不应过大,通常不超过60行语句。有人从心理学角度研究得知,当一个模块包含的语句数超过30以后,模块的可理解程度迅速下降。过大的模块往往是由于分解不充分,但是进一步分解必须符合问题结构,一般说来,分解后不应该降低模块独立性。过小的模块开销大于有效操作,而且模块数目过多将使系统接口复杂。因此过小的模块有时不值得单独存在,特别是只有一个模块调用它时,通常可以把它合并到上级模块中去而不必单独存在。
深度、宽度、扇出和扇入都应适当
深度表示软件结构中控制的层数,它往往能粗略地标志一个系统的大小和复杂程度。深度和程序长度之间应该有粗略的对应关系。如果层数过多则应该考虑是否有许多管理模块过分简单了,能否适当合并。
宽度是软件结构内同一个层次上的模块总数的最大值。一般说来,宽度越大系统越复杂。对宽度影响最大的因素是模块的扇出。
扇出是一个模块直接调用的模块数目,扇出过大意味着模块过分复杂,需要控制和协调过多的下级模块;扇出过小也不好。经验表明,一个设计得好的典型系统的平均扇出通常是3或4(扇出的上限通常是5~9)。
扇出太大一般是因为缺乏中间层次,应该适当增加中间层次的控制模块。扇出太小时可以把下级模块进一步分解成若干个子功能模块,或者合并到它的上级模块中去。当然分解模块或合并模块必须符合问题结构,不能违背模块独立原理。
一个模块的扇入表明有多少个上级模块直接调用它,扇入越大则共享该模块的上级模块数目越多,这是有好处的,但是,不能违背模块独立原理单纯追求高扇入。设计得很好的软件结构通常顶层扇出比较高,中层扇出较少,底层扇入到公共的实用模块中去(底层模块有高扇入)。
模块的作用域应该在控制域之内
模块的作用域定义为受该模块内一个判定影响的所有模块的集合。模块的控制域是这个模块本身以及所有直接或间接从属于它的模块的集合。例如,在图5.2中模块A的控制域是A、B、C、D、E、F等模块的集合
力争降低模块接口的复杂程度
模块接口复杂是软件发生错误的一个主要原因。应该仔细设计模块接口,使得信息传递简单并且和模块的功能一致。接口复杂或不一致,是紧耦合或低内聚的征兆,应该重新分析这个模块的独立性。
设计单入口单出口的模块
这条启发式规则警告软件工程师不要使模块间出现内容耦合。当从顶部进入模块并且从底部退出来时,软件是比较容易理解的,也是比较容易维护的。
模块功能应该可以预测
模块的功能应该能够预测,但也要防止模块功能过分局限。如果一个模块可以当做一个黑盒子,也就是说,只要输入的数据相同就产生同样的输出,这个模块的功能就是可以预测的。以上列出的启发式规则多数是经验规律,对改进设计,提高软件质量,往往有重要的参考价值;但是,它们既不是设计的目标也不是设计时应该普遍遵循的原理
层次图用来描绘软件的层次结构。层次图中的一个矩形框代表一个模块,方框间的连线表示调用关系。图5.3是层次图的一个例子。
层次图很适于在自顶向下设计软件的过程中使用。HIPO图是IBM公司发明的“层次图加输入/处理/输出图”的缩写。为了能使HIPO图具有可追踪性,在层次图里除了最顶层的方框之外,每个方框都加了编号。图5.3加了编号后得到图5.4。
Yourdon提出的结构图是进行软件结构设计的另一个工具。结构图和层次图类似,也是描绘软件结构的图形工具,图中一个方框代表一个模块,框内注明模块的名字或主要功能;方框之间的箭头(或直线)表示模块的调用关系。因为按照惯例总是图中位于上方的方框代表的模块调用下方的模块,即使不用箭头也不会产生二义性,为了简单起见,可以只用直线而不用箭头表示模块间的调用关系。在结构图中通常还用带注释的箭头表示模块调用过程中来回传递的信息。如果希望进一步标明传递的信息是数据还是控制信息,则可以利用注释箭头尾部的形状来区分:尾部是空心圆表示传递的是数据,实心圆表示传递的是控制信息。图5.5是结构图的一个例子。
以上介绍的是结构图的基本符号,也就是最经常使用的符号。此外还有一些附加的符号,可以表示模的选择调用或循环调用。图5.6表示当模块M中某个判定为真时调用模块A,为假时调用模块B。图5.7表示模块M循环调用模块A、B和C。
面向数据流的设计方法的目标是给出设计软件结构的一个系统化的途径。在软件工程的需求分析阶段,信息流是一个关键考虑,通常用数据流图描绘信息在系统中加工和流动的情况。面向数据流的设计方法定义了一些不同的**“映射”,利用这些映射可以把数据流图变换成软件结构**。因为任何软件系统都可以用数据流图表示,所以面向数据流的设计方法理论上可以设计任何软件的结构。通常所说的结构化设计方法(简称SD方法),也就是基于数据流的设计方法。
面向数据流的设计方法把信息流映射成软件结构,信息流的类型决定了映射的方法。信息流有下述两种类型。
3. 设计过程
图5.10说明了使用面向数据流方法逐步设计的过程。注意,任何设计过程都不是机械地一成不变的。
变换分析是一系列设计步骤的总称,经过这些步骤把具有变换流特点的数据流图按预先确定的模式映射成软件结构。
这个数据流图对于软件结构设计的“第一次分割”而言已经足够详细了,因此不需要精化就可以进行下一个设计步骤。
第3步 **确定数据流图具有变换特性还是事务特性。**一般地说,一个系统中的所有信息流都可以认为是变换流,但是,当遇到有明显事务特性的信息流时,建议采用事务分析方法进行设计。在这一步,设计人员应该根据数据流图中占优势的属性,确定数据流的全局特性。此外还应该把具有和全局特性不同的特点的局部区域孤立出来,以后可以按照这些子数据流的特点精化根据全局特性得出的软件结构。从图5.11看出,数据沿着两条输入通路进入系统,然后沿着5条通路离开,没有明显的事务中心。因此可以认为这个信息流具有变换流的总特征。
第4步 确定输入流和输出流的边界,从而孤立出变换中心。输入流和输出流的边界和对它们的解释有关,也就是说,不同设计人员可能会在流内选取稍微不同的点作为边界的位置。当然在确定边界时应该仔细认真,但是把边界沿着数据流通路移动一个处理框的距离,通常对最后的软件结构只有很小的影响。对于汽车数字仪表板的例子,设计人员确定的流的边界如图5.12所示
第5步 完成“第一级分解”。软件结构代表对控制的自顶向下的分配,所谓分解就是分配控制的过程。对于变换流的情况,数据流图被映射成一个特殊的软件结构,这个结构控制输入、变换和输出等信息处理过程。图5.13说明了第一级分解的方法。位于软件结构最顶层的控制模块Cm协调下述从属的控制功能:输入信息处理控制模块Ca,协调对所有输入数据的接收;变换中心控制模块Ct,管理对内部形式的数据的所有操作;输出信息处理控制模块Ce,协调输出信息的产生过程。
虽然图5.13意味着一个三叉的控制结构,但是,对一个大型系统中的复杂数据流可以用两个或多个模块完成上述一个模块的控制功能。应该在能够完成控制功能并且保持好的耦合和内聚特性的前提下,尽量使第一级控制中的模块数目取最小值。对于数字仪表板的例子,第一级分解得出的结构如图5.14所示。每个控制模块的名字表明了为它所控制的那些模块的功能。
第6步 完成“第二级分解”。
所谓第二级分解就是把数据流图中的每个处理映射成软件结构中一个适当的模块。完成第二级分解的方法是,从变换中心的边界开始沿着输入通路向外移动,把输入通路中每个处理映射成软件结构中Ca控制下的一个低层模块;然后沿输出通路向外移动,把输出通路中每个处理映射成直接或间接受模块Ce控制的一个低层模块;最后把变换中心内的每个处理映射成受Ct控制的一个模块。图5.15表示进行第二级分解的普遍途径。
虽然图5.15描绘了在数据流图中的处理和软件结构中的模块之间的一对一的映射关系,但是,不同的映射经常出现。应该根据实际情况以及“好”设计的标准,进行实际的第二级分解。对于数字仪表板系统的例子,第二级分解的结果分别用图5.16,5.17和5.18描绘。这3张图表示对软件结构的初步设计结果。虽然图中每个模块的名字表明了它的基本功能,但是仍然应该为每个模块写一个简要说明,描述:进出该模块的信息(接口描述);模块内部的信息;过程陈述,包括主要判定点及任务等;对约束和特殊特点的简短讨论。这些描述是第一代的设计规格说明,在这个设计时期进一步的精化和补充是经常发生的
第7步 使用设计度量和启发式规则对第一次分割得到的软件结构进一步精化。
对第一次分割得到的软件结构,总可以根据模块独立原理进行精化。为了产生合理的分解,得到尽可能高的内聚、尽可能松散的耦合,最重要的是,为了得到一个易于实现、易于测试和易于维护的软件结构,应该对初步分割得到的模块进行再分解或合并。具体到数字仪表板的例子,对于从前面的设计步骤得到的软件结构,还可以做许多修改。下面是某些可能的修改:输入结构中的模块“转换成rpm”和“收集sps”可以合并;模块“确定加速/减速”可以放在模块“计算mph”下面,以减少耦合;模块“加速/减速显示”可以相应地放在模块“显示mph”的下面。经过上述修改后的软件结构画在图5.19中。上述7个设计步骤的目的是,开发出软件的整体表示。也就是说,一旦确定了软件结构就可以把它作为一个整体来复查,从而能够评价和精化软件结构。在这个时期进行修改只需要很少的附加工作,但是却能够对软件的质量特别是软件的可维护性产生深远的影响
软件设计人员应该致力于开发能够满足所有功能和性能要求,而且按照设计原理和启发式设计规则衡量是值得接收的软件。应该在设计的早期阶段尽量对软件结构进行精化。可以导出不同的软件结构,然后对它们进行评价和比较,力求得到“最好”的结果。
结构简单通常既表示设计风格优雅,又表明效率高。设计优化应该力求做到在有效的模块化的前提下使用最少量的模块,以及在能够满足信息要求的前提下使用最简单的数据结构。
(1) 在不考虑时间因素的前提下开发并精化软件结构;
(2) 在详细设计阶段选出最耗费时间的那些模块,仔细地设计它们的处理过程(算法),以求提高效率;
(3) 使用高级程序设计语言编写程序;
(4) 在软件中孤立出那些大量占用处理机资源的模块;
(5) 必要时重新设计或用依赖于机器的语言重写上述大量占用资源的模块的代码,以求提高效率。上述优化方法遵守了一句格言:“先使它能工作,然后再使它快起来。
总体设计阶段的基本目的是用比较抽象概括的方式确定系统如何完成预定的任务,也就是说,应该确定系统的物理配置方案,并且进而确定组成系统的每个程序的结构。因此,总体设计阶段主要由两个小阶段组成。首先需要进行系统设计,然后进行软件结构设计,确定软件由哪些模块组成以及这些模块之间的动态调用关系。层次图和结构图是描绘软件结构的常用工具。
在进行软件结构设计时应该遵循的最主要的原理是模块独立原理,也就是说,软件应该由一组完成相对独立的子功能的模块组成,这些模块彼此之间的接口关系应该尽量简单。抽象和求精是一对互补的概念,在进行软件结构设计时一种有效的方法就是,由抽象到具体地构造出软件的层次结构。软件工程师总结出一些很有参考价值的启发式规则,对如何改进软件设计给出宝贵的提示。在软件开发过程中既要充分重视和利用这些启发式规则,又要从实际情况出发避免生搬硬套。自顶向下逐步求精是进行软件结构设计的常用途径;但是,如果已经有了详细的数据流图,也可以使用面向数据流的设计方法,用形式化的方法由数据流图映射出软件结构。这样映射出来的只是软件的初步结构,还必须根据设计原理并且参考启发式规则,认真分析和改进软件的初步结构,以得到质量更高的模块和更合理的软件结构。在进行详细的过程设计和编写程序之前,首先进行结构设计,其好处正在于可以在软件开发的早期站在全局高度对软件结构进行优化。在这个时期进行优化付出的代价不高,却可以使软件质量得到重大改进。
主要内容
详细设计
详细设计的目标
详细设计的工具
程序复杂程度的度量方法
教材中的页码
教材第117页----第140页(6.1节简单阅读,可不看6.4节)
课件中的页码
PPT第6章第1页—第96页
名词术语
系统响应时间
盒图
判定树
判定表
过程设计语言PDL
流图
环形复杂度
重点
程序复杂程度的定量度量
环形复杂度的计算方法
难点
人机界面设计
环形复杂度的计算方法
作业题
教材141页第六章习题6的第三、第七、第八题
详细设计阶段的根本目标是确定应该怎样具体地实现所要求的系统,也就是说,经过这个阶段的设计工作,应该得出对目标系统的精确描述,从而在编码阶段可以把这个描述直接翻译成用某种程序设计语言书写的程序。
详细设计阶段的任务还不是具体地编写程序,而是要设计出程序的“蓝图”,以后程序员将根据这个蓝图写出实际的程序代码。因此,详细设计的结果决定了最终的程序代码的质量。在软件的生命周期中,设计测试方案、诊断程序错误、修改和改进程序等等都必须首先读懂程序。实际上对于长期使用的软件系统而言人读程序的时间可能比写程序的时间还要长得多。因此,衡量程序的质量不仅要看它的逻辑是否正确,性能是否满足要求,更主要的是要看它是否容易阅读和理解。详细设计的目标不仅仅是逻辑上正确地实现每个模块的功能,更重要的是设计出的处理过程应该尽可能简明易懂。
结构程序设计的概念最早由E.W.Dijkstra提出。他在一次会议上提出取消GO TO语句。1966年Bohm和Jacopini证明了,只用3种基本的控制结构就能实现任何单入口单出口的程序。这3种基本的控制结构是“顺序”、“选择”和“循环”。
结构程序设计本质上并不是无GO TO语句的编程方法,而是一种使程序代码容易阅读、容易理解的编程方法。在多数情况下,无GO TO语句的代码确实是容易阅读、容易理解的代码,但是,在某些情况下,为了达到容易阅读和容易理解的目的,反而需要使用GO TO语句。因此,下述的结构程序设计的定义可能更全面一些:
“结构程序设计是尽可能少用GO TO语句的程序设计方法。最好仅在检测出错误时才使用GO TO语句,而且应该总是使用前向GO TO语句。”为了实际使用方便,常常还允许使用DO-UNTIL和DO-CASE两种控制结构。
有时需要立即从循环(甚至嵌套的循环)中转移出来,如果允许使用LEAVE(或BREAK)结构,则不仅方便而且会使效率提高很多。LEAVE或BREAK结构实质上是受限制的GO TO 语句,用于转移到循环结构后面的语句。
人机界面设计是接口设计的一个重要的组成部分。人机界面设计通常包括计算机通过输出设备向人提供信息及提示,以及人通过输入设备向计算机输入有关信息、问题问答等。近年来,人机界面在系统中所占的比例越来越大。人机界面的设计质量,直接影响用户对软件产品的评价,从而影响软件产品的竞争力和寿命,因此,必须对人机界面设计给予足够重视。
人机交互的三元素
一个人机交互的计算机系统,通常必须考虑三个元素:交互设备,交互软件,以及人的因素。
交互设备
交互设备构成了交互计算机系统人机对话的基础;
交互设备可以分为许多类,主要有数字和字母输入输出设备(字符显示终端、打印机、键盘);图形和图像输入输出设备(图形图像显示器,摄影机、扫描仪、绘图仪);声音、触感及专用输入输出设备等。
交互软件
交互软件是交互计算机系统的核心,向用户提供各种交互功能,以满足系统预定的要求;交互软件分为系统软件和应用软件。系统软件包括操作系统、解释/编译程序、数据库查询语言、以及图形软件包。
人的因素
人的因素指的是用户操作模型,它与用户的各种特征有关;首先,用户是人,人有许多弱点,如操作时经常出错。其次,用户的年龄、文化程度、工作经历对操作使用的要求也各不相同。
在设计人机界面的过程中,几乎总会遇到下述4个问题:系统响应时间、用户帮助设施、出错信息处理和命令交互。不幸的是,许多设计者直到设计过程后期才开始考虑这些问题,这样做往往导致出现不必要的设计反复、项目延期和用户产生挫折感。最好在设计初期就把这些问题作为重要的设计问题来考虑,这时修改比较容易,代价也低。
系统响应时间
系统响应时间指从用户完成某个控制动作(例如,按回车键或点击鼠标),到软件给出预期的响应(输出信息或做动作)之间的这段时间。
系统响应时间有两个重要属性,分别是长度和易变性。如果系统响应时间过长,用户就会感到紧张和沮丧。但是,当用户工作速度是由人机界面决定的时候,系统响应时间过短也不好,这会迫使用户加快操作节奏,从而可能会犯错误。易变性指系统响应时间相对于平均响应时间的偏差,在许多情况下,这是系统响应时间的更重要的属性。即使系统响应时间较长,响应时间易变性低也有助于用户建立起稳定的工作节奏。例如,稳定在1秒的响应时间比从0.1秒到2.5秒变化的响应时间要好。
用户帮助设施
几乎交互式系统的每个用户都需要帮助,当遇到复杂问题时甚至需要查看用户手册以寻找答案。大多数现代软件都提供联机帮助设施,这使得用户无须离开用户界面就能解决自己的问题。常见的帮助设施可分为集成的和附加的两类。集成的帮助设施从一开始就设计在软件里面,通常对用户工作内容是敏感的,因此用户可以从与刚刚完成的操作有关的主题中选择一个请求帮助。可以缩短用户获得帮助的时间,增加界面的友好性。附加的帮助设施是在系统建成后再添加到软件中的,在多数情况下它实际上是一种查询能力有限的联机用户手册。集成的帮助设施优于附加的帮助设施。具体设计帮助设施时,必须解决下述的一系列问题。
(1) 在用户与系统交互期间,是否在任何时候都能获得关于系统任何功能的帮助信息?有两种选择:提供部分功能的帮助信息和提供全部功能的帮助信息。
(2) 用户怎样请求帮助?有3种选择:帮助菜单,特殊功能键和HELP命令。
(3) 怎样显示帮助信息?有3种选择:在独立的窗口中,指出参考某个文档和在屏幕固定位置显示简短提示。
(4) 用户怎样返回到正常的交互方式中?有两种选择:屏幕上的返回按钮和功能键。
(5) 怎样组织帮助信息?有3种选择:平面结构,信息的层次结构和超文本结构。
出错信息处理
出错信息和警告信息,是出现问题时交互式系统给出的“坏消息”。出错信息设计得不好,将向用户提供无用的甚至误导的信息,反而会加重用户的挫折感。一般说来,交互式系统给出的出错信息或警告信息,应该具有下述属性。
(1) 信息应该用用户可以理解的术语描述问题。
(2) 信息应该提供有助于从错误中恢复的建设性意见。
(3) 信息应该指出错误可能导致哪些负面后果(例如,破坏数据文件),以便用户检查是否出现了这些问题,并在确实出现问题时及时解决。
(4) 信息应该伴随着听觉上或视觉上的提示,例如,在显示信息时同时发出警告铃声,或者信息用闪烁方式显示,或者信息用明显表示出错的颜色显示。
(5) 信息不能带有指责色彩,也就是说,不能责怪用户。当确实出现了问题的时候,有效的出错信息能提高
交互式系统的质量,减轻用户的挫折感。
命令交互
现在,面向窗口的、点击和拾取方式的界面已经减少了用户对命令行的依赖,但是,许多高级用户仍然偏爱面向命令行的交互方式。在多数情况下,用户既可以从菜单中选择软件功能,也可以通过键盘命令序列调用软件功能。在提供命令交互方式时,必须考虑下列设计问题。
(1) 是否每个菜单选项都有对应的命令?
(2) 采用何种命令形式?有3种选择:控制序列(例如,Ctrl+P),功能键和键入命令。
(3) 学习和记忆命令的难度有多大?忘记了命令怎么办?
(4) 用户是否可以定制或缩写命令?
在越来越多的应用软件中,人机界面设计者都提供了“命令宏机制”,利用这种机制用户可以用自己定义的名字代表一个常用的命令序列。需要使用这个命令序列时,用户无须依次键入每个命令,只需输入命令宏的名字就可以顺序执行它所代表的全部命令。在理想的情况下,所有应用软件都有一致的命令使用方法。如果在一个应用软件中命令Ctrl+D表示复制一个图形对象,而在另一个应用软件中Ctrl+D命令的含义是删除一个图形对象,显然会使用户感到困惑,并且往往会导致用错命令。
用户界面设计是一个迭代的过程,也就是说,通常先创建设计模型,再用原型实现这个设计模型,并由用户试用和评估,然后根据用户意见进行修改。
用户界面设计主要依靠设计者的经验。总结众多设计者的经验得出的设计指南,有助于设计者设计出友好、高效的人机界面。下面介绍3类人机界面设计指南。
程序流程图又称为程序框图,它是历史最悠久、使用最广泛的描述过程设计的方法,然而它也是用得最混乱的一种方法。程序流程图一直是软件设计的主要工具。它的主要优点是对控制流程的描绘很直观,便于初学者掌握。至今仍在广泛使用着。
程序流程图的主要缺点如下:
(1) 程序流程图本质上不是逐步求精的好工具,它诱使程序员过早地考虑程序的控制流程,而不去考虑程序的全局结构。
(2) 程序流程图中用箭头代表控制流,因此程序员不受任何约束,可以完全不顾结构程序设计的精神,随意转移控制。
(3) 程序流程图不易表示数据结构。
Nassi和Shneiderman提出了盒图,又称为N-S图。它有下述特点:
(1) 功能域(即,一个特定控制结构的作用域)明确,可以从盒图上一眼就看出来。
(2) 不可能任意转移控制。
(3) 很容易确定局部和全程数据的作用域。
(4) 很容易表现嵌套关系,也可以表示模块的层次结构。
图6.4给出了结构化控制结构的盒图表示,也给出了调用子程序的盒图表示方法。
盒图没有箭头,因此不允许随意转移控制。坚持使用盒图作为详细设计的工具,可以使程序员逐步养成用结构化的方式思考问题和解决问题的习惯。
PAD是问题分析图(problem analysis diagram)的英文缩写,1973年由日立公司发明。它用二维树形结构的图来表示程序的控制流,将这种图翻译成程序代码比较容易。图6.5给出PAD图的基本符号。
PAD图的主要优点如下:
(1) 使用表示结构化控制结构的PAD符号所设计出来的程序必然是结构化程序。
(2) PAD图所描绘的程序结构十分清晰。图中最左面的竖线是程序的主线,即第一层结构。随着程序层次的增加,PAD图逐渐向右延伸,每增加一个层次,图形向右扩展一条竖线。PAD图中竖线的总条数就是程序的层次数。
(3) 用PAD图表现程序逻辑,易读、易懂、易记。
(4) 容易将PAD图转换成高级语言源程序。
(5) 既可用于表示程序逻辑,也可用于描绘数据结构。
(6) PAD图的符号支持自顶向下、逐步求精方法的使用。PAD图是面向高级程序设计语言的,FORTRAN,COBOL和PASCAL等每种常用的高级程序设计语言都提供了一整套相应的图形符号。
当算法中包含多重嵌套的条件选择时,用判定表却能够清晰地表示复杂的条件组合与应做的动作之间的对应关系。
一张判定表由4部分组成,左上部列出所有条件,左下部是所有可能做的动作,右上部是表示各种条件组合的一个矩阵,右下部是和每种条件组合相对应的动作。判定表右半部的每一列实质上是一条规则,规定了与特定的条件组合相对应的动作。
下面以行李托运费的算法为例说明判定表的组织方法。假设某航空公司规定,乘客可以免费托运重量不超过30kg的行李。当行李重量超过30kg时,对头等舱的国内乘客超重部分每公斤收费4元,对其他舱的国内乘客超重部分每公斤收费6元,对外国乘客超重部分每公斤收费比国内乘客多一倍,对残疾乘客超重部分每公斤收费比正常乘客少一半。用判定表可以清楚地表示与上述每种条件组合相对应的计算行李费的算法,如表6.1所示。
从上面这个例子可以看出,判定表能够简洁而又无歧义地描述处理规则。当把判定表和布尔代数或卡诺图结合起来使用时,可以对判定表进行校验或化简。但是,判定表并不适于作为一种通用的设计工具,没有一种简单的方法使它能同时清晰地表示顺序和重复等处理特性。
判定表虽然能清晰地表示复杂的条件组合与应做的动作之间的对应关系,但其含义却不是一眼就能看出来的。此外,当数据元素的值多于两个时(例如,6.3.4例子中假设对机票需细分为头等舱、二等舱和经济舱等多种级别时),判定表的简洁程度也将下降。
判定树是判定表的变种,也能清晰地表示复杂的条件组合与应做的动作之间的对应关系。判定树的优点在于,它的形式简单到不需任何说明,一眼就可以看出其含义,因此易于掌握和使用。判定树是一种比较常用的系统分析和设计的工具。图6.7是和表6.1等价的判定树。
**过程设计语言(PDL)**也称为伪码,是用正文形式表示数据和处理过程的设计工具。
PDL具有严格的关键字外部语法,用于定义控制结构和数据结构;另一方面,PDL表示实际操作和条件的内部语法通常又是灵活自由的,可以适应各种工程项目的需要。因此,PDL是一种“混杂”语言,它使用一种语言的词汇,同时却使用另一种语言(某种结构化的程序设计语言)的语法。
PDL应该具有下述特点:
(1) 关键字的固定语法,它提供了结构化控制结构、数据说明和模块化的特点。为了使结构清晰和可读性好,通常在所有可能嵌套使用的控制结构的头和尾都有关键字,例如,if…endif等等。
(2) 自然语言的自由语法,它描述处理特点。
(3) 数据说明的手段。应该既包括简单的数据结构(例如纯量和数组),又包括复杂的数据结构(例如,链表或层次的数据结构)。
(4) 模块定义和调用的技术,应该提供各种接口描述模式。
PDL作为一种设计工具有如下一些优点:
(1) 可以作为注释直接插在源程序中间。这样做能促使维护人员在修改程序代码的同时也相应地修改PDL注释,因此有助于保持文档和程序的一致性,提高了文档的质量。
(2) 可以使用普通的正文编辑程序或文字处理系统,很方便地完成PDL的书写和编辑工作。
(3) 已经有自动处理程序存在,而且可以自动由PDL生成程序代码。PDL的缺点是不如图形工具形象直观,描述复杂的条件组合与动作间的对应关系时,不如判定表清晰简单。
数据结构既影响程序的结构又影响程序的处理过程,重复出现的数据通常由具有循环控制结构的程序来处理,可选择的数据要用带有分支控制结构的程序来处理。
面向数据结构的设计方法的最终目标是得出对程序处理过程的描述。这种方法最适合于在详细设计阶段使用,也就是说,在完成了软件结构设计之后,可以使用面向数据结构的方法来设计每个模块的处理过程。
Jackson方法和Warnier方法是最著名的两个面向数据结构的设计方法。使用面向数据结构的设计方法,当然首先需要分析确定数据结构,并且用适当的工具清晰地描绘数据结构。
虽然程序中实际使用的数据结构种类繁多,但是它们的数据元素彼此间的逻辑关系却只有顺序、选择和重复3类,因此,逻辑数据结构也只有这3类。
顺序结构
顺序结构的数据由一个或多个数据元素组成,每个元素按确定次序出现一次。图6.8是表示顺序结构的Jackson图的一个例子。
选择结构
选择结构的数据包含两个或多个数据元素,每次使用这个数据时按一定条件从这些数据元素中选择一个。图6.9是表示3个中选1个结构的Jackson图
重复结构
重复结构的数据,根据使用时的条件由一个数据元素出现零次或多次构成。图6.10是表示重复结构的Jackson图。
Jackson图有下述优点:
便于表示层次结构,而且是对结构进行自顶向下分解的有力工具;形象直观可读性好;既能表示数据结构也能表示程序结构(因为结构程序设计也只使用上述3种基本控制结构)。
Jackson图实质上是对第3.7节中介绍的层次方框图的一种精化。虽然Jackson图和描绘软件结构的层次图形式相当类似,但是含义却很不相同,即,层次图中的一个方框通常代表一个模块;而Jackson图即使在描绘程序结构时,一个方框也并不代表一个模块,通常一个方框只代表几个语句。层次图表现的是调用关系,通常一个模块除了调用下级模块外,还完成其他操作;而Jackson图表现的是组成关系,也就是说,一个方框中包括的操作仅仅由它下层框中的那些操作组成
Jackson结构程序设计方法基本上由下述5个步骤组成:
(1) 分析并确定输入数据和输出数据的逻辑结构,并用Jackson图描绘这些数据结构。
(2) 找出输入数据结构和输出数据结构中有对应关系的数据单元。所谓有对应关系是指有直接的因果关系,在程序中可以同时处理的数据单元(对于重复出现的数据单元必须重复的次序和次数都相同才可能有对应关系)。
(3) 用下述3条规则从描绘数据结构的Jackson图导出描绘程序结构的Jackson图:
第一,为每对有对应关系的数据单元,按照它们在数据结构图中的层次在程序结构图的相应层次画一个处理框(注意,如果这对数据单元在输入数据结构和输出数据结构中所处的层次不同,则和它们对应的处理框在程序结构图中所处的层次与它们之中在数据结构图中层次低的那个对应);
第二,根据输入数据结构中剩余的每个数据单元所处的层次,在程序结构图的相应层次分别为它们画上对应的处理框;
第三,根据输出数据结构中剩余的每个数据单元所处的层次,在程序结构图的相应层次分别为它们画上对应的处理框。总之,描绘程序结构的Jackson图应该综合输入数据结构和输出数据结构的层次关系而导出来。在导出程序结构图的过程中,由于改进的Jackson图规定在构成顺序结构的元素中不能有重复出现或选择出现的元素,因此可能需要增加中间层次的处理框。
(4) 列出所有操作和条件(包括分支条件和循环结束条件),并且把它们分配到程序结构图的适当位置。
(5) 用伪码表示程序。Jackson方法中使用的伪码和Jackson图是完全对应的,下面是和3种基本结构对应的伪码。
下面结合一个具体例子进一步说明Jackson结构程序设计方法。
[例]一个正文文件由若干个记录组成,每个记录是一个字符串。要求统计每个记录中空格字符的个数,以及文件中空格字符的总个数。要求的输出数据格式是,每复制一行输入字符串之后,另起一行印出这个字符串中的空格数,最后印出文件中空格的总个数。对于这个简单例子而言,输入和输出数据的结构很容易确定。图6.12是用Jackson图描绘的输入输出数据结构。
确定了输入输出数据结构之后,第二步是分析确定在输入数据结构和输出数据结构中有对应关系的数据单元。在这个例子中哪些数据单元有对应关系呢?输出数据总是通过对输入数据的处理而得到的,因此在输入输出数据结构最高层次的两个单元总是有对应关系的。这一对单元将和程序结构图中最顶层的方框(代表程序)相对应,也就是说经过程序的处理由正文文件得到输出表格。因为每处理输入数据中一个“字符串”之后,就可以得到输出数据中一个“串信息”,它们都是重复出现的数据单元,而且出现次序和重复次数都完全相同,因此,“字符串”和“串信息”也是一对有对应关系的单元。还有其他有对应关系的单元吗?为了回答这个问题依次考察输入数据结构中余下的每个数据单元。“字符”不可能和多个字符组成的“字符串”对应,和输出数据结构中其他数据单元也不能对应。“空格”能和“空格数”对应吗?显然,单个空格并不能决定一个记录中包含的空格个数,因此没有对应关系。通过类似的考察发现,输入数据结构中余下的任何一个单元在输出数据结构中都找不到对应的单元,也就是说,在这个例子中输入输出数据结构中只有上述两对有对应关系的单元。在图6.12中用一对虚线箭头把有对应关系的数据单元连接起来,以突出表明这种对应关Jackson程序设计方法的第三步是从数据结构图导出程序结构图。按照前面已经讲述过的规则,这个步骤的大致过程是:
首先,在描绘程序结构的Jackson图的最顶层画一个处理框“统计空格”,它与“正文文件”和“输出表格”这对最顶层的数据单元相对应。但是接下来还不能立即画与另一对数据单元(“字符串”和“串信息”)相对应的处理框,因为在输出数据结构中“串信息”的上层还有“表格体”和“空格总数”两个数据单元,在程序结构图的第二层应该有与这两个单元对应的处理框——“程序体”和“印总数”。因此,在程序结构图的第三层才是与“字符串”和“串信息”相对应的处理框——“处理字符串”。在程序结构图的第四层似乎应该是和“字符串”、“字符”及“空格数”等数据单元对应的处理框“印字符串”、“分析字符”及“印空格数”,这3个处理是顺序执行的。但是,“字符”是重复出现的数据单元,因此“分析字符”也应该是重复执行的处理。改进的Jackson图规定顺序执行的处理中不允许混有重复执行或选择执行的处理,所以在“分析字符”这个处理框上面又增加了一个处理框“分析字符串”。最后得到的程序结构图为图6.13。
Jackson程序设计方法的第四步是列出所有操作和条件,并且把它们分配到程序结构图的适当位置。
首先,列出统计空格个数需要的全部操作和条件。经过简单分析不难把这些操作和条件分配到程序结构图的适当位置,结果为图6.14。Jackson方法的最后一步是用伪码表示程序处理过程。因为Jackson使用的伪码和Jackson图之间存在简单的对应关系,所以从图6.14很容易得出下面的伪码
以上简单介绍了由英国人M.Jackson提出的结构程序设计方法。这个方法在设计比较简单的数据处理系统时特别方便,当设计比较复杂的程序时常常遇到输入数据可能有错、条件不能预先测试、数据结构冲突等问题。为了克服上述困难,把Jackson方法应用到更广阔的领域,需要采用一系列比较复杂的辅助技术,详细介绍这些技术已经超出本书的范围。
详细设计阶段设计出的模块质量如何呢?人们希望能定量度量软件的性质。本节将要介绍的程序复杂程度定量度量方法是其中比较成熟的一种。定量度量程序复杂程度的方法很有价值:把程序的复杂程度乘以适当常数即可估算出软件中错误的数量以及软件开发需要用的工作量,定量度量的结果可以用来比较两个不同的设计或两个不同算法的优劣;程序的定量的复杂程度可以作为模块规模的精确限度。下面介绍使用得比较广泛的McCabe方法和Halstead方法。
流图
McCabe方法根据程序控制流的复杂程度定量度量程序的复杂程度,这样度量出的结果称为程序的环
形复杂度。为了突出表示程序的控制流,人们通常使用流图(也称为程序图)。所谓流图实质上是“退化了的”
程序流程图,它仅仅描绘程序的控制流程,完全不表现对数据的具体操作以及分支或循环的具体条件。
在流图中用圆表示结点,一个圆代表一条或多条语句。程序流程图中的一个顺序的处理框序列和一个菱形判定框,可以映射成流图中的一个结点。流图中的箭头线称为边,它和程序流程图中的箭头线类似,代表控制流。在流图中一条边必须终止于一个结点,即使这个结点并不代表任何语句(实际上相当于一个空语句)。由边和结点围成的面积称为区域,当计算区域数时应该包括图外部未被围起来的那个区域。
图6.15举例说明把程序流程图映射成流图的方法。用任何方法表示的过程设计结果,都可以翻译成流图。图6.16是用PDL表示的处理过程及与之对应的流图。当过程设计中包含复合条件时,生成流图的方法稍微复杂一些。所谓复合条件,就是在条件中包含了一个或多个布尔运算符(逻辑OR,AND,NAND,NOR)。在这种情况下,应该把复合条件分解为若干个简单条件,每个简单条件对应流图中一个结点。包含条件的结点称为判定节点,从每个判定结点引出两条或多条边。图6.17是由包含复合条件的PDL片断翻译成的流图。
计算环形复杂度的方法
环形复杂度定量度量程序的逻辑复杂度。有了描绘程序控制流的流图之后,可以用下述3种方法中的任何一种来计算环形复杂度。
(1) 流图中的区域数等于环形复杂度。
(2) 流图G的环形复杂度V(G)=E-N+2,其中,E是流图中边的条数,N是结点数。
(3) 流图G的环形复杂度V(G)=P+1,其中,P是流图中判定结点的数目。
环形复杂度的用途
程序的环形复杂度取决于程序控制流的复杂程度,
也即是取决于程序结构的复杂程度。当程序内分支
数或循环个数增加时,环形复杂度也随之增加,因
此它是对测试难度的一种定量度量,也能对软件最
终的可靠性给出某种预测。
McCabe研究大量程序后发现,环形复杂度高的程
序往往是最困难、最容易出问题的程序。实践表明,
模块规模以V(G)≤10为宜,也就是说,V(G)=10是
模块规模的一个更科学更精确的上限。
Halstead方法是另一个著名的方法,它根据程序中运算符和操作数的总数来度量程序的复杂程度。令N1为程序中运算符出现的总次数,N2为操作数出现的总次数,程序长度N定义为:N=N1+N2详细设计完成之后,可以知道程序中使用的不同运算符(包括关键字)的个数n1,以及不同操作数(变量和常数)的个数n2。Halstead给出预测程序长度的公式如下:H=n1 log2 n1+n2 log2 n2 多次验证都表明,预测的长度H与实际长度N非常接近。
Halstead还给出了预测程序中包含错误的个数的公式如下:E=N log2(n1+n2)/3000
有人曾对从300条到12000条语句范围内的程序核实了上述公式,发现预测的错误数与实际错误数相比误差在8%之内。
详细设计阶段的关键任务是确定怎样具体地实现用户需要的软件系统,也就是要设计出程序的“蓝图”。除了应该保证软件的可靠性之外,使将来编写出的程序可读性好、容易理解、容易测试、容易修改和维护,是详细设计阶段最重要的目标。结构程序设计技术是实现上述目标的基本保证,是进行详细设计的逻辑基础。
人机界面设计是接口设计的一个重要的组成部分。人机界面的质量直接影响用户对软件产品的接受程度,因此,对人机界面设计必须给予足够重视。在设计人机界面的过程中,必须充分重视并认真处理好系统响应时间、用户帮助设施、出错信息处理和命令交互等4个设计问题。总结人们在设计人机界面过程中积累的经验,得出了一些关于用户界面设计的指南,认真遵守这些指南有助于设计出友好、高效的人机界面。
过程设计应该在数据设计、体系结构设计和接口设计完成之后进行,它的任务是设计解题的详细步骤(即算法),它是详细设计阶段应完成的主要工作。过程设计的工具可分为图形、表格和语言3类。在许多应用领域中信息都有清楚的层次结构,在开发这类应用系统时可以采用面向数据结构的设计方法完成过程设计。本章以Jackson结构程序设计技术为例,对面向数据结构的设计方法做了初步介绍。使用环形复杂度可以定量度量程序的复杂程度,实践表明,环形复杂度V(G)=10是模块规模的合理上限。
导读
主要内容
编码
测试技术基础
测试的目的
测试的目标
测试的准则
白盒测试技术
黑盒测试技术
软件可靠性
教材中的页码
教材第145页----第182页
课件中的页码
PPT第7章第1页—第161页
名词术语
白盒测试
黑盒测试
模块测试
单元测试
集成测试
确认测试
系统测试
α测试
β测试
代码审查
逻辑覆盖
语句覆盖
判定覆盖
条件覆盖
判定/条件覆盖
条件组合覆盖
等价划分
重点
编码风格
测试的步骤
如何设计测试的覆盖程度
如何进行等价类的设计
估算平均无故障时间
估算错误总数的方法
难点
保持良好的编码风格
思考题
如何开发出质量高的、风格优秀的代码?
作业题
教材183页第七章习题7的第四题
教材183页第七章习题7的第八题、第九题、第十题
通常把编码和测试统称为实现。所谓编码就是把软件设计结果翻译成用某种程序设计语言书写的程序。程序的质量主要取决于软件设计的质量。但是,所选用的程序设计语言的特点及编码风格也将对程序的可靠性、可读性、可测试性和可维护性产生深远的影响。无论怎样强调软件测试的重要性和它对软件可靠性的影响都不过分。人的主观认识不可能完全符合客观现实,与工程密切相关的各类人员之间的通信和配合也不可能完美无缺,因此,在软件生命周期的每个阶段都不可避免地会产生差错。力求在每个阶段结束之前通过严格的技术审查,尽可能早地发现并纠正差错;但是,经验表明审查并不能发现所有差错,此外在编码过程中还不可避免地会引入新的错误。测试的目的就是在软件投入生产性运行之前,尽可能多地发现软件中的错误。但是,发现错误并不是最终目的。通过测试发现错误之后还必须诊断并改正错误。目前软件测试仍然是保证软件质量的关键步骤,它是对软件规格说明、设计和编码的最后复审。通常在编写出每个模块之后就对它做必要的测试称为单元测试),模块的编写者和测试者是同一个人。在这个阶段结束之后,对软件系统还应该进行各种综合测试,这是软件生命周期中的另一个独立的阶段,通常由专门的测试人员承担这项工作。大量统计资料表明,软件测试的工作量往往占软件开发总工作量的40%以上。因此,必须高度重视软件测试工作。
程序设计语言是人和计算机通信的最基本的工具,它的特点必然会影响人的思维和解题方式,会影响
人和计算机通信的方式和质量,也会影响其他人阅读和理解程序的难易程度。因此,编码之前的一项
重要工作就是选择一种适当的程序设计语言。
适宜的程序设计语言能使根据设计去完成编码时困难最少,可以减少需要的程序测试量,并且可以得
出更容易阅读和更容易维护的程序。由于软件系统的绝大部分成本用在生命周期的测试和维护阶段,
所以容易测试和容易维护是极端重要的。使用汇编语言编码需要把软件设计翻译成机器操作的序列,由于这两种表示方法很不相同,因此汇编程序设计既困难又容易出差错。高级语言使用的符号和概念更符合人的习惯。因此,用高级语言写的程序容易阅读,容易测试,容易调试,容易维护。总的说来,高级语言明显优于汇编语言,因此,除了在很特殊的应用领域(例如,对程序执行时间和使用的空间都有很严格限制的情况;需要产生任意的甚至非法的指令序列;体系结构特殊的微处理机,以致在这类机器上通常不能实现高级语言编译程序),或者大型系统中执行时间非常关键的(或直接依赖于硬件的)一小部分代码需要用汇编语言书写之外,其他程序应该一律用高级语言书写。为了使程序容易测试和维护以减少软件的总成本,所选用的高级语言应该有理想的模块化机制,以及可读性好的控制结构和数据结构;为了便于调试和提高软件可靠性,语言特点应该使编译程序能够尽可能多地发现程序中的错误;为了降低软件开发和维护的成本,选用的高级语言应该有良好的独立编译机制。在实际选择语言时不能仅仅使用理论上的标准,还必须同时考虑实用方面的各种限制。下面是主要的实用标准:
(1) 系统用户的要求。如果所开发的系统由用户负责维护,用户通常要求用他们熟悉的语言书写程序。
(2) 可以使用的编译程序。运行目标系统的环境中可以提供的编译程序往往限制了可以选用的语言的范围。
(3) 可以得到的软件工具。如果某种语言有支持程序开发的软件工具可以利用,则目标系统的实现和验证都变得比较容易。
(4) 工程规模。如果工程规模很庞大,现有的语言又不完全适用,那么设计并实现一种供这个工程项目专用的程序设计语言,可能是一个正确的选择。
(5) 程序员的知识。如果和其他标准不矛盾,那么应该选择一种已经为程序员所熟悉的语言。
(6) 软件可移植性要求。如果目标系统将在几台不同的计算机上运行,或者预期的使用寿命很长,那么选择一种标准化程度高、程序可移植性好的语言就是很重要的。
(7) 软件的应用领域。所谓的通用程序设计语言实际上并不是对所有应用领域都同样适用。因此,选择语言时应该充分考虑目标系统的应用范围。
源程序代码的逻辑简明清晰、易读易懂是好程序的一个重要标准,为了做到这一点,应该遵循下述规则。
在测试阶段测试人员努力设计出一系列测试方案,目的却是为了“破坏”已经建造好的软件系统——竭力证明程序中有错误不能按照预定要求正确工作。当然,发现问题是为了解决问题,测试阶段的根本目标是尽可能多地发现并排除软件中潜藏的错误,最终把一个高质量的软件系统交给用户使用。
(1) 测试是为了发现程序中的错误而执行程序的过程;
(2) 好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案;
(3) 成功的测试是发现了至今为止尚未发现的错误的测试。
正确认识测试的目标是十分重要的,测试目标决定了测试方案的设计。如果为了表明程序是正确的而进行测试,就会设计一些不易暴露错误的测试方案;相反,如果测试是为了发现程序中的错误,就会力求设计出最能暴露错误的测试方案。由于测试的目标是暴露程序中的错误,由程序的编写者自己进行测试是不恰当的。因此,在综合测试阶段通常由其他人员组成测试小组来完成测试工作。此外,即使经过了最严格的测试之后,仍然可能还有没被发现的错误潜藏在程序中。测试只能查找出程序中的错误,不能证明程序中没有错误。
为了能设计出有效的测试方案,软件工程师必须深入理解并正确运用指导软件测试的基本准则。下面
讲述主要的测试准则。
(1) 所有测试都应该能追溯到用户需求。正如上一小节讲过的,软件测试的目标是发现错误。从用户的角度看,最严重的错误是导致程序不能满足用户需求的那些错误。
(2) 应该远在测试开始之前就制定出测试计划。实际上,一旦完成了需求模型就可以着手制定测试计划,在建立了设计模型之后就可以立即开始设计详细的测试方案。因此,在编码之前就可以对所有测试工作进行计划和设计。
(3) 把二八原则应用到软件测试中。pareto原理:测试中发现的错误中的80%很可能是由程序中20%的模块造成的。
(4) 应该从“小规模”测试开始,并逐步进行“大规模”测试。通常,首先重点测试单个程序模块,然后把测试重点转向在集成的模块簇中寻找错误,最后在整个系统中寻找错误。
(5) 穷举测试是不可能的。所谓穷举测试就是把程序所有可能的执行路径都检查一遍的测试。但是,精心地设计测试方案,有可能充分覆盖程序逻辑并使程序达到所要求的可靠性。
(6) 为了达到最佳的测试效果,应该由独立的第三方从事测试工作。
测试有两种方法:
黑盒测试:如果已经知道了产品应该具有的功能,可以通过测试来检验是否每个功能都能正常使用;
白盒测试:如果知道产品的内部工作过程,可以通过测试来检验产品内部动作是否按照规格说明书的规定正常进行。
对于软件测试而言,黑盒测试法把程序看作一个黑盒子,完全不考虑程序的内部结构和处理过程。也就是说,黑盒测试是在程序接口进行的测试,它只检查程序功能是否能按照规格说明书的规定正常使用,程序是否能适当地接收输入数据并产生正确的输出信息,程序运行过程中能否保持外部信息的完整性。黑盒测试又称为功能测试。
白盒测试法与黑盒测试法相反,它的前提是可以把程序看成装在一个透明的白盒子里,测试者完全知道程序的结构和处理算法。这种方法按照程序内部的逻辑测试程序,检测程序中的主要执行通路是否都能按预定要求正确工作。白盒测试又称为结构测试
测试过程分步骤进行,后一个步骤在逻辑上是前一个步骤的继续。大型软件系统通常由若干个子系统组成,每个子系统又由许多模块组成,因此,大型软件系统的测试过程基本上由下述几个步骤组成
图7.1描绘了测试阶段的信息流,这个阶段的输入信息有两类
(1)软件配置,包括需求说明书、设计说明书和源程序清单等;
(2)测试配置,包括测试计划和测试方案。所谓测试方案不仅仅是测试时使用的输入数据(称为测试用例),还应该包括每组输入数据预定要检验的功能,以及每组输入数据预期应该得到的正确输出。实际上测试配置是软件配置的一个子集,最终交出的软件配置应该包括上述测试配置以及测试的实际结果和调试的记录。
比较测试得出的实际结果和预期的结果,如果两者不一致则很可能是程序中有错误。设法确定错误的准确位置并且改正它,这就是调试的任务。与测试不同,通常由程序的编写者负责调试。
单元测试集中检测软件设计的最小单元——模块。在编写出源程序代码并通过语法检查之后,就可以用详细设计作指南,对重要的执行通路进行测试,以便发现模块内部的错误。单元测试主要使用白盒测试技术,而且对多个模块的测试可以并行地进行。
在单元测试期间着重从下述5个方面对模块进行测试。
人工测试源程序可以由编写者本人非正式地进行,也可以由审查小组正式进行。后者称为代码审查,它是一种非常有效的程序验证技术,对于典型的程序来说,可以查出30%~70%的逻辑设计错误和编码错误。审查小组最好由下述4人组成:
(1) 组长,应该是一个很有能力的程序员,而且没有直接参与这项工程;
(2) 程序的设计者;
(3) 程序的编写者;
(4) 程序的测试者。
如果一个人既是程序的设计者又是编写者,或既是编写者又是测试者,则审查小组中应该再增加一个
程序员。审查之前,小组成员应该先研究设计说明书,力求理解这个设计。可以先由设计者扼要地介绍他的设计,其他成员倾听他的讲解,并力图发现其中的错误。审查会上进行的另外一项工作,是对照类似于
上一小节中介绍的程序设计常见错误清单,分析审查这个程序。当发现错误时由组长记录下来,审查会继续进行(审查小组的任务是发现错误而不是改正错误)。
审查会还有另外一种常见的进行方法,称为预排:由一个人扮演“测试者”,其他人扮演“计算机”。
会前测试者准备好测试方案,会上由扮演计算机的成员模拟计算机执行被测试的程序。在大多数情况
下,通过向程序员提出关于他的程序的逻辑和他编写程序时所做的假设的疑问,可以发现的错误比由
测试方案直接发现的错误还多。
代码审查比计算机测试优越的是:一次审查会上可以发现许多错误;用计算机测试的方法发现错误之
后,通常需要先改正这个错误才能继续测试,因此错误是一个一个地发现并改正的。也就是说,采用代码审查的方法可以减少系统验证的总工作量。
实践表明,对于查找某些类型的错误来说,人工测试比计算机测试更有效;对于其他类型的错误来说则刚好相反。因此,人工测试和计算机测试是互相补充,相辅相成的,缺少其中任何一种方法都会使查找错误的效率降低
模块并不是一个独立的程序,因此必须为每个单元测试开发驱动软件和(或)存根软件。通常驱动程序也就是一个“主程序”,它接收测试数据,把这些数据传送给被测试的模块,并且印出有关的结果。存根程序代替被测试的模块所调用的模块。因此存根程序也可以称为“虚拟子程序”。它使用被它代替的模块的接口,可能做最少量的数据操作,印出对入口的检验或操作结果,并且把控制归还给调用它的模块。
例如,图7.2是一个正文加工系统的部分层次图,假定要测试其中编号为3.0的关键模块——正文编辑模块。因为正文编辑模块不是一个独立的程序,所以需要有一个测试驱动程序来调用它。这个驱动程序说明必要的变量,接收测试数据——字符串,并且设置正文编辑模块的编辑功能。因为在原来的软件结构中,正文编辑模块通过调用它的下层模块来完成具体的编辑功能,所以需要有存根程序简化地模拟这些下层模块。为了简单起见,测试时可以设置的编辑功能只有修改(CHANGE)和添加(APPEND)两种,用控制变量CFUNCT标记要求的编辑功能,而且只用一个存根程序模拟正文编辑模的所有下层模块。下面是用伪码书写的存根程序和驱动程序
![在这里插入图片描述](https://img-blog.csdnimg.cn/9ffd581ca4644e20974cd81cf2f872c2.png =3=400x)
驱动程序和存根程序代表开销,也就是说,为了进行单元测试必须编写测试软件,但是通常并不把它们作为软件产品的一部分交给用户。许多模块不能用简单的测试软件充分测试,为了减少开销可以使用下节将要介绍的渐增式测试方法,在集成测试的过程中同时完成对模块的详尽测试。模块的内聚程度高可以简化单元测试过程。如果每个模块只完成一种功能,则需要的测试方案数目将明显减少,模块中的错误也更容易预测和发现。
集成测试是测试和组装软件的系统化技术,例如,子系统测试即是在把模块按照设计要求组装起来的同时进行测试,主要目标是发现与接口有关的问题。例如,数据穿过接口时可能丢失;一个模块对另一个模块可能由于疏忽而造成有害影响;把子功能组合起来可能不产生预期的主功能;个别看来是可以接受的误差可能积累到不能接受的程度;全程数据结构可能有问题等等。
由模块组装成程序时有两种方法。
一种方法是先分别测试每个模块,再把所有模块按设计要求放在一起结合成所要的程序,这种方法称
为非渐增式测试方法;
另一种方法是把下一个要测试的模块同已经测试好的那些模块结合起来进行测试,测试完以后再把下
一个应该测试的模块结合进来测试。这种每次增加一个模块的方法称为渐增式测试,这种方法实际上
同时完成单元测试和集成测试。
非渐增式测试一下子把所有模块放在一起,并把庞大的程序作为一个整体来测试,测试者面对的情况十分复杂。测试时会遇到许许多多的错误,改正错误更是极端困难,因为在庞大的程序中想要诊断定位一个错误是非常困难的。而且一旦改正一个错误后,马上又会遇到新的错误,这个过程将继续下去,看起来好像永远也没有尽头。渐增式测试与“一步到位”的非渐增式测试相反,它把程序划分成小段来构造和测试,在这个过程中比较容易定位和改正错误;对接口可以进行更彻底的测试;可以使用系统化的测试方法。因此,目前在进行集成测试时普遍采用渐增式测试方法。当使用渐增方式把模块结合到程序中去时,有自顶向下和自底向上两种集成策略。
自顶向下集成方法是一个日益为人们广泛采用的测试和组装软件的途径。从主控制模块开始,沿着程序的控制层次向下移动,逐渐把各个模块结合起来。在把附属于主控制模块的那些模块组装到程序结构中去时,或者使用深度优先的策略,或者使用宽度优先的策略。
参看图7.3,深度优先的结合方法先组装在软件结构的一条主控制通路上的所有模块。选择一条主控制通路取决于应用的特点,并且有很大任意性。而宽度优先的结合方法是沿软件结构水平地移动,把处于同一个控制层次上的所有模块组装起来。
把模块结合进软件结构的具体过程由下述4个步骤完成:
第一步,对主控制模块进行测试,测试时用存根程序代替所有直接附属于主控制模块的模块;
第二步,根据选定的结合策略(深度优先或宽度优先),每次用一个实际模块代换一个存根程序(新结合进来的模块往往又需要新的存根程序);
第三步,在结合进一个模块的同时进行测试;
第四步,为了保证加入模块没有引进新的错误,可能需要进行回归测试(即,全部或部分地重复以前做过的测试)。
从第二步开始不断地重复进行上述过程,直到构造起完整的软件结构为止。图7.3描绘了这个过程。
自顶向下的结合策略能够在测试的早期对主要的控制或关键的抉择进行检验。在一个分解得好的软件结构中,关键的抉择位于层次系统的较上层,因此首先碰到。如果选择深度优先的结合方法,可以在早期实现软件的一个完整的功能并且验证这个功能。
自顶向下的方法讲起来比较简单,但是实际使用时可能遇到逻辑上的问题。这类问题中最常见的是,为了充分地测试软件系统的较高层次,需要在较低层次上的处理。然而在自顶向下测试的初期,存根程序代替了低层次的模块,因此,在软件结构中没有重要的数据自下往上流。为了解决这个问题,测试人员有两种选择: 第一,把许多测试推迟到用真实模块代替了存根程序以后再进行;第二,从层次系统的底部向上组装软件。第一种方法失去了在特定的测试和组装特定的模块之间的精确对应关系,这可能导致在确定错误的位置和原因时发生困难。
后一种方法称为自底向上的测试,下面讨论这种方法。
自底向上测试从“原子”模块(即在软件结构最低层的模块)开始组装和测试。因为是从底部向上结合模块,总能得到所需的下层模块处理功能,所以不需要存根程序。
用下述步骤可以实现自底向上的结合策略:
第一步,把低层模块组合成实现某个特定的软件子功能的族;
第二步,写一个驱动程序(用于测试的控制程序),协调测试数据的输入和输出;
第三步,对由模块组成的子功能族进行测试;
第四步,去掉驱动程序,沿软件结构自下向上移动,把子功能族组合起来形成更大的子功能族。上述第二步到第四步实质上构成了一个循环。图7.4描绘了自底向上的结合过程。
随着结合向上移动,对测试驱动程序的需要也减少了。事实上,如果软件结构的顶部两层用自顶向下的方法组装,可以明显减少驱动程序的数目,而且族的结合也将大大简化。
一般说来,一种方法的优点正好对应于另一种方法的缺点。自顶向下测试方法的主要优点是不需要测试驱动程序,能够在测试阶段的早期实现并验证系统的主要功能,而且能在早期发现上层模块的接口错误。自顶向下测试方法的主要缺点是需要存根程序,可能遇到与此相联系的测试困难,低层关键模块中的错误发现较晚,而且用这种方法在早期不能充分展开人力。可以看出,自底向上测试方法的优缺点与上述自顶向下测试方法的优缺点刚好相反。
在测试实际的软件系统时,应该根据软件的特点以及工程进度安排,选用适当的测试策略。一般说来,纯粹自顶向下或纯粹自底向上的策略可能都不实用,人们在实践中创造出许多混合策略:
(1) 改进的自顶向下测试方法。基本上使用自顶向下的测试方法,但是在早期使用自底向上的方法测试软件中的少数关键模块。一般的自顶向下方法所具有的优点在这种方法中也都有,而且能在测试的早期发现关键模块中的错误;但是,它的缺点也比自顶向下方法多一条,即测试关键模块时需要驱动程序。
(2) 混合法。对软件结构中较上层使用的自顶向下方法与对软件结构中较下层使用的自底向上方法相结合。这种方法兼有两种方法的优点和缺点,当被测试的软件中关键模块比较多时,这种混合法可能是最好的折衷方法。
在集成测试过程中每当一个新模块结合进来时,程序就发生了变化:建立了新的数据流路径,可能出现了新的I/O操作,激活了新的控制逻辑。这些变化有可能使原来工作正常的功能出现问题。在集成测试的范畴中,所谓回归测试是指重新执行已经做过的测试的某个子集,以保证上述这些变化没有带来非预期的副作用。
回归测试就是用于保证由于调试或其他原因引起的变化,不会导致非预期的软件行为或额外错误的测试活动。
回归测试可以通过重新执行全部测试用例的一个子集人工地进行,也可以使用自动化的捕获回放工具自动进行。利用捕获回放工具,软件工程师能够捕获测试用例和实际运行结果,然后可以回放(即重新执行测试用例),并且比较软件变化前后所得到的运行结果。
回归测试集(已执行过的测试用例的子集)包括下述3类不同的测试用例:
(1) 检测软件全部功能的代表性测试用例;
(2) 专门针对可能受修改影响的软件功能的附加测试;
(3) 针对被修改过的软件成分的测试。
在集成测试过程中,回归测试用例的数量可能变得非常大。因此,应该把回归测试集设计成只包括可以检测程序每个主要功能中的一类或多类错误的那样一些测试用例。一旦修改了软件之后就重新执行检测程序每个功能的全部测试用例,是低效而且不切实际的。
确认测试也称为验收测试,它的目标是验证软件的有效性。什么样的软件才是有效的呢?软件有效性的一个简单定义是: 如果软件的功能和性能如同用户所合理期待的那样,软件就是有效的。
需求分析阶段产生的软件需求规格说明书,准确地描述了用户对软件的合理期望,因此是软件有效性的标准,也是进行确认测试的基础。
确认测试必须有用户积极参与,或者以用户为主进行。用户应该参与设计测试方案,使用用户界面输入测试数据并且分析评价测试的输出结果。为了使得用户能够积极主动地参与确认测试,特别是为了使用户能有效地使用这个系统,通常在验收之前由开发单位对用户进行培训。
确认测试通常使用黑盒测试法。应该仔细设计测试计划和测试过程,测试计划包括要进行的测试的种类及进度安排,测试过程规定了用来检测软件是否与需求一致的测试方案。通过测试和调试要保证软件能满足所有功能要求,能达到每个性能要求,文档资料是准确而完整的,此外,还应该保证软件能满足其他预定的要求(例如,安全性、可移植性、兼容性和可维护性等)。
确认测试有下述两种可能的结果:
(1) 功能和性能与用户要求一致,软件是可以接受的;
(2) 功能和性能与用户要求有差距。
在这个阶段发现的问题往往和需求分析阶段的差错有关,涉及的面通常比较广,因此解决起来也比较困难。为了制定解决确认测试过程中发现的软件缺陷或错误的策略,通常需要和用户充分协商。
确认测试的一个重要内容是复查软件配置。复查的目的是保证软件配置的所有成分都齐全,质量符合要求,文档与程序完全一致,具有完成软件维护所必须的细节,而且已经编好目录。
除了按合同规定的内容和要求,由人工审查软件配置之外,在确认测试过程中还应该严格遵循用户指南及其他操作程序,以便检验这些使用手册的完整性和正确性。必须仔细记录发现的遗漏或错误,并且适当地补充和改正。
如果软件是专为某个客户开发的,可以进行一系列验收测试,以便用户确认所有需求都得到满足了。如果一个软件是为许多客户开发的,那么,让每个客户都进行正式的验收测试是不现实的。在这种情况下,绝大多数软件开发商都使用被称为Alpha测试和Beta测试的过程,来发现那些看起来只有最终用户才能发现的错误。
Alpha测试由用户在开发者的场所进行,并且在开发者对用户的“指导”下进行测试。开发者负责记录发现的错误和使用中遇到的问题。总之,Alpha测试是在受控的环境中进行的。
Beta测试由软件的最终用户们在一个或多个客户场所进行。与Alpha测试不同,开发者通常不在Beta测试的现场,因此,Beta测试是软件在开发者不能控制的环境中的“真实”应用。用户记录在Beta测
试过程中遇到的一切问题,并且定期把这些问题报给开发者。接收到在Beta测试期间报告的问题之后,开发者对软件产品进行必要的修改。
设计测试方案是测试阶段的关键技术问题。所谓测试方案包括具体的测试目的(例如,预定要测试的
具体功能),应该输入的测试数据和预期的结果。通常又把测试数据和预期的输出结果称为测试用例。
其中最困难的问题是设计测试用的输入数据。不同的测试数据发现程序错误的能力差别很大,为了提高测试效率降低测试成本,应该选用高效的测试数据。因为不可能进行穷尽的测试,选用少量“最有效的”测试数据,做到尽可能完备的测试就更重要了。
有选择地执行程序中某些最有代表性的通路是对穷尽测试的惟一可行的替代办法。所谓逻辑覆盖是对一系列测试过程的总称,这组测试过程逐渐进行越来越完整的通路测试。测试数据执行(或叫覆盖)程序逻辑的程度可以划分成哪些不同的等级呢?从覆盖源程序语句的详尽程度分析,大致有以下一些不
同的覆盖标准。
第二步,计算流图的环形复杂度。
环形复杂度定量度量程序的逻辑复杂性。有了描绘程序控制流的流图之后,可以用第6.5.1小节讲述的3种方法之一计算环形复杂度。经计算,图7.6所示流图的环形复杂度为6。
第三步,确定线性独立路径的基本集合。
所谓独立路径是指至少引入程序的一个新处理语句集合或一个新条件的路径,用流图术语描述,独立
路径至少包含一条在定义该路径之前不曾用过的边。使用基本路径测试法设计测试用例时,程序的环形
复杂度决定了程序中独立路径的数量,而且这个数是确保程序中所有语句至少被执行一次所需的测试数量的上界。
对于图7.6所描述的求平均值过程来说,由于环形复杂度为6,因此共有6条独立路径。
通常在设计测试用例时,识别出判定结点是很有必要的。本例中结点2、3、5、6和10是判定结点。
第四步,设计可强制执行基本集合中每条路径的测试用例。应该选取测试数据使得在测试每条路径时都适当地设置好了各个判定结点的条件。在测试过程中,执行每个测试用例并把实际输出结果与预期结果相比较。一旦执行完所有测试用例,就可以确保程序中所有语句都至少被执行了一次,
而且每个条件都分别取过true值和false值。应该注意,某些独立路径不能以独立的方式测试,也就是说,程序的正常流程不能形成独立执行该路径所需要的数据组合。在这种情况下,这些路径必须作为另一个路径的一部分来测试。
2. 条件测试
用条件测试技术设计出的测试用例,能够检查程序模块中包含的逻辑条件。一个简单条件是一个布尔变量或一个关系表达式,在布尔变量或关系表达式之前还可能有一个NOT算符。关系表达式的形式如下:
E1<关系算符>E2
其中,E1和E2是算术表达式,而<关系算符>是下
列算符之一:“<”
,
“≤”
,
“
=”
,
“≠”
,
“>”或“≥”
。
复合条件由两个或多个简单条件、布尔算符和括弧组成。布尔算符有OR(“|”),AND(“&”)和NOT。不包含关系表达式的条件称为布尔表达式。因此,条件成分的类型包括布尔算符、布尔变量、布尔括弧(括住简单条件或复合条件)、关系算符及算术表达式。如果条件不正确,则至少条件的一个成分不正确。
因此,条件错误的类型如下:
布尔算符错(布尔算符不正确,遗漏布尔算符或有
多余的布尔算符)
布尔变量错
布尔括弧错
关系算符错
算术表达式错
条件测试方法着重测试程序中的每个条件。
条件测试策略有两个优点: ①容易度量条件的测试覆盖率; ②程序内条件的测试覆盖率可指导附加测试的设计。
条件测试的目的不仅是检测程序条件中的错误,而且是检测程序中的其他错误。如果程序P的测试集能有效地检测P中条件的错误,则它很可能也可以有效地检测P中的其他错误。此外,如果一个测试策略对检测条件错误是有效的,则很可能该策略对检测程序的其他错误也是有效的。人们已经提出了许多条件测试策略。分支测试可能是最简单的条件测试策略:对于复合条件C来说,C的真分支和假分支以及C中的每个简单条件,都应该至少执行一次。域测试要求对一个关系表达式执行3个或4个测试。对于形式为E1<关系算符>E2的关系表达式来说,需要3个测试分别使E1的值大于、等于或小于E2的值。如果<关系算符>错误而E1和E2正确,则这3个测试能够发现关系算符的错误。为了发现E1和E2中的错误,让E1值大于或小于E2值的测试数据应该使这两个值之间的差别尽可能小。包含n个变量的布尔表达式需要2n个测试。这个策略可以发现布尔算符、变量和括弧的错误,但是,该策略仅在n很小时才是实用的。在上述种种条件测试技术基础上,K.C.Tai提出了一种被称为BRO(branch and relational operator)测试的条件测试策略。如果在条件中所有布尔变量和关系算符都只出现一次而且没有公共变量,则BRO测试保证能发现该条件中的分支错和g关系算符错。
BRO测试利用条件C的条件约束来设计测试用例。包含n个简单条件的条件C的条件约束定义为(D1,D2,…,Dn),其中Di(0,=和<指定表达式的输出约束。
3. 循环测试
循环是绝大多数软件算法的基础,但是,在测试软件时却往往未对循环结构进行足够的测试。循环测试是一种白盒测试技术,它专注于测试循环结构的有效性。在结构化的程序中通常只有3种循环,即简单循环、串接循环和嵌套循环,如图7.7所示。下面分别讨论这3种循环的测试方法。
(1) 简单循环。应该使用下列测试集来测试简单循环,其中n是允许通过循环的最大次数。跳过循环。只通过循环一次。
通过循环两次。通过循环m次,其中m
(3) 串接循环。如果串接循环的各个循环都彼此独立,则可以使用前述的测试简单循环的方法来测
试串接循环。但是,如果两个循环串接,而且第一个循环的循环计数器值是第二个循环的初始值,则这两个循环并不是独立的。当循环不独立时,建议使用测试嵌套循环的方法来测试串接循环。
黑盒测试着重测试软件功能。黑盒测试并不能取代白盒测试,它是与白盒测试互补的测试方法,它很可能发现白盒测试不易发现的其他类型的错误。黑盒测试力图发现下述类型的错误: ①功能不正确或遗漏了功能; ②界面错误; ③数据结构错误或外部数据库访问错误; ④性能错误; ⑤初始化和终止错误。白盒测试在测试过程的早期阶段进行,而黑盒测试主要用于测试过程的后期。设计黑盒测试方案时,应该考虑下述问题:
(1) 怎样测试功能的有效性?
(2) 哪些类型的输入可构成好测试用例?
(3) 系统是否对特定的输入值特别敏感?
(4) 怎样划定数据类的边界?
(5) 系统能够承受什么样的数据率和数据量?
(6) 数据的特定组合将对系统运行产生什么影响?
应用黑盒测试技术,能够设计出满足下述标准的测试用例集:
(1) 所设计出的测试用例能够减少为达到合理测试所需要设计的测试用例的总数;
(2) 所设计出的测试用例能够告诉我们,是否存在某些类型的错误,而不是仅仅指出与特定测试相关的错误是否存在。
等价划分是一种黑盒测试技术,这种技术把程序的输入域划分成若干个数据类,据此导出测试用例。一个理想的测试用例能独自发现一类错误。穷尽的黑盒测试通常是不现实的,只能选取少量最有代表性的输入数据作为测试数据,以期用较小的代价暴露出较多的程序错误。等价划分法力图设计出能发现若干类程序错误的测试用例,从而减少必须设计的测试用例的数目。
如果把所有可能的输入数据(有效的和无效的)划分成若干个等价类,则可以假定:每类中的一个典型值在测试中的作用与这一类中所有其他值的作用相同。因此,可以从每个等价类中只取一组数据作为测试数据。这样选取的测试数据最有代表性,最可能发现程序中的错误。使用等价划分法设计测试方案首先需要划分输入数据的等价类,为此需要研究程序的功能说明,从而确定输入数据的有效等价类和无效等价类。在确定输入数据的等价类时常常还需要分析输出数据的等价类,以便根据输出数据的等价类导出对应的输入数据等价类。划分等价类需要经验,下述几条启发式规则有助于等价类的划分:
(1) 如果规定了输入值的范围,则可划分出一个有效的等价类(输入值在此范围内),两个无效的等价类(输入值小于最小值或大于最大值);
(2) 如果规定了输入数据的个数,则可以划分出一个有效的等价类和两个无效的等价类;
(3) 如果规定了输入数据的一组值,而且程序对不同输入值做不同处理,则每个允许的输入值是一个有效的等价类,此外还有一个无效的等价类(任一个不允许的输入值);
(4) 如果规定了输入数据必须遵循的规则,则可以划分出一个有效的等价类(符合规则)和若干个无效的等价类(从各种不同角度违反规则);
(5) 如果规定了输入数据为整型,则可以划分出正整数、零和负整数等3个有效类;
(6) 如果程序的处理对象是表格,则应该使用空表,以及含一项或多项的表。为了正确划分等价类,一是要注意积累经验,二是要正确分析被测程序的功能。一般说来,不需要设计测试数据用来暴露编译程序肯定能发现的错误。划分出等价类以后,根据等价类设计测试方案时主要使用下面两个步骤:
(1) 设计一个新的测试方案以尽可能多地覆盖尚未被覆盖的有效等价类,重复这一步骤直到所有有效等价类都被覆盖为止;
(2) 设计一个新的测试方案,使它覆盖一个而且只覆盖一个尚未被覆盖的无效等价类,重复这一步骤直到所有无效等价类都被覆盖为止。注意,通常程序发现一类错误后就不再检查是否还有其他错误,因此,应该使每个测试方案只覆盖一个无效的等价类。
下面用等价划分法设计一个简单程序的测试方案。假设有一个把数字串转变成整数的函数。运行程序的计算机字长16位,用二进制补码表示整数。这个函数是用Pascal语言编写的,它的说明如下:function strtoint (dstr:shortstr):integer; 函数的参数类型是shortstr,它的说明是:type shortstr=array[1…6] of char;
被处理的数字串是右对齐的,也就是说,如果数字串比6个字符短,则在它的左边补空格。如果数字串是负的,则负号和最高位数字紧相邻(负号在最高位数字左边一位)。考虑到Pascal编译程序固有的检错功能,测试时不需要使用长度不等于6的数组做实参,更不需要使用任何非字符数组类型的实参。分析这个程序的规格说明,可以划分出如下等价类:有效输入的等价类有
(1) 1~6个数字字符组成的数字串(最高位数字不是
零);
(2) 最高位数字是零的数字串;
(3) 最高位数字左邻是负号的数字串;无效输入的等价类有
(4) 空字符串(全是空格);
(5) 左部填充的字符既不是零也不是空格;
(6) 最高位数字右面由数字和空格混合组成;
(7) 最高位数字右面由数字和其他字符混合组成;
(8) 负号与最高位数字之间有空格;合法输出的等价类有
(9) 在计算机能表示的最小负整数和零之间的负整数;
(10) 零;
(11) 在零和计算机能表示的最大正整数之间的正整数;
非法输出的等价类有
(12) 比计算机能表示的最小负整数还小的负整数;
(13) 比计算机能表示的最大正整数还大的正整数。
因为所用的计算机字长16位,用二进制补码表示整数,所以能表示的最小负整数是-32 768,能表示的最大正整数是32 767。
经验表明,处理边界情况时程序最容易发生错误。例如,许多程序错误出现在下标、数据结构和循环等等的边界附近。因此,设计使程序运行在边界情况附近的测试方案,暴露出程序错误的可能性更大一些。
使用边界值分析方法设计测试方案首先应该确定边界情况,通常输入等价类和输出等价类的边界,就是应该着重测试的程序边界情况。选取的测试数据应该等于、刚刚小于和刚刚大于边界值。通常设计测试方案时总是联合使用等价划分和边界值分析两种技术。
有时分别使用每组测试数据时程序都能正常工作,这些输入数据的组合却可能检测出程序的错误。因此必须依靠测试人员的经验和直觉,从各种可能的测试方案中选出一些最可能引起程序出错的方案。对于程序中可能存在哪类错误的推测,是挑选测试方案时的一个重要因素。
错误推测法在很大程度上靠直觉和经验进行。它的基本想法是列举出程序中可能有的错误和容易发生错误的特殊情况,并且根据它们选择测试方案。应该分析程序规格说明书,找出其中遗漏或省略的部分,以便设计相应的测试方案,检测程序员对这些部分的处理是否正确。选择输入组合的另一个有效途径是把计算机测试和人工检查代码结合起来。例如,通过代码检查发现程序中两个模块使用并修改某些共享的变量,如果一个模块对这些变量的修改不正确,则会引起另一个模块出错,因此这是程序发生错误的一个可能的原因。应该设计测试方案,在程序的一次运行中同时检测这两个模块,特别要着重检测一个模块修改了共享变量后,另一个模块能否像预期的那样正常使用这些变量。
调试(也称为纠错)作为成功测试的后果出现,也就是说,调试是在测试发现错误之后排除错误的过程软件工程师在评估测试结果时,往往仅面对着软件错误的症状,也就是说,软件错误的外部表现和它的内在原因之间可能并没有明显的联系。调试就是把症状和原因联系起来的尚未被人深入认识的智力过程。
调试(也称为纠错)作为成功测试的后果出现,也就是说,调试是在测试发现错误之后排除错误的过程。软件工程师在评估测试结果时,往往仅面对着软件错误的症状,也就是说,软件错误的外部表现和它的内在原因之间可能并没有明显的联系。调试就是把症状和原因联系起来的尚未被人深入认识的智力过程。
调试不是测试,但是它总是发生在测试之后。调试过程从执行一个测试用例开始,评估测试结果,如果发现实际结果与预期结果不一致,则这种不一致就是一个症状,它表明在软件中存在着隐藏的问题。调试过程试图找出产生症状的原因,以便改正错误。调试过程总会有以下两种结果之一: ①找到了问题的原因并把问题改正和排除掉了; ②没找出问题的原因。在后一种情况下,调试人员可以猜想一个原因,并设计测试用例来验证这个假设,重复此过程直至找到原因并改正了错误。
软件错误的特征:
(1) 症状和产生症状的原因可能在程序中相距甚远,也就是说,症状可能出现在程序的一个部分,而实际的原因可能在与之相距很远的另一部分。
(2) 当改正了另一个错误之后,症状可能暂时消失了。
(3) 症状可能实际上并不是由错误引起的(例如,舍入误差)。
(4) 症状可能是由不易跟踪的人为错误引起的。
(5) 症状可能是由定时问题而不是由处理问题引起的。
(6) 可能很难重新产生完全一样的输入条件。
(7) 症状可能时有时无。
(8) 症状可能是由分布在许多任务中的原因引起的,这些任务运行在不同的处理机上。
无论采用什么方法,调试的目标都是寻找软件错误的原因并改正错误。通常需要把系统地分析、直觉和运气组合起来,才能实现上述目标。一般说来,有下列3种调试途径可以采用:
软件可靠性的定义
对于软件可靠性有许多不同的定义,其中多数人承认的一个定义是:软件可靠性是程序在给定的时间间隔内,按照规格说明书的规定成功地运行的概率。在上述定义中包含的随机变量是时间间隔。显然,随着运行时间的增加,运行时出现程序故障的概率也将增加,即可靠性随着给定的时间间隔的加大而减少。
按照IEEE的规定,术语“错误”的含义是由开发人员造成的软件差错(bug),而术语“故障”的含义是由错误引起的软件的不正确行为。在下面的论述中,将按照IEEE规定的含义使用这两个术语。
软件的可用性
通常用户也很关注软件系统可以使用的程度。一般说来,对于任何其故障是可以修复的系统,都应该同时使用可靠性和可用性衡量它的优劣程度。软件可用性的一个定义是: 软件可用性是程序在给定的时间点,按照规格说明书的规定,成功地运行的概率。
可靠性和可用性之间的主要差别是,可靠性意味着在0到t这段时间间隔内系统没有失效,而可用性只意味着在时刻t,系统是正常运行的。如果在一段时间内,软件系统故障停机时间分别为td1,td2,…,正常运行时间分别为tu1,tu2,…,则系统的稳态可用性为:
软件的平均无故障时间MTTF是一个重要的质量指标,往往作为对软件的一项要求,由用户提出来。为了估算MTTF,首先引入一些有关的量。
实现包括编码和测试两个阶段。
按照传统的软件工程方法学,编码是在对软件进行了总体设计和详细设计之后进行的,程序的质量基本上取决于设计的质量。但是,编码使用的语言,特别是写程序的风格,也对程序质量有相当大的影响。程序内部的良好文档资料,有规律的数据说明格式,简单清晰的语句构造和输入输出格式等等,都对提高程序的可读性有很大作用,也在相当大的程度上改进了程序的可维护性。软件测试是保证软件可靠性的主要手段。测试阶段的根本任务是发现并改正软件中的错误。软件测试是软件开发过程中最艰巨最繁重的任务,软件测试至少分为单元测试、集成测试和验收测试3个基本阶段。设计测试方案是测试阶段的关键技术问题,基本目标是选用最少量的高效测试数据,做到尽可能完善的测试,从而尽可能多地发现软件中的问题。软件测试不仅仅指利用计算机进行的测试,还包括人工进行的测试如代码审查。两种测试途径各有优缺点,互相补充,缺一不可。
白盒测试和黑盒测试是软件测试的两类基本方法,这两类方法各有所长,相互补充。通常,在测试过
程的早期阶段主要使用白盒方法,而在测试过程的后期阶段主要使用黑盒方法。
设计白盒测试方案的技术主要有,逻辑覆盖和控制结构测试;
设计黑盒测试方案的技术主要有,等价划分、边界值分析和错误推测。
在测试过程中发现的软件错误必须及时改正,这就是调试的任务。
测试和调试是软件测试阶段中的两个关系非常密切的过程,它们往往交替进行。
程序中潜藏的错误的数目,直接决定了软件的可靠性。根据测试和调试过程中已经发现和改正的错误数,可以估算软件的平均无故障时间;反之,根据要求达到的软件平均无故障时间,可以估算出应该改正的错误数,从而能够判断测试阶段何时可以结束。
主要内容
软件维护
教材中的页码
教材第189页----第200页(8.6简单阅读即可)
课件中的页码
PPT第8章第1页—第36页
名词术语
软件维护
改正性维护
适应性维护
完善性维护
预防性维护
重点
软件维护过程
软件的可维护性
难点
维护过程中保证软件的可维护性以及文档的一致性
思考题
如何正确看待软件维护的重要意义
作业题
教材201页第八章习题8的第一题
在软件产品被开发出来并交付用户使用之后,就进入了软件的运行维护阶段。这个阶段是软件生命周期的最后一个阶段,其基本任务是保证软件在一个相当长的时期能够正常运行。软件维护需要的工作量很大,平均说来,大型软件的维护成本高达开发成本的4倍左右。软件工程的目的是要提高软件的可维护性,减少软件维护所需要的工作量,降低软件系统的总成本。在软件开发的过程中,维护费用占总费用最高。
所谓软件维护就是在软件已经交付使用之后,为了改正错误或满足新的需要而修改软件的过程。可以通过描述软件交付使用后可能进行的4项活动,具体地定义软件维护。
改正性维护:在程序的使用期间,用户发现程序错误,并且把问题报告给维护人员。把诊断和改正错误的过程称为改正性维护。
适应性维护:为了和变化了的环境适当地配合而进行的修改软件的活动,是既必要又经常的维护活动。
完善性维护:在使用软件的过程中用户往往提出增加新功能或修改已有功能的建议,还可能提出一般
性的改进意见。为了满足这类要求,需要进行完善性维护。这项维护活动通常占软件维护工作的大部分。
预防性维护:当为了改进未来的可维护性或可靠性,或为了给未来的改进奠定更好的基础而修改软件时,这项维护活动通常称为预防性维护,目前这项维护活动相对比较少。
结构化维护与非结构化维护差别巨大
结构化维护:
维护工作从评价设计文档开始,确定软件重要的结构特点、性能特点以及接口特点;估量要求的改动将带来的影响,并且计划实施途径。然后修改设计并且对所做的修改进行仔细复查。接下来编写相应的源程序代码;使用在测试说明书中包含的信息进行回归测试;最后,把修改后的软件交付使用。
维护的代价高昂
在过去的几十年中,软件维护的费用稳步上升。1970年用于维护已有软件的费用只占软件总预算的35%~40%,1990年上升为70%~80%。软件维护的无形的代价:可用的资源必须供维护任务使用,以致耽误了开发的良机;当看来合理的有关改错或修改的要求不能及时满足时将引起用户不满;
由于维护时的改动,在软件中引入了潜伏的错误,从而降低了软件的质量;必须把软件工程师调去从事维护工作时,将在开发过程中造成混乱。软件维护的最后一个代价是生产率的大幅度下降,这种情况在维护旧程序时常常遇到。
维护的问题
(1) 理解别人写的程序通常非常困难,而且困难程度随着软件配置成分的减少而迅速增加。
(2) 需要维护的软件往往没有合格的文档,或者文档资料显著不足。
(3) 当需要对软件进行维护时,往往原来写程序的人已经不在该项目组中了。
(4) 绝大多数软件在设计时没有充分考虑将来的修改。
(5) 软件维护不是一项吸引人的工作。形成这种观念很大程度上是因为维护工作经常遭受挫折。软件工程至少部分地解决了与维护有关的每一个问题。
维护过程本质上是修改和压缩了的软件定义和开发过程。首先必须建立一个维护组织,随后必须确定报告和评价的过程,而且必须为每个维护要求规定一个标准化的事件序列。此外,还应该建立一个适用于维
护活动的记录保管过程,并且规定复审标准。
维护组织
通常并不需要建立正式的维护组织。每个维护要求都通过维护管理员转交给相应的系统管理员去评价。系统管理员对维护任务做出评价之后,由变化授权人决定应该进行的活动。图8.1描绘了上述组织方式。在维护活动开始之前就明确维护责任是十分必要的,这样做可以大大减少维护过程中可能出现的混乱。
维护报告
应该用标准化的格式表达所有软件维护要求,称为软件问题报告表,这个表格由用户填写。如果遇到了一个错误,那么必须完整描述导致出现错误的环境。对于适应性或完善性的维护要求,应该提出一个简短的需求说明书。由维护管理员和系统管理员评价用户提交的维护要求表。
维护要求表是计划维护活动的基础。软件组织内部应该制定出一个软件修改报告,它给出下述信息:
(1) 满足维护要求表中提出的要求所需要的工作量;
(2) 维护要求的性质;
(3) 要求的优先次序;
(4) 与修改有关的事后数据。
在拟定进一步的维护计划之前,把软件修改报告提交给变化授权人审查批准。
维护的事件流
不管维护类型如何,都需要进行同样的技术工作。这些工作包括修改软件设计、复查、必要的代码修改、单元测试和集成测试(包括使用以前的测试方案的回归测试)、验收测试和复审。不同类型的维护强调的重点不同,但是基本途径是相同的。维护事件流中最后一个事件是复审,它再次检验软件配置的所有成分的有效性,并且保证事实上满足了维护要求表中的要求。
保存维护记录
保存维护记录需要保存下述内容:
①程序标识; ②源语句数; ③机器指令条数; ④使用的程序设计语言; ⑤程序安装的日期; ⑥自从安装以来程序运行的次数; ⑦自从安装以来程序失效的次数; ⑧程序变动的层次和标识; ⑨因程序变动而增加的源语句数; 因程序变动而删除的源语句数; 每个改动耗费的人时数; 程序改动的日期; 软件工程师的名字; 维护要求表的标识;维护类型; 维护开始和完成的日期; 累计用于维护的人时数; 与完成的维护相联系的纯效益。应该为每项维护工作都收集上述数据。可以利用这些数据构成一个维护数据库的基础,并且对它们进行评价。
评价维护活动
(1) 每次程序运行平均失效的次数;
(2) 用于每一类维护活动的总人时数;
(3) 平均每个程序、每种语言、每种维护类型所做的程序变动数;
(4) 维护过程中增加或删除一个源语句平均花费的人时数;
(5) 维护每种语言平均花费的人时数;
(6) 一张维护要求表的平均周转时间;
(7) 不同维护类型所占的百分比。
把软件的可维护性定义为: 维护人员理解、改正、改动或改进这个软件的难易程度。提高可维护性是支配软件工程方法学所有步骤的关键目标。
在维护的修改之后应该进行必要的测试,以保证所做的修改是正确的。如果是改正性维护,还必须预先进行调试以确定错误的具体位置。因此,决定软件可维护性的因素主要有下述5个:
文档是影响软件可维护性的决定因素。软件在使用过程中必然会经受多次修改,所以文档比程序代码更重要。软件系统的文档可以分为用户文档和系统文档两类。用户文档主要描述系统功能和使用方法,并不关心这些功能是怎样实现的;系统文档描述系统设计、实现和测试等各方面的内容。总的说来,软件文档应该满足下述要求:
(1) 必须描述如何使用这个系统;
(2) 必须描述怎样安装和管理这个系统;
(3) 必须描述系统需求和设计;
(4) 必须描述系统的实现和测试,以便使系统成为可维护的。
可维护性是所有软件都应该具备的基本特点,必须在开发阶段保证软件具有可维护性。在软件工程过程的每一个阶段都应该考虑并努力提高软件的可维护性,在每个阶段结束前的技术审查和管理复审中,应该着重对可维护性进行复审。在需求分析阶段的复审过程中,应该对将来要改进的部分和可能会修改的部分加以注意并指明;应该讨论软件的可移植性问题,并且考虑可能影响软件维护的系统界面。
在正式的和非正式的设计复审期间,应该从容易修改、模块化和功能独立的目标出发,评价软件的结构和过程;设计中应该对将来可能修改的部分预作准备。
代码复审应该强调编码风格和内部说明文档这两个影响可维护性的因素。在设计和编码过程中应该尽量使用可重用的软件构件。
在测试结束时进行最正式的可维护性复审,称为配置复审。配置复审的目的是保证软件配置的所有成分是完整的、一致的和可理解的,而且为了便于修改和管理已经编目归档了。在完成了每项维护工作之后,都应该对软件维护本身进行仔细认真的复审。维护应该针对整个软件配置,不应该只修改源程序代码。当对源程序代码的修改没有反映在设计文档或用户手册中时,就会产生严重的后果。每当对数据、软件结构、模块过程或任何其他有关的软件特点做了改动时,必须立即修改相应的技术文档。不能准确反映软件当前状态的设计文档可能比完全没有文档更坏。在以后的维护工作中很可能因文档不完全符合实际而不能正确理解软件,从而在维护中引入过多的错误。
当初开发这些老程序时并没有使用软件工程方法学来指导,文档不全甚至完全没有文档,对曾经做过的修改也没有完整的记录。
怎样满足用户对上述这类老程序的维护要求呢?为了修改这类程序以适应用户新的或变更的需求,有以下几种做法可供选择:
(1) 反复多次地做修改程序的尝试,以实现所要求的修改;
(2) 通过分析程序尽可能多地掌握程序的内部工作细节,以便更有效地修改它;
(3) 在深入理解原有设计的基础上,用软件工程方法重新设计、重新编码和测试那些需要变更的软件部分;
(4) 以软件工程方法学为指导,对程序全部重新设计、重新编码和测试,为此可以使用CASE工具来帮助理解原有的设计。
通常人们采用后3种做法。其中第4种做法称为软件再工程。预防性维护方法是由Miller提出来的,他把这种方法定义为:“把今天的方法学应用到昨天的系统上,以支持明天的需求。”
维护是软件生命周期的最后一个阶段,也是持续时间最长代价最大的一个阶段。软件工程学的主要目的就是提高软件的可维护性,降低维护的代价。软件维护通常包括4类活动:改正性维护、适应性维护、完善性维护以及预防性维护。
软件的可理解性、可测试性、可修改性、可移植性和可重用性,是决定软件可维护性的基本因素,软件重用技术是能从根本上提高软件可维护性的重要技术。
软件生命周期每个阶段的工作都和软件可维护性有密切关系。良好的设计,完整准确易读易理解的文档资料,以及一系列严格的复审和测试,使得一旦发现错误时比较容易诊断和纠正,当用户有新要求或外部环境变化时软件能较容易地适应,并且能够减少维护引入的错误。因此,在软件生命周期的每个阶段都必须充分考虑维护问题,并且为软件维护做准备。
文档是影响软件可维护性的决定因素,因此,文档甚至比可执行的程序代码更重要。文档可分为用户文档和系统文档两大类。不管是哪一类文档都必须和程序代码同时维护,只有和程序代码完全一致的文档才是真正有价值的文档。
目前预防性维护在全部维护活动中仅占很小比例,但是不应该忽视这类维护活动,在条件具备时应该主动地进行预防性维护。
预防性维护实质上是软件再工程。典型的软件再工程过程模型定义了库存目录分析、文档重构、逆向工程、代码重构、数据重构和正向工程等6类活动。上述模型是一个循环模型,这意味着每项活动都可能被重复,而且对于任意一个特定的循环来说,再工程过程可以在完成任意一个活动之后终止。
主要内容
软件项目管理
教材中的页码
教材第305页----第331页(13.7简单阅读即可)
课件中的页码
PPT第13章第1页—第117页
名词术语
软件项目管理
代码行/千代码行(LOC/KLOC)
功能点技术
人月(PM)
甘特图
软件质量
走查
审查
软件配置管理
能力成熟度模型(CMM)
重点
使用甘特图来制定开发进度计划
使用工程网络图来制定开发进度计划
影响软件质量的十三个因素,理解这些因素的含义
掌握软件配置管理的作用
了解软件配置管理的过程
难点
如何有效正确地估算出软件规模
如何准确地估算出总的工作量
估算工程进度,识别工程的关键路径
思考题
体会制定软件开发进度计划的重要性。
为什么需要软件配置管理?
软件配置管理需要管理哪些活动?
作业题
教材336页第十三章习题13的第7、8、10、11题
所谓管理就是通过计划、组织和控制等一系列活动,合理地配置和使用各种资源,以达到既定目标的程。
软件项目管理先于任何技术活动之前开始,并且贯穿于软件的整个生命周期之中。软件项目管理过程从一组项目计划活动开始,而制定计划的基础是工作量估算和完成期限估算。为了估算项目的工作量和完成期限,首先需要估算软件的规模。
项目管理的三大核心要素:时间,成本,质量
代码行技术是比较简单的定量估算软件规模的方法。这种方法依据经验和历史数据,估计实现一个功能所需要的源程序行数。当有以往开发类似产品的历史数据可供参考时,用这种方法估计出的数值还是比较准确的。把实现每个功能所需要的源程序行数累加起来,就可得到实现整个软件所需要的源程序行数。
可由多名有经验的软件工程师分别做出估计。每个人都估计程序的最小规模(a)、最大规模(b)和最可能的规模(m),分别算出这3种规模的平均值,和之后,再用下式计算程序规模的估计值:
代码行技术的主要优点是:代码是所有软件开发项目都有的“产品”,而且很容易计算代码行数。代码行技术的缺点是: 源程序仅是软件配置的一个成分,用它的规模代表整个软件的规模似乎不太合理;用不同语言实现同一个软件所需要的代码行数并不相同;这种方法不适用于非过程语言。为了克服代码行技术的缺点,人们又提出了功能点技术。
功能点技术依据对软件信息域特性和软件复杂性的评估结果,估算软件规模。这种方法用功能点(FP)为单位度量软件规模。
软件估算模型使用由经验导出的公式来预测软件开发工作量,工作量是软件规模(KLOC或FP)的函数,工作量的单位通常是人月(pm)。支持大多数估算模型的经验数据,都是从有限个项目的样本集中总结出来的,因此,没有一个估算模型可以适用于所有类型的软件和开发环境。
从上面列出的模型可以看出,对于相同的KLOC或FP值,用不同模型估算将得出不同的结果。主要原因是,这些模型多数都是仅根据若干应用领域中有限个项目的经验数据推导出来的,适用范围有限。因此,必须根据当前项目的特点选择适用的估算模型,并且根据需要适当地调整估算模型。
COCOMO是构造性成本模型(constructive cost model)的英文缩写。COCOMO2给出了3个层次的软件开发工作量估算模型,这3个层次的模型在估算工作量时,对软件细节考虑的详尽程度逐级增加。这些模型既可以用于不同类型的项目,也可以用于同一个项目的不同开发阶段。这3个层次的估算模型分别是:
(1) 应用系统组成模型。这个模型主要用于估算构建原型的工作量,模型名字暗示在构建原型时大量使用已有的构件。
(2) 早期设计模型。这个模型适用于体系结构设计阶段。
(3) 后体系结构模型。这个模型适用于完成体系结构设计之后的软件开发阶段。
COCOMO2使用的5个分级因素如下所述:
(1) 项目先例性。这个分级因素指出,对于开发组织来说该项目的新奇程度。诸如开发类似系统的经验,需要创新体系结构和算法,以及需要并行开发硬件和软件等因素的影响,都体现在这个分级因素中。
(2) 开发灵活性。这个分级因素反映出,为了实现预先确定的外部接口需求及为了及早开发出产品而需要增加的工作量。
(3) 风险排除度。这个分级因素反映了重大风险已被消除的比例。在多数情况下,这个比例和指定了重要模块接口(即选定了体系结构)的比例密切相关。
(4) 项目组凝聚力。这个分级因素表明了开发人员相互协作时可能存在的困难。这个因素反映了开发人员在目标和文化背景等方面相一致的程度,以及开发人员组成一个小组工作的经验。
(5) 过程成熟度。这个分级因素反映了按照能力成熟度模型度量出的项目组织的过程成熟度。在原始的COCOMO模型中,仅粗略地考虑了前两个分级因素对指数b之值的影响。工作量方程中模型系数a的典型值为3.0,在实际工作中应该根据历史经验数据确定一个适合本组织当前开发的项目类型的数值。
能力成熟度模型有初始级,可重复级,已定义级,已管理级,优化级 5 级
管理者必须制定一个足够详细的进度表,以便监督项目进度并控制整个项目。一个有效的软件过程应该定义一个适用于当前项目的任务集合。一个任务集合包括一组软件工程工作任务、里程碑和可交付的产品。为一个项目所定义的任务集合,必须包括为获得高质量的软件产品而完成的所有任务。项目管理者的目标是定义全部项目任务,识别出关键任务,跟踪关键任务的进展状况,以保证能及时发现拖延进度的情况。
软件项目的进度安排是这样一种活动,它通过把工作量分配给特定的软件工程任务并规定完成各项任务的起止日期,从而将估算出的项目工作量分布于计划好的项目持续期内。进度计划将随着时间的流逝而不断演化。在项目计划的早期,首先制定一个宏观的进度安排表,标识出主要的软件工程活动和这些活动影响到的产品功能。随着项目的进展,把宏观进度表中的每个条目都精化成一个详细进度表,从而标识出完成一个活动所必须实现的一组特定任务,并安排好了实现这些任务的进度
估算出完成给定项目所需的总工作量之后,接下来需要估算的就是项目开发时间。实际上软件开发时间与从事开发工作的人数之间并不是简单的反比关系。通常,成本估算模型也同时提供了估算开发时间T的方程。与工作量方程不同,各种模型估算开发时间的方程很相似,例如:
其中,
E是开发工作量(以人月为单位),
T是开发时间(以月为单位)。
用上列方程计算出的T值,代表正常情况下的开发时间。客户往往希望缩短软件开发时间,显然,为了缩短开发时间应该增加从事开发工作的人数。但是,经验告诉我们,随着开发小组规模扩大,个人生产率将下降,以致开发时间与从事开发工作的人数并不成反比关系。出现这种现象主要有下述两个
原因:
当小组变得更大时,每个人需要用更多时间与组内其他成员讨论问题、协调工作,因此增加了通信开销。
如果在开发过程中增加小组人员,则最初一段时间内项目组总生产率不仅不会提高反而会下降。这是因为新成员在开始时不仅不是生产力,而且在他们学习期间还需要花费小组其他成员的时间。
综合上述两个原因,存在被称为Brooks规律的下述现象: 向一个已经延期的项目增加人力,只会使得它更加延期。下面让我们研究项目组规模与项目组总生产率的关系。
项目组成员之间的通信路径数,由项目组人数和项目组结构决定。如果项目组共有P名组员,每个组员必须与所有其他组员通信以协调开发活动,则通信路径数为P(P-1)/2。如果每个组员只需与另外一个组员通信,则通信路径数为P-1。
Gantt(甘特)图是历史悠久、应用广泛的制定进度计划的工具,下面通过一个非常简单的例子介绍这种工具。
假设有一座陈旧的矩形木板房需要重新油漆。这工作必须分3步完成: 首先刮掉旧漆,然后刷上新漆,最后清除溅在窗户上的油漆。假设一共分配了15名工人去完成这项工作,然而工具却很有限只有5把刮旧漆用的刮板,5把刷漆用的刷子,5把清除溅在窗户上的油漆用的小刮刀。怎样安排才能使工作进行得更有效呢?
一种做法是首先刮掉四面墙壁上的旧漆,然后给每面墙壁都刷上新漆,最后清除溅在每个窗户上的油漆。显然这是效率最低的做法,因为总共有15名工人,然而每种工具却只有5件,这样安排工作在任何时候都有10名工人闲着没活干。应该采用“流水作业法”,也就是说,首先由5名工人用刮板刮掉第1面墙上的旧漆(这时其余10名工人休息),当第1面墙刮净后,另外5名工人立即用刷子给这面墙刷新漆(与此同时拿刮板的5名工人转去刮第2面墙上的旧漆),一旦刮旧漆的工人转到第3面墙而且刷新漆的工人转到第2面墙以后,余下的5名工人立即拿起刮刀去清除溅在第1面墙窗户上的油漆,……。这样安排每个工人都有活干,因此能
够在较短的时间内完成任务。假设木板房的第2、4两面墙的长度比第1、3两面墙的长度长一倍,此外,不同工作需要用的时间长短也不同,刷新漆最费时间,其次是刮旧漆,清理(即清除溅在窗户上的油漆)需要的时间最少。表13.5列出了估计每道工序需要用的时间。可以使用图13.1中的Gantt图描绘上述流水作业过程: 在时间为零时开始刮第1面墙上的旧漆,两小时后刮旧漆的工人转去刮第2面墙,同时另5名工人开始给第1面墙刷新漆,每当给一面墙刷完新漆之后,第3组的5名工人立即清除溅在这面墙窗户上的漆。从图13.1可以看出12小时后刮完所有旧漆,20小时后完成所有墙壁的刷漆工作,再过2小时后清理工作结束。因此全部工程在22小时后结束,如果用前述的第一种做法,则需要36小时。
上一小节介绍的Gantt图能很形象地描绘任务分解情况,以及每个子任务(作业)的开始时间和结束时间,因此是进度计划和进度管理的有力工具。它具有直观简明和容易掌握、容易绘制的优点,但是Gantt图也有3个主要缺点:
(1) 不能显式地描绘各项作业彼此间的依赖关系;
(2) 进度计划的关键部分不明确,难于判定哪些部分应当是主攻和主控的对象;
(3) 计划中有潜力的部分及潜力的大小不明确,往往造成潜力的浪费。
当把一个工程项目分解成许多子任务,并且它们彼此间的依赖关系又比较复杂时,仅仅用Gantt图作为安排进度的工具是不够的,不仅难于做出既节省资源又保证进度的计划,而且还容易发生差错
工程网络是制定进度计划时另一种常用的图形工具,它同样能描绘任务分解情况以及每项作业的开始时间和结束时间,此外,它还显式地描绘各个作业彼此间的依赖关系。因此,工程网络是系统分析和系统设计的强有力的工具。在工程网络中用箭头表示作业(例如,刮旧漆,刷新漆,清理等),用圆圈表示事件(一项作业开始或结束)。注意,事件仅仅是可以明确定义的时间点,
它并不消耗时间和资源。作业通常既消耗资源又需要持续一定时间。图13.2是旧木板房刷漆工程的工程网络。图中表示刮第1面墙上旧漆的作业开始于事件1,结束于事件2。用开始事件和结束事件的编号标识一个作业,因此“刮第1面墙上旧漆”是作业1—2。
在工程网络中的一个事件,如果既有箭头进入又有箭头离开,则它既是某些作业的结束又是另一些作业的开始。例如,图13.2中事件2既是作业1—2(刮第1面墙上的旧漆)的结束,又是作业2—3(刮第2面墙上旧漆)和作业2—4(给第1面墙刷新漆)的开始。也就是说,只有第1面墙上的旧漆刮完之后,才能开始刮第2面墙上旧漆和给第1面墙刷新漆这两个作业。因此,工程网络显式地表示了作业之间的依赖关系。
在图13.2中还有一些虚线箭头,它们表示虚拟作业,也就是事实上并不存在的作业。引入虚拟作业是为了显式地表示作业之间的依赖关系。例如,事件4既是给第1面墙刷新漆结束,又是给第2面墙刷新漆开始(作业4—6)。但是,在开始给第2面墙刷新漆之前,不仅必须已经给第1面墙刷完了新漆,而且第2面墙上的旧漆也必须已经刮净(事件3)。也就是说,在事件3和事件4之间有依赖关系,或者说在作业2—3(刮第2面墙上旧漆)和作业4—6(给第2面墙刷新漆)之间有依赖关系,虚拟作业3—4明确地表示了这种依赖关系。注意,虚拟作业既不消耗资源也不需要时间。
画出类似图13.2那样的工程网络之后,系统分析员就可以借助它的帮助估算工程进度了。为此需要在工程网络上增加一些必要的信息。
首先,把每个作业估计需要使用的时间写在表示该项作业的箭头上方。注意,箭头长度和它代表的作业持续时间没有关系,箭头仅表示依赖关系,它上方的数字才表示作业的持续时间。
其次,为每个事件计算下述两个统计数字: 最早时刻EET和最迟时刻LET。这两个数字将分别写在表示事件的圆圈的右上角和右下角,如图13.3左下角的符号所示。
事件的最早时刻是该事件可以发生的最早时间。通常工程网络中第一个事件的最早时刻定义为零,其他事件的最早时刻在工程网络上从左至右按事件发生顺序计算。计算最早时刻EET使用下述3条简单规则:
(1) 考虑进入该事件的所有作业;
(2) 对于每个作业都计算它的持续时间与起始事件的EET之和;
(3) 选取上述和数中的最大值作为该事件的最早时刻EET。
按照这种方法,不难沿着工程网络从左至右顺序算出每个事件的最早时刻,计算结果标在图13.3的工程网络中(每个圆圈内右上角的数字)。事件的最迟时刻是在不影响工程竣工时间的前提下,该事件最晚可以发生的时刻。按惯例,最后一个事件(工程结束)的最迟时刻就是它的最早时刻。其他事件的最迟时刻在工程网络上从右至左按逆作业流的方向计算。计算最迟时刻LET使用下述3条规则:
(1) 考虑离开该事件的所有作业;
(2) 从每个作业的结束事件的最迟时刻中减去该作业的持续时间;
(3) 选取上述差数中的最小值作为该事件的最迟时刻LET。
图13.3中每个圆圈内右下角的数字就是该事件的最迟时刻。
图13.3中有几个事件的最早时刻和最迟时刻相同,这些事件定义了关键路径,在图中关键路径用粗线箭头表示。关键路径上的事件(关键事件)必须准时发生,组成关键路径的作业(关键作业)的实际持续时间不能超过估计的持续时间,否则工程就不能准时结束。
工程项目的管理人员应该密切注视关键作业的进展情况,如果关键事件出现的时间比预计的时间晚,则会使最终完成项目的时间拖后;如果希望缩短工期,只有往关键作业中增加资源才会有效果。
不在关键路径上的作业有一定程度的机动余地——实际开始时间可以比预定时间晚一些,或者实际持续时间可以比预定的持续时间长一些,而并不影响工程的结束时间。一个作业可以有的全部机动时间等于它的结束事件的最迟时刻减去它的开始事件的
最早时刻,再减去这个作业的持续时间**:机动时间=(LET)结束-(EET)开始-持续时间**
对于前述油漆旧木板房的例子,计算得到的非关键作业的机动时间列在表13.6中。
在工程网络中每个作业的机动时间写在代表该项作业的箭头下面的括弧里(参看图13.3)。在制定进度计划时仔细考虑和利用工程网络中的机
动时间,往往能够安排出既节省资源又不影响最终竣工时间的进度表。在图13.4中的Gantt图描绘了其中的一种方案。
这个简单例子明显说明了工程网络比Gantt图优越的地方: 它显式地定义事件及作业之间的依赖关系,Gantt图只能隐含地表示这种关系。但是Gantt图的形式比工程网络更简单更直观,为更多的人所熟悉,因此,应该同时使用这两种工具制订和管理进度计划,使它们互相补充取长补短。以上通过旧木板房刷新漆工程的简单例子,介绍了制订进度计划的两个重要工具和方法。软件工程项目虽然比这个简单例子复杂得多,但是计划和管理的基本方法仍然是自顶向下分解,也就是把项目分解为若干个阶段,每个阶段再分解成许多更小的任务,每个任务又可进一步分解为若干个步骤等等。这些阶段、任务和步骤之间有复杂的依赖关系,因此,工程网络和Gantt图同样是安排进度和管理工程进展情况的强有力的工具。第13.2节中介绍的工作量估计技术可以帮助我们估计每项任务的工作量,根据人力分配情况,可以进一步确定每项任务的持续时间。从这些基本数据出发,根据作业之间的依赖关系,利用工程网络和Gantt图可以制定出合理的进度计划,并且能够科学地管理软件开发工程的进展情况
软件项目成功的关键是有高素质的软件开发人员。必须把多名软件开发人员合理地组织起来,使他们有效地分工协作共同完成开发工作。
为了成功地完成软件开发工作,项目组成员必须以一种有意义且有效的方式彼此交互和通信。如何组织项目组是一个重要的管理问题,管理者应该合理地组织项目组,使项目组有较高生产率,能够按预定的进度计划完成所承担的工作。经验表明,项目组组织得越好,其生产率越高,而且产品质量也越好。
除了追求更好的组织方式之外,每个管理者的目标都是建立有凝聚力的项目组。一个有高度凝聚力的小组,由一批团结得非常紧密的人组成,他们的整体力量大于个体力量的总和。一旦项目组具有了凝聚力,成功的可能性就大大增加了。现有的软件项目组的组织方式很多,通常,组织软件开发人员的方法,取决于所承担的项目的特点、以往的组织经验以及管理者的看法和喜好。下面介绍3种典型的组织方式。
民主制程序员组的一个重要特点是,小组成员完全平等,享有充分民主,通过协商做出技术决策。因此,小组成员之间的通信是平行的,如果小组内有n个成员,则可能的通信信道共有n(n-1)/2条。程序设计小组的人数不能太多,否则组员间彼此通信的时间将多于程序设计时间。此外,通常不能把一个软件系统划分成大量独立的单元,因此,如果程序设计小组人数太多,则每个组员所负责开发的程序单元与系统其他部分的界面将是复杂的,不仅出现接口错误的可能性增加,而且软件测试将既困难又费时间。
一般说来,程序设计小组的规模应该比较小,以2~8名成员为宜。如果项目规模很大,用一个小组不能在预定时间内完成开发任务,则应该使用多个程序设计小组,每个小组承担工程项目的一部分任务,在一定程度上独立自主地完成各自的任务。系统的总体设计应该能够保证由各个小组负责开发的各部分之间的接口是良好定义的,并且是尽可能简单的。小组规模小,不仅可以减少通信问题,而且还有其他好处。例如,容易确定小组的质量标准,而且用民主方式确定的标准更容易被大家遵守;组员间关系密切,能够互相学习等等。
民主制程序员组通常采用非正式的组织方式,也就是说,虽然名义上有一个组长,但是他和组内其他成员完成同样的任务。在这样的小组中,由全体讨论协商决定应该完成的工作,并且根据每个人的能力和经验分配适当的任务。
民主制程序员组的主要优点是,组员们对发现程序错误持积极的态度,这种态度有助于更快速地发现错误,从而导致高质量的代码。民主制程序员组的另一个优点是,组员们享有充分民主,小组有高度凝聚力,组内学术空气浓厚,有利于攻克技术难关。因此,当有难题需要解决时,也就是说,当所要开发的软件的技术难度较高时,采用民主制程序员组是适宜的。如果组内多数成员是经验丰富技术熟练的程序员,那么上述非正式的组织方式可能会非常成功。在这样的小组内组员享有充分民主,通过协商,在自愿的基础上作出决定,因此能够增强团结、提高工作效率。但是,如果组内多数成员技术水平不高,或是缺乏经验的新手,那么这种非正式的组织方式也有严重缺点: 由于没有明确的权威指导开发工程的进行,组员间将缺乏必要的协调,最终可能导致工程失败。
为了使少数经验丰富、技术高超的程序员在软件开发过程中能够发挥更大作用,程序设计小组也可以采用下一小节中介绍的另外一种组织形式。
美国IBM公司在20世纪70年代初期开始采用主程序员组的组织方式。采用这种组织方式主要出于下述几点考虑:
(1) 软件开发人员多数比较缺乏经验;
(2) 程序设计过程中有许多事务性的工作,例如,大量信息的存储和更新;
(3) 多渠道通信很费时间,将降低程序员的生产率。
主程序员组用经验多、技术好、能力强的程序员作为主程序员,同时,利用人和计算机在事务性工作方面给主程序员提供充分支持,而且所有通信都通过一两个人进行。这种组织方式类似于外科手术小组的组织。
主程序员组核心人员的分工如下所述:
(1) 主程序员既是成功的管理人员又是经验丰富、技术好、能力强的高级程序员,负责体系结构设计和关键部分(或复杂部分)的详细设计,并且负责指导其他程序员完成详细设计和编码工作。如图13.5所示,程序员之间没有通信渠道,所有接口问题都由主程序员处理。主程序员对每行代码的质量负责,因此,他还要对组内其他成员的工作成果进行复查。
(2) 后备程序员也应该技术熟练而且富于经验,他协助主程序员工作并且在必要时(例如,主程序员生病、出差或“跳槽”)接替主程序员的工作。因此,后备程序员必须在各方面都和主程序员一样优秀,并且对本项目的了解也应该和主程序员一样深入。平时,后备程序员的工作主要是,设计测试方案、分析测试结果及独立于设计过程的其他工作。
(3) 编程秘书负责完成与项目有关的全部事务性工作,例如,维护项目资料库和项目文档,编译、链接、执行源程序和测试用例。注意,上面介绍的是20世纪70年代初期的主程序员组组织结构,现在的情况已经和当时大不相同了,程序员已经有了自己的终端或工作站,他们自己完成代码的输入、编辑、编译、链接和测试等工作,无须由编程秘书统一做这些工作。典型的主程序员组的现代形式将在下一小节介绍。
虽然图13.5所示的主程序员组的组织方式说起来有不少优点,但是,它在许多方面却是不切实际的。首先,如前所述,主程序员应该是高级程序员和优秀管理者的结合体。承担主程序员工作需要同时具这两方面的才能,但是,在现实社会中这样的人才并不多见。通常,既缺乏成功的管理者也缺乏技术熟练的程序员。其次,后备程序员更难找。人们期望后备程序员像主程序员一样优秀,但是,他们必须坐在“替补席”
上,拿着较低的工资等待随时接替主程序员的工作。几乎没有一个高级程序员或高级管理人员愿意接受这样的工作。第三,编程秘书也很难找到。专业的软件技术人员一般都厌烦日常的事务性工作,但是,人们却期望编程秘书整天只干这类工作。我们需要一种更合理、更现实的组织程序员组的方法,这种方法应该能充分结合民主制程序员组和主程序员组的优点,并且能用于实现更大规模的软件产品。
民主制程序员组的一个主要优点,是小组成员都对发现程序错误持积极、主动的态度。但是,使用主程序员组的组织方式时,主程序员对每行代码的质量负责,因此,他必须参与所有代码审查工作。由于主程序员同时又是负责对小组成员进行评价的管理员,他参与代码审查工作就会把所发现的程序错误与小组成员的工作业绩联系起来,从而造成小组成员出现不愿意发现错误的心理。
解决上述问题的方法是,取消主程序员的大部分行政管理工作。前面已经指出,很难找到既是高度熟练的程序员又是成功的管理员的人,取消主程序员的行政管理工作,不仅解决了小组成员不愿意发现程序错误的心理问题,也使得寻找主程序员的人选不再那么困难。于是,实际的“主程序员”应该由两个人共同担任: 一个技术负责人,负责小组的技术活动;一个行政负责人,负责所有非技术性事务的管理决策。这样的组织结构如图13.6所示。技术组长自然要参与全部代码审查工作,因为他要对代码的各方面质量负责;相反,行政组长不可以参与代码审查工作,因为他的职责是对程序员的业绩进行评价。行政组长应该在常规调度会议上了解每名组员的技术能力和工作业绩。
在开始工作之前明确划分技术组长和行政组长的管理权限是很重要的。但是,即使已经做了明确分工,有时也会出现职责不清的矛盾。例如,考虑年度休假问题,行政组长有权批准某个程序员休年假的申请,因为这是一个非技术性问题,但是技术组长可能马上否决了这个申请,因为已经接近预定的项目结束日期,目前人手非常紧张。解决这类问题的办法是求助于更高层的管理人员,对行政组长和技术组长都认为是属于自己职责范围内的事务,制定一个处理方案。由于程序员组成员人数不宜过多,当软件项目规模较大时,应该把程序员分成若干个小组,采用图13.7所示的组织结构。该图描绘的是技术管理组织结构,非技术管理组织结构与此类似。由图可以看出,产品开发作为一个整体是在项目经理的指导下进行的,程序员向他们的组长汇报工作,而组长则向项目经理汇报工作。当产品规模更大时,可以适当增加中间管理层次。
把民主制程序员组和主程序员组的优点结合起来的另一种方法,是在合适的地方采用分散做决定的方法,如图13.8所示。这样做有利于形成畅通的通信渠道,以便充分发挥每个程序员的积极性和主动性,集思广益攻克技术难关。这种组织方式对于适合采用民主方法的那类问题(例如,研究性项目或遇到技术难题需要用集体智慧攻关)非常有效。尽管这种组织方式适当地发扬了民主,但是上下级之间的箭头(即管理关系)仍然是向下的,也就是说,是在集中指导下发扬民主。显然,如果程序员可以指挥项目经理,则只会引起混乱。
概括地说,软件质量就是“软件与明确地和隐含地定义的需求相一致的程度”。更具体地说,软件质量是软件与明确地叙述的功能和性能需求、文档中明确描述的开发标准以及任何专业开发的软件产品都应该具有的隐含特征相一致的程度。上述定义强调了下述的3个要点:
(1) 软件需求是度量软件质量的基础,与需求不一致就是质量不高。
(2) 指定的开发标准定义了一组指导软件开发的准则,如果没有遵守这些准则,几乎肯定会导致软件质量不高。
(3) 通常,有一组没有显式描述的隐含需求(例如,软件应该是容易维护的)。如果软件满足明确描述的需求,但却不满足隐含的需求,那么软件的质量仍然是值得怀疑的。虽然软件质量是难于定量度量的软件属性,但是仍然能够提出许多重要的软件质量指标(其中绝大多数目前还处于定性度量阶段)。影响软件质量的主要因素是从管理角度对软件质量的度量。可以把这些质量因素分成3组,分别反映用户在使用软件产品时的3种不同倾向或观点。这3种倾向是: 产品运行、产品修改和产品转移。图13.9描绘了软件质量因素和上述3种倾向(或产品活动)之间的关系,表13.7(见书315页)列出了软件质量因素的简明定义
软件质量保证(software quality assurance,SQA)的措施主要有: 基于非执行的测试(也称为复审或评审),基于执行的测试(即以前讲过的软件测试)和程序正确性证明。复审主要用来保证在编码之前各阶段产生的文档的质量;基于执行的测试需要在程序编写出来之后进行,它是保证软件质量的最后一道防线;程序正确性证明使用数学方法严格验证程序是否与对它的说明完全一致。
参加软件质量保证工作的人员,可以划分成下述两类:
软件工程师通过采用先进的技术方法和度量,进行正式的技术复审以及完成计划周密的软件测试来保证软件质量。
SQA小组的职责,是辅助软件工程师以获得高质量的软件产品。其从事的软件质量保证活动主要是:计划,监督,记录,分析和报告。简言之,SQA小组的作用是,通过确保软件过程的质量来保证软件产品的质量。
任何软件开发都是迭代过程,也就是说,在设计过程会发现需求说明书中的问题,在实现过程又会暴露出设计中的错误,……。此外,随着时间推移客户的需求也会或多或少发生变化。因此,在开发软件的过程中,变化(或称为变动)既是必要的,又是不可避免的。但是,变化也很容易失去控制,如果不能适当地控制和管理变化,势必造成混乱并产生许多严重的错误。
软件配置管理是在软件的整个生命期内管理变化的一组活动。具体地说,这组活动用来: ①标识变化; ②控制变化; ③确保适当地实现了变化; ④向需要知道这类信息的人报告变化。软件配置管理不同于软件维护。维护是在软件交付给用户使用后才发生的,而配置管理是在软件项目启动时就开始,并且一直持续到软件退役后才终止的一组跟踪和控制活动。软件配置管理的目标是,使变化更正确且更容易被适应,在必须变化时减少所需花费的工作量。
软件配置管理是软件质量保证的重要一环,它的主要任务是控制变化,同时也负责各个软件配置项和软件各种版本的标识、软件配置审计以及对软件配置发生的任何变化的报告。具体来说,软件配置管理主要有5项任务: 标识、版本控制、变化控制、配置审计和报告。
软件工程包括技术和管理两方面的内容,是技术与管理紧密结合的产物。因此,有效的管理是大型软件工程项目成功的关键。软件项目管理始于项目计划,而第一项计划活动就是估算。为了估算项目工作量和完成期限,首先需要预测软件规模。
度量软件规模的常用技术主要有代码行技术和功能点技术。这两种技术各有优缺点,应该根据项目特点及从事计划工作的人对这两种技术的熟悉程度,选用适用的技术。根据软件规模可以估算出完成该项目所需的工作量,常用的估算模型为静态单变量模型、动态多变量模型和COCOMO2模型。为了使估算结果更接近实际值,通常至少同时使用上述3种模型中的两种。通过比较和协调使用不同模型得出的估算值,有可能得到比较准确的估算结果。成本估算模型通常也同时提供了估算软件开发时间的方程式,这样估算出的开发时间是正常开发时间。管理者必须制定出一个详细的进度表,以便监督项目进度并控制整个项目。常用的制定进度计划的工具有Gantt图和工程网络,这两种工具各有优缺点,通常,联合使用Gantt图和工程网络来制定进度计划并监督项目进展状况。高素质的开发人员和合理的项目组组织结构,是软件项目取得成功的关键。比较典型的组织结构有民主制程序员组、主程序员组和现代程序员组等3种,这3种组织方式的适用场合并不相同。
软件质量保证是在软件过程中的每一步都进行的活动。软件质量保证措施主要有基于非执行的测试(也称为复审)、基于执行的测试(即通常所说的测试)和程序正确性证明。软件复审是最重要的软件质量保证活动之一,它的优点是在改正错误的成本相对比较低时就能及时发现并排除软件错误。软件配置管理是应用于整个软件过程中的保护性活动,是在软件整个生命期内管理变化的一组活动。软件配置管理的目标是,使变化能够更正确且更容易被适应,在需要修改软件时减少为此而花费的工作量