[TOC]
Part1.软件工程概述
1.软件与软件工程的概念
1.1软件的概念、特性和分类
概念
•软件是计算机系统中与硬件相互依存的另一部分,它是包括程序,数据及其相关文档的完整集合。
•程序是按事先设计的功能和性能要求执行的指令序列。
•数据是使程序能正常操纵信息的数据结构。
•文档是与程序开发,维护和使用有关的图文材料。
特性
(1) 形态特性: 软件是无形的、不可见的逻辑实体。度量常规产品的几何尺寸、物理性质和化学成分对它却是毫无意义的。
(2) 智能特性:软件是复杂的智力产品,它的开发凝聚了人们的大量脑力劳动,它本身也体现了知识实践经验和人类的智慧,具有一定的智能。它可以帮助我们解决复杂的计算、分析、判断和决策问题。
(3) 开发特性: 尽管已经有了一些工具(也是软件)来辅助软件开发工作,但到目前为止尚未实现自动化。软件开发中仍然包含了相当份量的个体劳动,使得这一大规模知识型工作充满了个人行为和个人因素。
(4) 质量特性:目前还无法得到完全没有缺陷的软件产品 。
(5) 生产特性:与硬件或传统的制造业产品的生产完全不同,软件一旦设计开发出来,如果需要提供多个用户,它的复制十分简单,其成本也极为有限 。
(6) 管理特性:由于上述的几个特点,使得软件的开发管理显得更为重要,也更为独特 。
(7) 环境特性:软件的开发和运行都离不开相关的计算机系统环境,包括支持它的开发和运行的相关硬件和软件。软件对于计算机系统的环境有着不可摆脱的依赖性。
(8) 维护特性:软件投入使用以后需要进行维护,但这种维护与传统产业产品的维护概念有着很大差别。
(9) 废弃特性: 与硬件不同,软件并不是由于被“用坏”而被废弃的 。
(10) 应用特性:软件的应用极为广泛,如今它已渗入国民经济和国防的各个领域,现已成为信息产业、先进制造业和现代服务业的核心,占据了无可取代的地位。
分类
(1) 系统软件
•操作系统•数据库管理系统•设备驱动程序•通信和网络处理程序等
(2)支撑软件(工具软件)
•纵向支撑软件:分析、设计、编码、测试工具等•横向支撑软件:项目管理工具,配置管理工具等
(3)应用软件
•工程与科学计算软件•商业数据处理软件•ERP软件•计算机辅助设计/制造软件•系统仿真软件•智能产品嵌入软件•事务管理、办公自动化软件
(4)可复用软件
•标准函数库、类库、构件库等
作用
具有产品和产品生产载体的双重作用。
(1)作为产品,软件显示了由计算机硬件体现的计算能力,扮演着信息转换的角色:产生、管理、查询、修改、显示或者传递各种不同的信息。
(2)作为产品生产的载体,软件提供了计算机控制(操作系统)、信息通信(网络),以及应用程序开发和控制的基础平台(软件工具和环境)。
软件质量
不同类型、不同应用领域、不同用户对软件质量要素的要求也有很大差别。从事办公和机要工作的软件对安全性要求比较高。实时嵌入式软件对正确性和可靠性要求比较高等等。软件开发过程必须根据软件的实际情况对软件质量要素的定位进行折衷和决策。
下面给出软件工程常用的八个质量要素的定义和解释。
(1)正确性(correctness)。
软件满足需求规约及完成用户目标的程度。
(2)可用性(usability)。
学习和使用软件的难易程度,包括:操作软件、为软件准备输入数据,解释软件输出结果等。学习和使用困难的软件可用性差,符合人们习惯、传统的软件可用性好。
(3)可靠性(reliability)。
软件完成预期功能,成功运行的概率。软件可靠性反映了软件无故障工作的状况。为了提高可靠性必须提高软件开发、测试、维护的质量,减少软件潜伏的缺陷数量。对于实时嵌入式计算机系统,软件要实时地控制一个物理过程,如果可靠性得不到保证,一旦出现问题可能是灾难性的,后果不堪设想。
(4)有效性(efficiency)。
软件系统利用计算机的时间资源和空间资源完成系统功能的能力。各种计算机软件无不将系统的时/空开销和网络环境下的信息传输开销作为衡量软件质量的一项重要技术指标。很多场合,在追求时间有效性和空间有效性方面会发生矛盾,这时不得不牺牲时间效率换取空间有效性,或牺牲空间效率换取时间有效性。时/空折衷是经常出现的。有经验的软件设计人员会巧妙地利用折衷概念,在具体的物理环境中实现用户的需求和自己的设计。
(5)可维护性(maintainability)。
软件制品交付用户使用后,能够对它进行修改,以便改正潜伏的缺陷、改进性能和其他属性,使软件制品适应环境的变化等等。由于软件是逻辑产品,只要用户需要可以无限期地使用下去,软件维护是不可避免的。软件维护成本约占整个生命周期的40%至60%。软件维护费用高的问题今天仍然没有明显改变。
(6)可移植性(portability)。
将软件安装在不同计算机系统或环境的难易程度。为了获得比较高的可移植性,在软件设计过程中通常采用通用的程序设计语言和运行支撑环境。对依赖于计算机系统的低级(物理)特征部分,如编译系统的目标代码生成,应相对独立、集中。这样与处理机无关的部分就可以移植到其他系统上使用。
(7)安全性(security)。
控制或保护程序和数据不受破坏的机制,以防止程序和数据受到意外的或蓄意的存取、使用、修改、毁坏或泄密。在网络环境下计算机犯罪、恶作剧增多,软件安全受到人们的高度重视。软件的安全性已成为用户十分关心的质量要素,影响着软件开发、测试、维护各个方面。
(8)可复用性(reusebility)。
概念或功能相对独立的一个或一组相关模块定义为一个软构件。软构件可以在多种场合应用的程度称为构件的可复用性。可复用的软构件有的可以不加修改直接使用,有的需要修改才可使用。可复用软构件应具有清晰的结构和注解,应具有正确的编码和较低的时/空开销。各种可复用软构件还可以按照某种规则存放在软构件库中,供软件工程师们选用。可复用性有助于提高软件制品的质量和开发效率、有助于降低软件的开发和维护费用。
1.2软件危机与软件工程
软件危机暴发于上个世纪六十年代末。
主要表现为:软件的发展速度远远滞后于硬件的发展速度,不能满足社会日益增长的软件需求。软件开发周期长、成本高、质量差、维护困难。
原因:
缺乏软件开发的经验和有关软件开发数据的积累,使得开发工作的计划很难制定。
软件人员与用户的交流存在障碍,使得获取的需求不充分或存在错误 。
软件开发过程不规范。如,没有真正了解用户的需求就开始编程序。
随着软件规模的增大,其复杂性往往会呈指数级升高。需要很多人分工协作,不仅涉及技术问题,更重要的是必须有科学严格的管理。
缺少有效的软件评测手段,提交用户的软件质量不能完全保证。
解决:
彻底消除“软件就是程序”的错误观念。
充分认识到软件开发应该是一种组织良好、管理严密、各类人员协同配合、共同完成的工程项目。
推广和使用在实践中总结出来的开发软件的成功技术、方法和工具。
按工程化的原则和方法组织软件开发工作。
软件工程概念的提出:
在NATO会议上,Fritz Bauer对软件工程的定义是:“软件工程就是为了经济地获得可靠的且能在实际机器上有效地运行的软件,而建立和使用完善的工程原理。”
1993年IEEE给出的定义:“软件工程是:① 把系统的、规范的、可度量的途径应用于软件开发、运行和维护过程,也就是把工程应用于软件;② 研究①中提到的途径。”。
•概括地说,软件工程是指导计算机软件开发和维护的工程学科。
采用工程的概念、原理、技术和方法来开发与维护软件,把经过时间考验而证明正确的管理技术和当前能够得到的最好的技术方法结合起来,以经济地开发出高质量的软件并有效地维护它,这就是软件工程。
1.3软件工程的目标
软件工程的目标是运用先进的软件开发技术和管理方法来提高软件的质量和生产率,也就是要以较短的周期、较低的成本生产出高质量的软件产品,并最终实现软件的工业化生产。
付出较低的开发成本
达到要求的软件功能
取得较好的软件性能
开发的软件易于移植
需要较低的维护费用
能按时完成开发工作,及时交付使用
1.4软件生存期
软件也有一个孕育、诞生、成长、成熟和衰亡的生存过程,我们称这个过程为软件生命周期或软件生存期。
软件生存期分为三个时期
软件定义
问题定义:关键问题是:“要解决的问题是什么”。提交的内容为关于问题性质、工程目标和工程规模的书面报告。
可行性研究:回答的关键问题是:“上一个阶段所确定的问题是否有行得通的解决办法”。提交的内容为可行性研究报告,即从技术、经济和社会因素等方面研究各方案的可行性
需求分析:对用户提出的要求进行分析并给出详细的定义
准确地回答“目标系统必须做什么”这个问题。也就是对目标系统提出完整、准确、清晰、具体的要求。
编写软件需求说明书或系统功能说明书及初步的系统用户手册
提交管理机构评审
软件开发
任务:具体设计和实现前一个时期即软件定义时期定义的软件。
执行人:系统设计员,高级程序员,程序员,测试工程师和辅助人员等
阶段划分:分为概要设计、详细设计、编码和单元测试、集成测试和系统测试。其中前两个阶段又称为系统设计,后两个阶段又称为系统实现。
概要设计
概括地回答“怎样实现目标系统?”。
设计程序的体系结构,也就是确定程序由哪些模块组成以及模块间的关系。
提交的文档是概要设计说明书。
详细设计
回答“应该怎样具体地实现这个系统”。
详细地设计每个模块,确定实现模块功能所需要的算法和数据结构。
提交的文档是软件的详细设计说明书。
程序编码和单元测试
写出正确的容易理解、容易维护的程序模块。
提交的文档为源程序、详尽的程序说明和单元测试报告。
集成测试和系统测试
通过各种类型的测试(及相应的调试)使软件达到预定的要求。(黑盒白盒测试等)
提交的文档为测试计划、详细测试方案以及实际测试结果等。
运行维护
改正性维护,也就是诊断和改正在使用过程中发现的软件错误;
适应性维护,即修改软件以适应环境的变化;
完善性维护,即根据用户的要求改进或扩充软件,使它更完善;
预防性维护,即修改软件为将来的维护活动预先做准备。
1.5软件工程方法概述
软件工程包含技术和管理两方面的内容,是技术和管理紧密结合所形成的工程学科。
通常将软件开发全过程中使用的一整套技术方法的集合称为方法学(methedology),也称为范型(paradigm)。
目前使用最广泛的软件工程方法学:传统方法学(结构化方法学),面向对象方法学。
三要素:方法、工具和过程。
软件工程方法为软件开发提供了 “如何做” 的技术;
软件工具为软件工程方法提供了自动的或半自动的软件支撑环境;
过程是为了获得高质量的软件所需要完成的一系列任务框架,它规定了完成各项任务的工作步骤。
1.6软件工具概述
软件工具的概念
软件工具是指能支持软件生存周期中某一阶段(如系统定义、需求分析、设计、编码、测试或维护等)的需要而使用的软件工具。
早期的软件工具主要用来辅助程序员编程,如编辑程序、编译程序、排错程序等。
软件工具的分类:支持软件开发过程的工具、支持软件维护过程的工具、支持软件管理过程和支持过程的工具
1.7软件工程知识体系及知识域
似乎不太重要
2.软件生存期模型
2.1瀑布模型
瀑布模型(waterfall model)也称软件生存周期模型,是W.Royce在1970年首先提出的。
瀑布模型将软件开发过程分解为可行性研究、软件需求、设计、编码、测试、运行与维护、退役。
它们既是软件开发过程的分解,也是软件生存周期的阶段划分。
瀑布模型按照各阶段的目标和任务逐步进行开发,直至通过确认测试,向用户交付最终软件制品为止。
面对较大的复杂系统,人们细化了瀑布模型的阶段或过程划分,如下图所示。
特点
阶段间具有顺序性和依赖性。其中包含两重含义:
① 必须等前一阶段的工作完成之后,才能开始后一阶段的工作;
② 前一阶段的输出文档就是后一阶段的输入文档。
推迟实现的观点
① 瀑布模型在编码之前设置了系统分析和系统设计的各个阶段,分析与设计阶段的基本任务规定,在这两个阶段主要考虑目标系统的逻辑模型,不涉及软件的物理实现。
② 清楚地区分逻辑设计与物理设计,尽可能推迟程序的物理实现,是按照瀑布模型开发软件的一条重要的指导思想
质量保证的观点
① 每个阶段都必须完成规定的文档,没有交出合格的文档就是没有完成该阶段的任务。
② 每个阶段结束前都要对所完成的文档进行评审,以便尽早发现问题,改正错误。
实际的瀑布模型
实际的瀑布模型是带“反馈环”的,如图所示。实现代表开发过程,虚线代表维护过程。
•V模型:瀑布模型的一个变体
V模型描述了测试阶段的活动与开发阶段相关活动(包括需求建模、概要设计、详细设计、编码)之间的关系。
优点
可强迫开发人员采用规范化的方法。
严格地规定了每个阶段必须提交的文档。
要求每个阶段交出的所有产品都必须是经过验证的。
缺点
由于瀑布模型几乎完全依赖于书面的规格说明,很可能导致最终开发出的软件产品不能真正满足用户的需要。如果需求规格说明与用户需求之间有差异,就会发生这种情况。
瀑布模型只适用于项目开始时需求已确定的情况。(需求不确定→不适合)
2.2快速原型模型
快速原型是快速建立起来的可以在计算机上运行的程序,它所能完成的功能往往是最终产品能完成的功能的一个子集。
原型有两类。
(1)抛弃型原型(实验性原型)
利用原型定义和确认了软件需求后,原型就完成了任务。
开发人员就可以按照确认的需求进行软件设计、编码、测试。
(2)应用型原型(进化性原型)
利用原型确认软件需求后,对原型进一步加工、完善,使之成为系统的一部分。
优点
(1)有助于满足用户的真实需求。
(2)原型系统已经通过与用户的交互而得到验证,据此产生的规格说明文档能够正确地描述用户需求。
(3)软件产品的开发基本上是按线性顺序进行。
(4)因为规格说明文档正确地描述了用户需求,因此,在开发过程的后续阶段不会因为发现规格说明文档的错误而进行较大的返工。
(5)开发人员通过建立原型系统已经学到了许多东西,因此,在设计和编码阶段发生错误的可能性也比较小,这自然减少了在后续阶段需要改正前面阶段所犯错误的可能性。
(6) 快速原型的突出特点是“快速”。开发人员应该尽可能快地建造出原型系统,以加速软件开发过程,节约软件开发成本。原型的用途是获知用户的真正需求,一旦需求确定了,原型可以抛弃,当然也可以在原型的基础上进行开发。
2.3增量模型
使用增量模型开发软件时,把软件产品作为一系列的增量构件来设计、编码、集成和测试。
每个构件由多个相互作用的模块构成,并且能够完成特定的功能。
每个增量构件应当实现某种系统功能,因此增量构件的开发可以采用瀑布模型的方式
采用增量模型需注意的问题
(1)在把每个新的增量构件集成到现有软件体系结构中时,必须不破坏原来已经开发出的产品。
(2)软件体系结构必须是开放的,即向现有产品中加入新构件的过程必须简单、方便。
因此,采用增量模型比采用瀑布模型和快速原型模型更需要精心的设计。
优点
(1)能在较短时间内向用户提交可完成一些有用的工作产品,即从第1个构件交付之日起,用户就能做一些有用的工作。
(2)逐步增加产品的功能可以使用户有较充裕的时间学习和适应新产品,从而减少一个全新的软件可能给用户组织带来的冲击。
(3)项目失败的风险较低,虽然在某些增量构件中可能遇到一些问题,但其他增量构件将能够成功地交付给客户。
(4)优先级最高的服务首先交付,然后再将其他增量构件逐次集成进来。因此,最重要的系统服务将接受最多的测试。
2.4螺旋模型
采用增量模型需注意的问题
(1)在把每个新的增量构件集成到现有软件体系结构中时,必须不破坏原来已经开发出的产品。
(2)软件体系结构必须是开放的,即向现有产品中加入新构件的过程必须简单、方便。
因此,采用增量模型比采用瀑布模型和快速原型模型更需要精心的设计。
理解这种模型的一个简便方法,是把它看做在每个阶段之前都增加了风险分析过程的快速原型模型。
完整的螺旋模型
在螺旋模型中,软件过程表示成一个螺线,而不是像以往的模型那样表示为一个具有回溯的活动序列。
在螺线上的每一个循环表示过程的一个阶段。
每个阶段开始时的任务是确定该阶段的目标、为完成这些目标选择方案及设定这些方案的约束条件。接下来的任务是,从风险角度分析上一步的工作结果,努力排除各种潜在的风险,通常用建造原型的方法来排除风险。如果成功地排除了所有风险,则启动下一步开发步骤,在这个步骤的工作过程相当于纯粹的瀑布模型。最后是评价该阶段的工作成果并计划下一个阶段的工作。
螺旋模型适合大型软件开发,特别是目前应用广泛的电子商务、电子政务一类的业务软件系统。
开发这类系统时,软件需求往往不能完全确定。
项目开始时开发人员与用户协商,将能够确定的需求、暂时不能确定的需求划分为一系列的增量,并为增量排序,确定的、急需的增量排在前面,暂时不能确定或不急需的放在后面。
采用螺旋模型开发这类软件具有边学习、边建模,边开发、边使用、边改进的优点。
螺旋模型的4项活动
螺线上的每一个循环可划分为4个象限,分别表达了4个方面的活动。
(1)目标设定——定义在该阶段的目标,弄清对过程和产品的限制条件,制订详细的管理计划,识别项目风险,可能还要计划与这些风险有关的对策。
(2)风险估计与弱化——针对每一个风险进行详细分析,设想弱化风险的步骤。
(3)开发与验证——评价风险之后选择系统开发模型。
(4)计划——评价开发工作,确定是否继续进行螺线的下一个循环。如果确定要继续,则计划项目的下一个阶段的工作
优点
对可选方案和约束条件的强调有利于已有软件的重用,也有助于把软件质量作为软件开发的一个重要目标。
减少了过多测试或测试不足所带来的风险。
在螺旋模型中维护只是模型的另一个周期,因而在维护和开发之间并没有本质区别。
缺点
螺旋模型是风险驱动的,因此要求软件开发人员必须具有丰富的风险评估经验和这方面的专门知识,否则将出现真正的风险:当项目实际上正在走向灾难时,开发人员可能还以为一切正常。
2.5喷泉模型
喷泉模型是典型的面向对象生命周期模型。
“喷泉”一词体现了迭代和无间隙特性。图中代表不同阶段的圆圈相互重叠,这明确表示两个活动之间存在重叠。用面向对象方法开发软件时,在分析、设计和编码等项开发活动之间不存在明显的边界,而各阶段在表示方法上的一致性也保证了各项开发活动之间的无缝过度。途中一个阶段内的向下箭头代表该阶段中的迭代或求精。
2.6统一过程
工作流
在统一过程中,有6个核心工作流。
① 业务建模工作流。用商业用例为商业过程建立文档。
② 需求工作流。目标是描述系统应该做什么,确保开发人员构建正确的系统。为此,需明确系统的功能需求和非功能需求(约束)。
③ 分析和设计工作流。其目标是说明如何做。结果是分析模型和设计模型。
④ 实现工作流。用分层的方式组织代码的结构,用构件的形式来实现类,对构件进行单元测试,将构件集成到可执行的系统中。
⑤ 测试工作流。验证对象之间的交互、是否所有的构件都集成了、是否正确实现了所有需求、查错并改正。
⑥ 部署工作流。制作软件的外部版本、软件打包、分发、为用户提供帮助和支持。
阶段
统一过程有4个阶段,分别是初始阶段、细化阶段、构造阶段和移交阶段。
① 初始阶段。初始阶段主要关注项目计划和风险评估,其目的是确定是否值得开发目标信息系统。
② 细化阶段。细化阶段关心定义系统的总体框架,其目标是:细化初始需求(用况)、细化体系结构、监控风险并细化它们的优先级、细化业务案例以及制订项目管理计划。
③ 构造阶段。构造阶段是建立系统,构造信息系统的第1个具有操作质量的版本,以能够交付给客户进行b测试的版本结束,有时称为测试版本。
④ 移交阶段。移交阶段包含b测试时期,以发布完整的系统而终止,其目标是确保信息系统真正满足客户的需求。
2.7基于构件的开发模型
强调使用可复用的软件“构件”来设计和构造基于计算机的系统的过程。
考虑的焦点是集成,而不是实现。
不考虑构件的开发技术,基于构件的开发模型由以下步骤组成:
(1)对于该问题领域的基于构件的可用产品进行研究和评估。
(2)考虑构件集成的问题。
(3)设计软件架构以容纳这些构件。
(4)将构件集成到架构中。
(5)进行充分的测试以保证功能正常。
2.8敏捷过程
2001年,Kent Beck等17名编程大师发表“敏捷软件开发”宣言:
我们正在通过亲身实践以及帮助他人实践的方式来揭示更好的软件开发之路,通过这项工作,我们认为:
个体和交互胜过过程和工具;
可工作软件胜过宽泛的文档;
客户合作胜过合同谈判;
响应变化胜过遵循计划。
任何一个敏捷过程都可以由所强调的3个关键假设识别出来,这3个假设可适用于大多数软件项目。
(1) 提前预测哪些需求是稳定的、哪些需求会变化非常困难。同样的,预测项目进行中客户优先级的变化也很困难。
(2) 对很多软件,设计和构建是交错进行的。事实上,两种活动应当顺序开展以保证通过构建实施来验证设计模型,而在通过构建验证之前很难估计应该设计到什么程度。
(3) 从制订计划的角度来看,分析、设计、构建和测试并不像我们所设想的那么容易预测。
原则
(1)我们最优先要做的是通过尽早、持续交付有价值的软件来使客户满意。
(2)即使在开发的后期,也欢迎需求变更。敏捷过程利用变更为客户创造竞争优势。
(3)经常交付可运行软件,交付的间隔可以从几个星期到几个月,交付的时间间隔越短越好。
(4)在整个项目开发期间,业务人员和开发人员必须天天都在一起工作。
(5)围绕有积极性的个人构建项目。给他们提供所需的环境和支持,并且信任他们能够完成工作。
(6)在团队内部,最富有效果和效率的信息传递方法是面对面交谈。
(7)可运行软件是进度的首要度量标准。
(8)敏捷过程提倡可持续的开发速度。责任人、开发者和用户应该能够保持一种长期、稳定的开发速度。
(9)不断地关注优秀的技能和好的设计会增强敏捷能力。
(10)简单是必要的。
(11)好的架构、需求和设计出自于自组织团队。
(12)每隔一定时间,团队会反省如何才能更有效地工作,并相应调整自己的行为
使用范围
敏捷软件开发过程适合小型一般软件的开发
适合建造原型系统
适合开发需求不确定的易变的业务处理系统
适合负责任的专业用户能加入开发团队并一直在开发现场工作的软件项目(多数场合,这一点是困难的)。
用敏捷软件开发过程快速构建原型,进行人机界面设计将是十分有效的。
生命攸关的实时系统,如空中交通指挥控制系统,机载、弹载、星载、船载的导航和武器控制系统的关键软件采用敏捷软件开发过程是不适宜的。这些软件需求定义严格、开发责任分明、必须有严格的测试和标准的文档,对安全性和保密性有更严格的规定。
极限编程(eXtreme Programming,XP)
使用最广泛的敏捷过程,最初由Kent Beck提出。XP包含了策划、设计、编码和测试4个框架活动的规则和实践。
自适应软件开发
自适应软件开发(adaptive software development,ASD)是由Jim Highsmith提出的;
它可作为构建复杂软件和系统的一项技术,其基本概念着眼于人员合作和团队自组织。
Part2.结构化分析与设计方法
3.软件需求获取与结构化分析方法
3.1需求获取与需求分析阶段的任务
需求获取的主要任务是与客户或用户沟通,了解系统或产品的目标是什么?客户或用户想要实现什么?系统和产品如何满足业务的要求,最终系统或产品如何用于日常工作?
获取并理解用户的需求是软件工程师所面对的最困难的任务之一。
导出需求变得如此困难的原因归为以下几个方面的问题:
系统的目标或范围问题;
需求不准确性问题 ;
需求的易变问题 ;
需求获取除了需要有专业的系统分析师,还需要通过有效的客户/开发者的合作才能成功。
1.需求获取的任务和原则
需求获取的任务
(1) 发现和分析问题,并分析问题的原因/结果关系。
(2) 与用户进行各种方式的交流,并使用调查研究方法收集信息。
(3) 按照三个成分观察问题的不同侧面:即数据、过程和接口。
(4) 将获取的需求文档化,形式有用例、决策表、需求表等。
需求获取应遵循的原则
(1) 深入浅出的原则。就是说,需求获取要尽可能全面、细致。获取的需求是个全集,目标系统真正实现的是个子集。
(2) 以流程为主线的原则。在与用户交流的过程中,应该用流程将所有的内容串起来。如信息、组织结构、处理规则等。这样便于交流沟通。流程的描述既有宏观描述,也有微观描述。
2.需求获取的过程
开发高层的业务模型
定义项目范围和高层需求
识别用户类和用户代表
系统的不同用户之间在很多方面存在差异,例如:
(1) 使用产品的频率;
(2) 用户在应用领域的经验和使用计算机系统的技能;
(3) 所用到的产品功能;
(4) 为支持业务过程所进行的工作;
(5) 访问权限和安全级别
获取具体的需求
确定了项目范围和高层需求,并确定了用户类及用户代表后,就需要获取更具体、完整和详细的需求。具体需求的来源可以来自以下几种典型的途径。
(1) 与用户进行交流。
(2) 现有产品或竞争产品的描述文档。
(3) 系统需求规格说明。
(4) 当前系统的问题报告和改进要求。
(5) 市场调查和用户问卷调查。
(6) 观察用户如何工作。
确定目标系统的业务工作流
具体到当前待开发的应用系统,确定系统的业务工作流和主要的业务规则,采取需求调研的方法获取所需的信息.
需求整理与总结
必须对上面步骤取得的需求资料进行整理和总结,确定对软件系统的综合要求,即软件的需求。
并提出这些需求实现条件,以及需求应达到的标准。
这些需求包括功能需求、性能需求、环境需求、可靠性需求、安全保密要求、用户界面需求、资源使用需求、软件成本消耗与开发进度需求等。
3.软件需求分析阶段的任务
可以把软件需求分析阶段的工作分为4个步骤,即获取需求、分析需求、定义需求和验证需求,如图所示。
需求获取
通过启发、引导从客户(或用户)那里得到的原始需求是他们的业务要求(needs),简称为N。
这是分析之前获取的需求,其中可能存在一些实际问题,这些问题只有通过分析才能得到解决,直接把获取的需求作为软件设计阶段的依据将会导致严重的后果。
需求分析
认真研究获取的需求,必须考虑以下几方面:
(1) 完整性:每项获取的需求都应给出清楚的描述,使得软件开发工作能够取得设计和实现该功能所需要的全部必要信息。
(2) 正确性:获取的每项需求必须是准确无误的,并且需求描述无歧义性。
(3) 合理性:各项需求之间、软件需求与系统需求之间应是协调一致的,不应存在矛盾和冲突。
(4) 可行性:包括技术可行性 、经济可行性 、社会可行性 。
(5) 充分性:获取的需求是否全面、周到。
由于分析的过程会对获取的需求做部分调整,也即从获取的需求N中去掉了一些a,又补充了一些c,从而得到的是分析的需求R1(b+c)。
需求定义
将已经过分析的需求清晰、全面、系统、准确地描述成为正式的文档,这一步定义需求的工作就是编写需求规格说明。
需求验证
为了确保已定义的需求(需求规格说明)准确无误,并能为客户(或用户)理解和接受,需要对其进行严格的评审。
3.2结构化分析方法
•结构化分析模型
结构化分析方法是一种建模技术,它建立的分析模型如图所示。
1.功能建模(数据流图)
概念
功能建模的思想就是用抽象模型的概念,按照软件内部数据传递、变换的关系,自顶向下逐层分解,直到找到满足功能要求的所有可实现的软件为止。功能模型用数据流图来描述。
基本图形符号:(注意命名的词性)
数据源或数据汇点表示图中要处理数据的输入来源或处理结果要送往何处,数据源或数据汇点不是目标系统的一部分,只是目标系统的外围环境中的实体部分,因此称为外部实体。实际问题中可能是组织、部门、人、相关的软件系统或硬件设备。
数据流表示数据沿箭头方向的流动。数据流可表示在加工之间被传送的有名数据,也可表示未命名数据;这些数据虽然没有命名,但因其所连接的是有名加工和有名数据存储,所以含义也是清楚的。
加工是对数据对象的处理或变换,加工的名字是动词短语,以表明所完成的加工。一个加工可能需要多个数据流,也可能产生多个数据流。
数据存储在数据流图中起保存数据的作用,可以是数据库文件或任何形式的数据组织。从数据存储中引出的数据流可理解为从数据存储读取数据或得到查询结果,指向数据存储的数据流理解为向数据存储写入数据。
多个数据流之间关系:
环境图
环境图(context diagram)也称为顶层数据流图(或0层数据流图),它仅包括一个数据处理过程,也就是要开发的目标系统。
环境图的作用是确定系统在其环境中的位置,通过确定系统的输入和输出与外部实体的关系确定其边界。
数据流图的分层
对于稍微复杂一些的实际问题,在数据流图上常常出现十几个甚至几十个加工,这样的数据流图看起来不直观,不易理解,分层的数据流图能很好地解决这一问题。
按照系统的层次结构进行逐步分解,并以分层的数据流图反映这种结构关系,能清楚地表达和容易理解整个系统。
招生系统的S0分解为S1-S4:
例题
银行储蓄系统的业务流程:
储户填写的存款单或取款单由业务员键入系统;
如果是存款则系统记录存款人姓名、住址(或电话号码)、身份证号码、存款类型、存款日期、到期日期、利率、密码(可选)等信息,并印出存单给储户;
如果是取款而且开户时留有密码,则系统首先核对储户密码,若密码正确或存款时未留密码,则系统计算利息并印出利息清单给储户。
要求画出分层的数据流图,并细化到2层数据流图。
答案
(1) 识别外部实体及输入输出数据流。
外部实体:储户、业务员。
输入数据:如果需要储户输入密码,储户才直接与系统进行交互。储户填写的存款或取款信息通过业务员键入系统,可以将存款及取款信息抽象为事务。
输出数据:存款单,利息清单。
(2) 画出环境图(顶层数据流图)
(3) 画出一层数据流图
对环境图中的银行储蓄系统进行分解,从大的方面分解为接收事务、处理存款、处理取款3部分,得到1层数据流图。
接收事务的主要功能是判断一个事务(输入数据流)的类型,其结果或者是存款业务,或者是取款业务。
存款信息需要使用外部文件或数据库的方式来存储 。
(4) 画出二层数据流图
对一层图中的“处理存款”及“处理取款”进行进一步分解,得到二层数据流图,即处理存款的数据流图和处理取款的数据流图。
2.数据建模(ER图)
在结构化分析方法中,使用实体—关系建模技术来建立数据模型。
这种技术是在较高的抽象层次(概念层)上对数据库结构进行建模的流行技术。
实体—关系模型表示为可视化的实体—关系图(entity-relationship diagram,ERD),也称为ER图。
ER图中仅包含3种相互关联的元素:数据对象(实体)、描述数据对象的属性及数据对象彼此间相互连接的关系。
数据对象
数据对象是目标系统所需要的复合信息的表示,所谓复合信息是具有若干不同属性的信息。在ER图中用矩形表示数据对象。
在实际问题中,数据对象(实体)可以是外部实体、事物、角色、行为或事件、组织单位、地点或结构等。
属性
属性定义数据对象的特征,如数据对象学生的学号、姓名、性别、专业等,课程的课程编号、课程名称、学分等。
在ER图中用椭圆或圆角矩形表示属性,并用无向边将属性与相关的数据对象连接在一起。
关系
不同数据对象的实例之间是有关联关系的,在ER图上用无向边表示。在无向边的两端应标识出关联实例的数量,也称为关联的重数。从关联重数的角度可以将关联分为3种。
(1) 一对一(1:1)关联
(2) 一对多(1:m)关联
(3) 多对多(m:n)关联
实例关联还有“必须”和“可选”之分。
关联数量的表示
在ER图中用圆圈表示所关联的实例是可选的,隐含表示“0”,没有出现圆圈就意味着是必须的。出现在连线上的短竖线可以看成是“1”。
关系的属性
关系本身也可能有属性,这在多对多的关系中尤其常见,如学生和课程之间的关系可起名为“选课”,其属性应该有学期、成绩等。
关系属性的表示:在表示关系的无向边上再加一个菱形框,并在菱形框中标明关系的名字,关系的属性同样用椭圆形或圆角矩形表示,并用无向边将关系与其属性连接起来。
银行储蓄系统ER图:
3.行为建模(状态图)
没大看懂?什么事件?什么守卫表达式?
状态转换图(简称状态图)通过描绘系统的状态及引起系统状态转换的事件,来表示系统的行为。状态图中使用的主要符号如图所示。
状态
状态是任何可以被观察到的系统行为模式,一个状态代表系统的一种行为模式,状态规定了系统对事件的响应方式。
状态可能有:初态(初始状态)、终态(最终状态)和中间态。
在一张状态图中只能有一个初态,而终态则可以有多个,也可以没有。
状态的表示
初态用实心圆表示,终态用牛眼图形表示,中间态用圆角矩形表示。
状态转换
状态图中两个状态之间带箭头的连线称为状态转换。
状态的变迁通常是由事件触发的,在这种情况下应在表示状态转换的箭头线上标出触发转换的事件表达式。
如果在箭头线上未标明事件,则表示在源状态的内部活动执行完之后自动触发转换。
事件
事件是在某个特定时刻发生的事情,它是对引起系统做动作或从一个状态转换到另一个状态的外部事件的抽象。事件表达式的语法如下:
事件说明(守卫条件)/动作表达式
(1) 事件说明的语法如下:
事件名(参数表)
(2) 守卫条件是一个布尔表达式。如果同时使用守卫条件和事件说明,则当且仅当事件发生且布尔表达式成立时,状态转换才发生。如果只有守卫条件没有事件说明,则只要守卫条件为真,状态转换就发生。
(3) 动作表达式是一个过程表达式,当状态转换开始时执行该表达式。
存款过程的状态图:
取款过程的状态图:
4.数据字典
数据字典以词条方式定义在数据模型、功能模型和行为模型中出现的数据对象及控制信息的特性,给出它们的准确定义,包括数据流、加工、数据文件、数据元素,以及数据源点、数据汇点等。
数据字典成为把3种分析模型黏合在一起的“黏合剂”,是分析模型的“核心”。
词条描述
对于在数据流图中每一个被命名的图形元素均加以定义;
其内容包括图形元素的名字,图形元素的别名或编号,图形元素类别(如加工、数据流、数据文件、数据元素、数据源点或数据汇点等)、描述、定义、位置等。
数据流词条
数据流是数据结构在系统内传播的路径,数据流词条应包括以下几项内容。
①数据流名:要求与数据流图中该图形元素的名字一致。
②简述:简要介绍它产生的原因和结果。
③组成:数据流的数据结构。
④来源:数据流来自哪个加工或作为哪个数据源的外部实体。
⑤去向:数据流流向哪个加工或作为哪个数据汇点的外部实体。
⑥流通量:单位时间数据的流通量。
⑦峰值:流通量的极限值。
数据元素词条
数据流图中的每个数据结构都是由数据元素构成的,数据元素是数据处理中最小的、不可再分的单位,它直接反映事物的某一特征。
① 类型:数据元素分为数字型与文字型。数字型又分为离散值和连续值,文字的类型用编码类型和长度区分。
② 取值范围:离散值的取值或是枚举的(如3,17,21),或是介于上下界的一组数(如2..100);连续值一般是有取值范围的实数集(如0.0..100.0)。对于文字型,文字的取值需加以定义。
③ 相关的数据元素及数据结构。
数据存储文件词条
数据存储文件是数据保存的地方。一个数据存储文件词条应有以下几项内容。
① 文件名:要求与数据流图中该图形元素的名字一致。
② 简述:简要介绍存放的是什么数据。
③ 组成:文件的数据结构。
④ 输入:从哪些加工获取数据。
⑤ 输出:由哪些加工使用数据。
⑥ 存取方式:分为顺序、直接、关键码等不同存取方式。
⑦ 存取频率:单位时间的存取次数。
加工词条
加工可以使用诸如判定表、判定树、结构化语言等形式表达,主要描述如下。
① 加工名:要求与数据流图中该图形元素的名字一致。
② 编号:用以反映该加工的层次和父子关系。
③ 简述:加工逻辑及功能简述。
④ 输入:加工的输入数据流。
⑤ 输出:加工的输出数据流。
⑥ 加工逻辑:简述加工程序和加工顺序。
数据源点及数据汇点词条
对于一个数据处理系统来说,数据源点和数据汇点应比较少。
① 名称:要求与数据流图中该外部实体的名字一致。
② 简述:简要描述是什么外部实体。
③ 有关数据流:该实体与系统交互时涉及哪些数据流。
④ 数目:该实体与系统交互的次数。
数据结构描述
在数据字典的编制中,分析员最常用的描述数据结构的方式有定义式、Warnier图等。
定义式。在数据流图中,数据流和数据文件都具有一定的数据结构,因此,必须以一种清晰、准确、无二义性的方式来描述数据结构。
Warnier图。Warnier图是表示数据结构的另一种图形工具,它用树形结构来描绘数据结构。
存折的定义格式
存折=户名+所号+账号+开户日+性质+(印密)+ 1{存取行}50
所号=“001”..“999”
户名=2{字母}24
账号=“00000000001”..“99999999999”
开户日=年+月+日
性质=“1”..“6”
印密=(“0”|“000001”..“999999”)
存取行=日期+(摘要)+支出+存入+余额+操作+复核
日期=年+月+日
年=“0001”..“9999”
月=“01”..“12”
日=“01”..“31”
Warnier图
5.加工规格说明
在对数据流图的分解中,位于层次树最低层的加工也称为基本加工或原子加工,对于每一个基本加工都需要进一步说明,这称为加工规格说明。
在编写基本加工的规格说明时,主要目的是要表达“做什么”,而不是“怎样做”。
加工规格说明应满足如下的要求:
(1) 对数据流图的每一个基本加工,必须有一个加工规格说明。
(2) 加工规格说明必须描述基本加工如何把输入数据流变换为输出数据流的加工规则。
(3) 加工规格说明必须描述实现加工的策略而不是实现加工的细节。
(4) 加工规格说明中包含的信息应是充足的,完备的,有用的,没有重复的多余信息。
决策表
决策表由4个部分组成:
左上部分是条件茬,在此区域列出了各种可能的单个条件;
左下部分是动作茬,在此区域列出了可能采取的单个动作;
右上部分是条件项,在此区域列出了针对各种条件的每一组条件取值的组合;
右下部分是动作项,这些动作项与条件项紧密相关,它指出了在条件项的各组取值的组合情况下应采取的动作。
建立决策表的步骤
(1) 列出与一个具体过程(或模块)有关的所有处理。
(2) 列出过程执行期间的所有条件(或所有判断)。
(3) 将特定条件取值组合与特定的处理相匹配,消去不可能发生的条件取值组合。
(4) 将右部每一纵列规定为一个处理规则,即对于某一条件取值组合将有什么动作。
决策表的改进:如果表中有两条或更多的处理规则具有相同的动作,并且其条件项之间存在着某种关系,就可设法将它们合并。
决策树
决策树(decision tree)也是用来表达加工逻辑的一种工具,有时侯它比决策表更直观。
检查订货单的决策树
3.3系统需求规格说明
这三节看课本
3.4需求评审
3.5需求管理
4.结构化设计方法
4.1软件设计的概念及原则
概念
设计是一项核心的工程活动。
“什么是设计?设计是你站在两个世界——技术世界和人类的目标世界——而你尝试将这两个世界结合在一起……”。
“设计良好的建筑应该展示出坚固、适用和令人赏心悦目”。
原则
(1) 分而治之
分而治之是人们解决大型复杂问题时通常采用的策略。将大型复杂的问题分解为许多容易解决的小问题,原来的问题也就容易解决了。
软件的体系结构设计、模块化设计都是分而治之策略的具体表现。
模块是构成软件的基本构件。模块化是将整体软件划分成独立命名且可以独立访问的模块,不同的模块通常具有不同的功能或指责,每个模块可独立地开发吗、测试,最后组装成完整的软件。
尽管模块分解可以简化要解决的问题,但模块分解并不是越小越好。
当模块数目增加时,每个模块的规模将减小,开发单个模块的成本确实减少了;但是,随着模块数目增加,模块之间关系的复杂程度也会增加,设计模块间接口所需要的工作量也将增加,如图所示。
(2) 模块独立性
定义: 是指软件系统中每个模块只涉及软件要求的具体的子功能, 而和软件系统中其它模块的接口是简单的。
有效的模块化使软件便于分工协作开发。
独立的模块比较容易测试和维护。
模块独立性的度量准则
内聚:是模块功能强度(一个模块内部各个元素彼此结合的紧密程度)的度量。
耦合:是模块之间的互相连接的紧密程度的度量。
模块独立性比较强的模块应是高内聚低耦合的模块。
耦合
非直接耦合(Nondirect Coupling)
两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
非直接耦合的模块独立性最强。
数据耦合 (Data Coupling)
一个模块访问另一个模块时,彼此之间是通过简单数据参数 (不是控制参数、公共数据结构或外部变量) 来交换输入、输出信息的。也是较理想的耦合。
特征耦合 (Stamp Coupling)
一组模块通过参数表传递记录信息,就是特征耦合。这个记录是某一数据结构的子结构,而不是简单变量。
控制耦合 (Control Coupling)
如果一个模块通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。
公共耦合(Common Coupling)
若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为公共耦合。公共的数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。
公共数据区为全局变量/数组等
内容耦合 (Content Coupling)
一个模块直接访问另一个模块的内部数据。
一个模块不通过正常入口转到另一模块内部。
两个模块有一部分程序代码重迭。(只可能出现在汇编语言中)。
一个模块有多个入口。
设计原则
- 尽量使用数据耦合
- 少用控制耦合
- 限制使用公共耦合(除非传递大量数据)
- 完全不用内容耦合
实际上,两个模块之间的耦合不只是一种类型,而是多种类型的混合。这就要求设计人员进行分析、比较,逐步加以改进,以提高模块的独立性。
内聚
偶然内聚(Coincidental Cohesion)
当模块内各部分之间没有联系,或者即使有联系,这种联系也很松散,则称这种模块为偶然内聚模块,内聚程度最低。
缺点:
1)内容不易理解,很难描述其功能。
2)把完整的程序分割到多个模块中,在程序运行时会频繁地互相调用。
逻辑内聚(Logical Cohesion)
把几种相关的功能组合在一起,每次被调用时,由传送给模块的判定参数来确定该模块应执行哪个功能。
缺点
1)不易修改,因包含多个功能
2)需传递控制参数——控制耦合
3)未用部分调入内存,影响效率
但是如果程序只是由一堆if或case和调用其他子程序的语句组成,这种逻辑内聚也是很好的。例如:事件处理器。
时间内聚(Classical Cohesion)
时间内聚模块大多为多功能模块,但模块的各个功能的执行与时间有关,通常要求所有功能必须在同一时间段内执行。
例如:
1.初始化模块和终止模块。
2.紧急故障处理模块:关闭文件、报警、保留现场必须无中断地同时处理
时间内聚模块比逻辑内聚模块的内聚程度又稍高一些。在一般情形下,各部分可以以任意的顺序执行,所以它的内部逻辑更简单。
过程内聚(Procedural Cohesion)
如果一个模块内的处理是相关的,而且必须以特定次序执行,则是过程内聚。
使用流程图做为工具设计程序时,把流程图中的某一部分划出组成模块,就得到过程内聚模块。
例如,把流程图中的循环部分、判定部分、计算部分分成三个模块,这三个模块都是过程内聚模块。
过程内聚仅包含完整功能的一部分。
通信内聚(Communicational Cohesion) 亦称数据内聚
一个模块中所有处理元素都使用同一个输入数据和产生同一个输出数据。
例如:模块A:从文件file读取数据
由数据产生报表A
由数据产生报表B
顺序内聚
一个模块中的处理元素和同一功能密切相关,而且这些处理必须顺序执行,则称为顺序内聚。
根据数据流图划分模块时,通常得到顺序内聚的模块。
功能内聚 (Functional Cohesion)
一个模块中各个部分都是完成某一具体功能必不可少的组成部分,或者说该模块中所有部分都是为了完成一项具体功能而协同工作,紧密联系,不可分割的。则称该模块为功能内聚模块。
优点:容易修改和维护
(3) 提高抽象层次
抽象是指忽视一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。
当我们进行软件设计时,设计开始时应尽量提高软件的抽象层次,按抽象级别从高到低进行软件设计。
(4) 复用性设计
复用是指同一事物不做修改或稍加修改就可以多次重复使用。将复用的思想用于软件开发,称为软件复用。
我们将软件的重用部分称为软构件。
也就是说,在构造新的软件系统时不必从零做起,可以直接使用已有的软构件即可组装(或加以合理修改)成新的系统。
(5) 灵活性设计
保证软件灵活性设计的关键是抽象。
面向对象系统中的类结构类似一座金字塔,越接近金字塔的顶端,抽象程度就越高。
“抽象”的反义词是“具体”。理想情况下,一个系统的任何代码、逻缉、概念在这个系统中都应该是唯一的,也就是说不存在重复的代码。
在设计中引入灵活性的方法有:
- 降低耦合并提高内聚(易于提高替换能力);
- 建立抽象(创建有多态操作的接口和父类);
- 不要将代码写死(消除代码中的常数);
- 抛出异常(由操作的调用者处理异常);
- 使用并创建可复用的代码。
4.2结构化设计
结构化软件设计的任务
从工程管理的角度,可以将软件设计分为概要设计阶段和详细设计阶段。
从技术的角度,传统的结构化方法将软件设计划分为体系结构设计、数据设计、接口设计和过程设计4部分;
面向对象方法则将软件设计划分为体系结构设计、类设计/数据设计、接口设计和构件级设计4部分。
体系结构设计:体系结构设计定义软件的主要结构元素及其之间的关系。通常称为模块设计。
接口设计(内部、外部接口):接口设计描述用户界面,软件和其他硬件设备、其他软件系统及使用人员的外部接口,以及各种构件之间的内部接口。
数据设计:传统方法主要根据需求阶段所建立的实体—关系图(ER图)来确定软件涉及的文件系统的结构及数据库的表结构。
过程设计:过程设计的主要工作是确定软件各个组成部分内的算法及内部数据结构,并选定某种过程的表达形式来描述各种算法。
结构化设计与结构化分析的关系
结构化设计方法的实施要点
(1) 研究、分析和审查数据流图。
(2) 根据数据流图决定问题的类型:变换型和事务型。针对两种不同的类型分别进行分析处理。
(3) 由数据流图推导出系统的初始结构图。
(4) 利用一些启发式原则来改进系统的初始结构图,直到得到符合要求的结构图为止。
(5) 根据分析模型中的实体关系图和数据字典进行数据设计,包括数据库设计或数据文件的设计。
(6) 在上面设计的基础上,并依据分析模型中的加工规格说明、状态转换图进行过程设计。
(7) 制定测试计划。
模块结构及表示
一般通过功能划分过程来完成软件结构设计。功能划分过程从需求分析确立的目标系统的模型出发,对整个问题进行分割,使其每一部分用一个或几个软件模块加以解决,整个问题就解决了。
模块
一个软件系统通常由很多模块组成,结构化程序设计中的函数和子程序都可称为模块,它是程序语句按逻辑关系建立起来的组合体。
模块用矩形框表示,并用模块的名字标记它。
大模块还可以分解,不能分解的最小模块称为原子模块。如果一个软件系统的全部数据计算或处理都由原子模块完成,其他非原子模块仅执行控制或协调功能,这样的系统就是完全因子分解的系统。完全因子分解的系统被认为是最好的系统。但实际上大多数做不到。
模块分类
模块的结构
模块结构最普通的形式就是树状结构和网状结构,如图所示。
结构图
结构图(structure chart,SC)是精确表达模块结构的图形表示工具。
(1) 模块的调用关系和接口:在结构图中,两个模块之间用单向箭头连接。
(2) 模块间的信息传递:当一个模块调用另一个模块时,调用模块把数据或控制信息传送给被调用模块,以使被调用模块能够运行。
模块间的调用关系和接口表示
(3) 条件调用和循环调用 :当模块A有条件地调用另一个模块B时,在模块A的箭头尾部标以一个菱形符号;当一个模块A反复地调用模块C和模块D时,在调用箭头尾部则标以一个弧形符号。
(4) 结构图的形态特征。在图中,上级模块调用下级模块,它们之间存在主从关系。相关概念:宽度、深度、扇入、扇出。
深度: 表示软件结构中从顶层模块到最底层模块的层数
宽度: 是软件结构在同一层次上的模块总数的最大值.一般来说,宽度越大系统就越复杂.
扇出: 指一个模块直接控制的下属模块个数,经验表明,一个设计的好的典型系统的平均扇出通常是3或4个,太多或太少都不好.
扇入: 指一个模块的直接上属模块个数
数据结构及表示
数据结构是数据的各个元素之间逻辑关系的一种表示。
数据结构设计应确定数据的组织、存取方式、相关程度,以及信息的不同处理方法。
数据结构的组织方法和复杂程度可以灵活多样,但典型的数据结构种类是有限的,它们是构成一些更复杂结构的基本构件块。
4.3体系结构设计
基于数据流方法的设计过程
基于数据流的设计方法也称为过程驱动的设计方法;
这种方法与软件需求分析阶段的结构化分析方法相衔接,可以很方便地将用数据流图表示的信息转换成程序结构的设计描述;
这种方法还能和编码阶段的“结构化程序设计方法”相适应,成为常用的结构化设计方法。
典型的数据流类型和系统结构
典型的数据流类型有变换型数据流和事务型数据流,数据流的类型不同,得到的系统结构也不同。
通常,一个系统中的所有数据流都可以认为是变换流,但是,当遇到有明显事务特性的数据流时,建议采用事务型映射方法进行设计。
变换型数据流和变换型系统结构图
变换型数据处理问题的工作过程大致分为3步,即取得数据、变换数据和给出数据,如图所示。
变换型系统的结构图由输入、中心变换和输出3部分组成。
事务型数据流和事务型系统结构图
通常接受一项事务,根据事务处理的特点和性质,选择分派一个适当的处理单元,然后给出结果。
完成选择分派任务的部分称为事务处理中心,或分派部件。
结构图
简化的事务型系统结构图
事务型系统的结构图可以有多种不同的形式,如有多层操作层或没有操作层。
如果调度模块并不复杂,可将其归入事务中心模块。
变换型映射方法
系统数据处理问题的处理流程总能表示为变换型数据流图,进一步可采用变换型映射方法建立系统的结构图。
也可能遇到明显的事务数据处理问题,这时可采用事务型映射方法。
变换分析方法的步骤
(1) 重画数据流图。在需求分析阶段得到的数据流图侧重于描述系统如何加工数据,而重画数据流图的出发点是描述系统中的数据是如何流动的。
(2)在数据流图上区分系统的逻辑输入、逻辑输出和中心变换部分。
(3) 进行一级分解,设计系统模块结构的顶层和第一层。自顶向下设计的关键是找出系统树形结构图的根或顶层模块。
首先设计一个主模块,并用程序的名字为它命名,然后将它画在与中心变换相对应的位置上。
第1层设计:为每个逻辑输入设计一个输入模块,它的功能是为主模块提供数据;为每个逻辑输出设计一个输出模块,它的功能是将主模块提供的数据输出;为中心变换设计一个变换模块,它的功能是将逻辑输入转换成逻辑输出。 第一层模块与主模块之间传送的数据应与数据流图相对应,如图所示。
(4) 进行二级分解,设计中、下层模块。
这一步工作是自顶向下,逐层细化,为每一个输入模块、输出模块、变换模块设计它们的从属模块。
设计下层模块的顺序是任意的。但一般是先设计输入模块的下层模块。
事务型映射方法
事务分析也是从分析数据流图开始,自顶向下,逐步分解,建立系统的结构图。
事务分析方法的步骤
(1) 识别事务源。利用数据流图和数据词典,从问题定义和需求分析的结果中,找出各种需要处理的事务。
(2) 规定适当的事务型结构。在确定了该数据流图具有事务型特征之后,根据模块划分理论,建立适当的事务型结构。
(3) 识别各种事务和它们定义的操作。
(4) 注意利用公用模块。
(5) 建立事务处理模块。对每一事务,或对联系密切的一组事务,建立一个事务处理模块。
(6) 对事务处理模块规定它们全部的下层操作模块。
(7) 对操作模块规定它们的全部细节模块。
大型的软件系统通常是变换型结构和事务型结构的混合结构,所以,我们通常利用以变换分析为主,事务分析为辅的方式进行软件结构设计。
混合结构的例子
软件模块结构的改进方法
(1)模块功能的完善化。
一个完整的功能模块,不仅能够完成指定的功能,而且还应当能够告诉使用者完成任务的状态,以及不能完成的原因。也就是说,一个完整的模块应当有以下几部分。
① 执行规定的功能的部分。
② 出错处理的部分。 当模块不能完成规定的功能时,必须回送出错标志,向它的调用者报告出现这种例外情况的原因。
③ 如果需要返回一系列数据给它的调用者,在完成数据加工或结束时,应当给它的调用者返回一个“结束标志”。
(2) 消除重复功能,改善软件结构。
① 完全相似。在结构上完全相似,可能只是在数据类型上不一致。此时可以采取完全合并的方法。图a
② 局部相似:此时,不可以把两者合并为一,如图(b)所示,因为这样在合并后的模块内部必须设置许多查询开关,如图(f)所示。
(3) 模块的作用范围应在控制范围之内。
模块的控制范围包括它本身及其所有的从属模块。
模块的作用范围是指模块内一个判定的作用范围,凡是受这个判定影响的所有模块都属于这个判定的作用范围。
如果一个判定的作用范围包含在这个判定所在模块的控制范围之内,则这种结构是简单的。
图(b)表明作用范围不在控制范围之内。模块G做出一个判定之后,若需要模块 C工作,则必须把信号回送给模块D,再由D把信号回送给模块B。图中加黑框表示判定的作用范围。
图(c)虽然表明模块的作用范围是在控制范围之内,可是判定所在模块TOP所处层次太高,这样也需要经过不必要的信号传送,增加了数据的传送量。
图(d) 表明作用范围在控制范围之内,只有一个判定分支有一个不必要的穿越,是一个较好的结构;
图(e)所示为一个比较理想的结构。
如果在设计过程中,发现作用范围不在控制范围内,可采用如下办法把作用范围移到控制范围之内。
① 将判定所在模块合并到父模块中,使判定处于较高层次。
② 将受判定影响的模块下移到控制范围内。
③ 将判定上移到层次中较高的位置。
(4) 尽可能减少高扇出结构,随着深度增大扇入。
模块的扇出数是指模块调用子模块的个数。如果一个模块的扇出数过大,就意味着该模块过分复杂,需要协调和控制过多的下属模块。
出现这种情况是由于缺乏中间层次,所以应当适当增加中间层次的控制模块。如图所示。
一个模块的扇入数越大,则共享该模块的上级模块数目越多。扇入大,是有好处的。
但如果一个模块的扇入数太大,如超过8,而它又不是公用模块,说明该模块可能具有多个功能。
在这种情况下应当对它进一步分析并将其功能分解。
(5) 避免或减少使用病态连接。
应限制使用如下3种病态连接。
(6) 模块的大小要适中。
模块的大小,可以用模块中所含语句的数量的多少来衡量。
通常规定其语句行数为50~100,保持在一页纸之内,最多不超过500行。
例 银行储蓄系统
1.对银行储蓄系统的数据流图进行复查并精化,得到如图所示的数据流图。
2.确定数据流图具有变换特性还是事务特性。通过对精化后的数据流图进行分析,可以看到整个系统是对存款及取款两种不同的事务进行处理,因此具有事务特性。
3.确定输入流和输出流的边界
4.完成第一级分解。分解后的结构图如图所示。
5.完成第二级分解。对上图中的“输入数据”、“输出数据”和“调度”模块进行分解,得到未经精化的输入结构、输出结构和事务结构,分别如图(a)、(b)和(c)所示。将上面的3部分合在一起,得到初始的软件结构,如图所示。
第6步:对软件结构进行精化。
(1) 由于调度模块下只有两种事务,因此,可以将调度模块合并到上级模块中,如图所示。
(2) “检查密码”模块的作用范围不在其控制范围之内(即“输入密码”模块不在“检查密码”模块的控制范围之内),需对其进行调整,如图所示。
(3) 提高模块的独立性,并对“输入事务”模块进行细化。也可以将“检查密码”功能合并到其上级模块中。
4.4接口设计
接口设计概述
接口设计的依据是数据流图中的自动化系统边界。
接口设计主要包括3个方面:
- 模块或软件构件间的接口设计;
- 软件与其他软硬件系统之间的接口设计;
- 软件与人(用户)之间的交互设计。人机交互(用户)界面是人机交互的主要方式
人机交互界面
在设计阶段,必须根据需求把交互细节加入到用户界面设计中,包括人机交互所必须的实际显示和输入。
人机交互界面是给用户使用的,为了设计好人机交互界面,设计者需要了解以下信息:
(1)用户界面应具有的特性?
(2)使用软件的用户是什么人?
(3)用户怎样学习与新的计算机系统进行交互?
(4)用户需要完成哪些工作?
用户界面应具备的特性
可使用性:是用户界面设计最重要的目标.包括使用简单、界面一致、拥有help帮助功能、快速的系统响应和低的系统成本、具有容错能力等。
灵活性:考虑到用户的特点、能力和知识水平,应该使用户接口满足不同用户的要求。因此,对不同的用户,应有不同的界面形式,但不同的界面形式不应影响任务的完成。
可靠性:用户界面的可靠性是指无故障使用的间隔时间。用户界面应能保证用户正确、可靠地使用系统,保证有关程序和数据的安全性。
用户类型
外行型:以前从未使用过计算机系统的用户。
初学型:尽管对新的系统不熟悉,但对计算机还有一些使用经验的用户。
熟练型:对一个系统有相当多的经验,能够熟练操作的用户。
专家型:这一类用户了解系统内部的构造,有关于系统工作机制的专业知识,具有维护和修改基本系统的能力。专家型需要为他们提供能够修改和扩充系统能力的复杂界面。
界面设计类型
在选用界面形式的时候,应当考虑每种类型的优点和限制,可以从以下几个方面来考察:
(1) 使用的难易程度:对于没有经验的用户,该界面使用的难度有多大。
(2) 学习的难易程度:学习该界面的命令和功能的难度有多大。
(3) 操作速度:在完成一个指定操作时,该界面在操作步骤、击键和反应时间等方面效率有多高。
(4) 复杂程度:该界面提供了什么功能、能否用新的方式组合这些功能以增强界面的功能。
(5) 控制:人机交互时,是由计算机还是由人发起和控制对话。
(6) 开发的难易程度:该界面设计是否有难度、开发工作量有多大。
设计详细的交互
人机交互的设计有若干准则,包括以下内容:
(1) 一致性。采用一致的术语、一致的步骤和一致的活动。
(2) 操作步骤少。使击键或点击鼠标的次数减到最少,甚至要减少做某些事所需的下拉菜单的距离。
(3) 不要“哑播放”。
(4) 提供Undo功能。
(5) 减少人脑的记忆负担。不应该要求人从一个窗口中记住某些信息,然后在另一个窗口中使用。
(6) 提高学习效率。为高级特性提供联机帮助,以便用户在需要时容易找到。
4.5数据设计
文件设计
以下几种情况适合于选择文件存储。
(1) 数据量较大的非结构化数据,如多媒体信息。
(2) 数据量大,信息松散,如历史记录、档案文件等。
(3) 非关系层次化数据,如系统配置文件。
(4) 对数据的存取速度要求极高的情况。
(5) 临时存放的数据。
一般要根据文件的特性,来确定文件的组织方式。
(1) 顺序文件:这类文件分两种,一种是连续文件,另一种是串联文件。
(2) 直接存取文件:可根据记录关键字的值,通过计算直接得到记录的存放地址。
(3) 索引顺序文件:其基本数据记录按顺序文件组织,记录排列顺序必须按关键字值升序或降序安排,且具有索引部分,索引部分也按同一关键字进行索引。
(4) 分区文件:这类文件主要用于存放程序。它由若干称为成员的顺序组织的记录组和索引组成。每一个成员就是一个程序,由于各个程序的长度不同,所以各个成员的大小也不同,需要利用索引给出各个成员的程序名、开始存放位置和长度。
(5) 虚拟存储文件:这是基于操作系统的请求页式存储管理功能而建立的索引顺序文件。
数据库设计
根据数据库的组织,可以将数据库分为网状数据库、层次数据库、关系数据库、面向对象数据库、文档数据库、多维数据库等。
关系数据库最成熟,应用也最广泛,一般情况下,大多数设计者都会选择关系数据库。
在结构化设计方法中,很容易将结构化分析阶段建立的实体—关系模型映射到关系数据库中。
数据对象实体的映射
一个数据对象(实体)可以映射为一个表或多个表,当分解为多个表时,可以采用横切和竖切的方法。
竖切常用于实例较少而属性很多的对象。通常将经常使用的属性放在主表中,而将其他一些次要的属性放到其他表中。
横切常常用于记录与时间相关的对象。往往在主表中只记录最近的对象,而将以前的记录转到对应的历史表中。
关系的映射
一对一关系的映射:可以在两个表中都引入外键,进行双向导航。也可以将两个数据对象组合成一张单独的表。
一对多关系的映射:可以将关联中的“一”端毫无变化地映射到一张表,将关联中表示“多”的端上的数据对象映射到带有外键的另一张表,使外键满足关系引用的完整性。
多对多关系的映射:为了表示多对多关系,关系模型必须引入一个关联表,将两个数据实体之间的多对多关系转换成两个一对多关系。
4.6过程设计
表达过程规格说明的工具称为过程描述工具,可以将过程描述工具分为以下3类。
(1) 图形工具:把过程的细节用图形方式描述出来,如程序流程图、N-S图、PAD图、决策树等。
(2) 表格工具:用一张表来表达过程的细节。这张表列出了各种可能的操作及其相应的条件,即描述了输入、处理和输出信息,如决策表。
(3) 语言工具:用某种类高级语言(称为伪代码)来描述过程的细节,如很多数据结构教材中使用类Pascal、类C语言来描述算法。
结构化程序设计
最早由E. W. Dijkstra提出;建议从高级语言中取消GOTO语句;1966年,Bohm和Jacopini证明:只用三种基本的控制结构“顺序”、“选择”和“循环”就能实现任何单入口和单出口的没有“死循环”的程序。
概念:如果一个程序的代码块仅仅通过顺序、选择和循环这三种基本控制结构进行连接,并且每个代码块只有一个入口和一个出口,则称这个程序是结构化的。
结构程序设计的主要原则
(1)使用语言中的顺序、选择、重复等有限的基本控制结构表示程序逻辑。
(2)选用的控制结构只准许有一个入口和一个出口。
(3)程序语句组成容易识别的块(Block),每块只有一个入口和一个出口。
(4)复杂结构应该用基本控制结构进行组合嵌套来实现。
(5)语言中没有的控制结构,可用一段等价的程序段模拟, 但要求该程序段在整个系统中应前后一致。
(6) 严格控制GOTO语句,仅在下列情形才可使用:
用非结构化的程序设计语言去实现结构化的构造。
若不使用GOTO语句就会使程序功能模糊。
在某种可以改善而不是损害程序可读性的情况下。例如,在查找结束时,文件访问结束时,出现错误情况要从循环中转出时,使用布尔变量和条件结构来实现就不如用GOTO语句来得简洁易懂。
(7) 在程序设计过程中,尽量采用自顶向下(Top-Down)、逐步细化(Stepwise Refinement)的原则,由粗到细,一步步展开。
程序流程图
程序流程图也称为程序框图,是软件开发者最熟悉的算法表达工具。
早期的流程图也存在一些缺点。特别是表示程序控制流程的箭头,使用的灵活性极大,程序员可以不受任何约束,随意转移控制,这将不符合结构化程序设计的思想。
为使用流程图描述结构化程序,必须对流程图加以限制。
程序流程图的基本控制结构
(1) 顺序型:几个连续的加工步骤依次排列构成。
(2) 选择型:由某个逻辑判断式的取值决定选择两个加工中的一个。
(3) 先判定(while)型循环:在循环控制条件成立时,重复执行特定的加工。
(4) 后判定(until)型循环:重复执行某些特定的加工,直至控制条件成立。
(5) 多情况(case)型选择:列举多种加工情况,根据控制变量的取值,选择执行其一。
基本控制结构
符号
循环标准符号
注释符号使用
多选择判断
N-S图
Nassi和Shneiderman 提出了一种符合结构化程序设计原则的图形描述工具,叫做盒图 (box-diagram),也叫做N-S图。
在N-S图中,为了表示5种基本控制结构,规定了5种图形构件。
PAD图
PAD(problem analysis diagram)是日本日立公司提出,由程序流程图演化来的,用结构化程序设计思想表现程序逻辑结构的图形工具。
PAD也设置了5种基本控制结构的图式,并允许递归使用。
伪代码
伪代码是一种介于自然语言和形式化语言之间的半形式化语言,是一种用于描述功能模块的算法设计和加工细节的语言,也称为程序设计语言(Program Design Language,PDL)。
伪码的语法规则分为“外语法”和“内语法”。
外语法应当符合一般程序设计语言常用语句的语法规则;
内语法可以用英语中一些简单的句子、短语和通用的数学符号来描述程序应执行的功能。
简单陈述句结构:避免复合语句。
判定结构:IF_THEN_ELSE或CASE_OF结构。
重复结构:WHILE_DO或REPEAT_UNTIL结构。
(1) 有固定的关键字外语法,提供全部结构化控制结构、数据说明和模块特征。外语法的关键字是有限的词汇集,它们能对伪代码正文进行结构分割,使之变得易于理解。
(2) 内语法使用自然语言来描述处理特性,为开发者提供方便,提高可读性。
(3) 有数据说明机制,包括简单的(如标量和数组)与复杂的(如链表和层次结构)的数据结构。
(4) 有子程序定义与调用机制,用以表达各种方式的接口说明。
自顶向下、逐步细化的设计过程
主要包括两个方面:
一是将复杂问题的解法分解和细化成由若干个模块组成的层次结构;
二是将每个模块的功能逐步分解细化为一系列的处理。
在处理较大的复杂任务时,常采取“模块化”的方法,即在程序设计时不是将全部内容都放在同一个模块中,而是分成若干个模块,每个模块实现一个功能。
模块分解完成后,下一步的任务就是将每个模块的功能逐步分解细化为一系列的处理。
在概要设计阶段,我们已经采用自顶向下、逐步细化的方法,把复杂问题的解法分解和细化成了由许多功能模块组成的层次结构的软件系统。
在详细设计和编码阶段,我们还应当采取自顶向下、逐步求精的方法,把模块的功能逐步分解,细化为一系列具体的步骤,进而翻译成一系列用某种程序设计语言写成的程序。
自顶向下、逐步细化方法举例
用筛选法求100以内的素数。所谓的筛选法,就是从2到100中去掉2,3,5,7的倍数,剩下的就是100以内的素数。
1.首先按程序功能写出一个框架
1 |
|
2.上述框架中每一个加工语句都可进一步细化成一个循环语句
1 |
|
自顶向下、逐步求精的方法的优点
(1) 自顶向下、逐步求精方法符合人们解决复杂问题的普遍规律。可提高软件开发的成功率和生产率。
(2) 用先全局后局部,先整体后细节,先抽象后具体的逐步求精的过程开发出来的程序具有清晰的层次结构,因此程序容易阅读和理解。
(3) 程序自顶向下、逐步细化,分解成树形结构。 在同一层的结点上做细化工作,相互之间没有关系,因此它们之间的细化工作相互独立。在任何一步发生错误,一般只影响它下层的结点,同一层的其他结点不受影响。
(4) 程序清晰和模块化,使得在修改和重新设计一个软件时,可复用的代码量最大。
(5) 程序的逻辑结构清晰,有利于程序正确性证明。
(6) 每一步工作仅在上层结点的基础上做不多的设计扩展,便于检查。
(7) 有利于设计的分工和组织工作。
4.7软件设计规格说明
国家标准GB/T 8567—2006《计算机软件文档编制规范》中有关软件总体设计的文档是《系统/子系统设计(结构设计)说明(SSDD)》, 描述了系统或子系统的系统级或子系统级设计与体系结构设计。
4.8软件设计评审
Part3.面向对象分析与设计方法
5.面向对象方法与UML
5.1面向对象的概念与开发方法
面向对象的定义:面向对象=对象+类+继承+消息通信
如果一个系统是使用这样4个概念设计和实现的,则可认为这个系统是面向对象的。
1.对象
对象是包含现实世界物体特征的抽象实体,它反映了系统为之保存信息和(或)与它交互的能力。
例如,Student对象的数据可能有姓名、性别、出生日期、家庭住址、电话号码等,其操作可能是对这些数据值的赋值及更改。
对象名有下列三种表示格式:
(1) 第一种格式是对象名在前,类名在后,中间用冒号连接。形如:
对象名:类名
(2) 第二种格式形如:
:类名
这种格式用于尚未给对象命名的情况,注意,类名前的冒号不能省略。
(3) 第三种格式形如:
对象名
对象有两个层次的概念:
(1) 现实生活中对象指的是客观世界的实体。可以是可见的有形对象,如人、学生、汽车、房屋等;也可以是抽象的逻辑对象,如银行帐号,生日。
(2) 程序中对象就是一组变量和相关方法的集合,其中变量表明对象的状态,方法表明对象所具有的行为。
可以将程序中的对象分为5类:物理对象,角色,事件,交互,规格说明。
物理对象(Physical Objects)── 物理对象是最易识别的对象,通常可以在问题领域的描述中找到,它们的属性可以标识和测量。
例如,大学课程注册系统中的学生对象;一个网络管理系统中各种网络物理资源对象(如开关、CPU和打印机)都是物理对象。
角色(Roles)── 一个实体的角色也可以抽象成一个单独的对象。角色对象的操作是由角色提供的技能。
例如,一个退休教师同时扮演退休者和教师的角色。
事件(Events)── 一个事件是某种活动的一次“出现”。
例如“鼠标”事件。一个事件对象通常是一个数据实体,它管理“出现”的重要信息。事件对象的操作主要用于对数据的存取。
交互(Interactions)── 交互表示了在两个对象之间的关系,这种类型的对象类似于在数据库设计时所涉及的“关系”实体。当实体之间是多对多的关系时,利用交互对象可将其简化为两个一对多的关系。
例如,在大学课程注册系统中,学生和课程之间的关系是多对多的关系,可设置一个“选课”交互对象来简化它们之间的关系。
规格说明(specification)规格说明对象表名组合某些实体时的要求。
2.类与封装
类
可以将现实生活中的对象经过抽象,映射为程序中的对象。对象在程序中是通过一种抽象数据类型来描述的,这种抽象数据类型称为类(Class)。
为了让计算机创建对象,必须先提供对象的定义,也就是先定义对象所属的类。例如,可以将学生对象所属的类定义为Student。类的图形表示如图所示。
封装
面向对象的封装特性与其抽象特性密切相关。封装是一种信息隐蔽技术,就是利用抽象数据类型将数据和基于数据的操作封装在一起。用户只能看到对象的封装界面信息,对象的内部细节对用户是隐蔽的。
封装的定义是:
(1) 清楚的边界,所有对象的内部信息被限定在这个边界内;
(2) 接口,即对象向外界提供的方法,外界可以通过这些方法与对象进行交互;
(3) 受保护的内部实现,即软件对象功能的实现细节,实现细节不能从类外访问。
3.继承
继承是一种联结类的层次模型,为类的重用提供了方便,它提供了明确表述不同类之间共性的方法。
4.多态
根据为请求提供服务的对象不同可以得到不同的行为,这种现象称为多态。通过在子类中覆盖父类的方法实现多态。
5.消息通信
消息是一个对象与另一个对象的通信单元,是要求某个对象执行类中定义的某个操作的规格说明。
发送给一个对象的消息定义了一个方法名和一个参数表(可能是空的),并指定某一个对象。
一个对象接收到消息,则调用消息中指定的方法,并将形式参数与参数表中相应的值结合起来。
6.面向对象软件开发方法
方法的唯一性:即方法是对软件开发过程所有阶段进行综合考虑而得到的。
从生存期的一个阶段到下一个阶段的高度连续性,即生存期后一阶段的成果只是在前一阶段成果的补充和修改。
将面向对象分析(OOA)、面向对象设计(OOD)和面向对象程序设计(OOP)集成到生存期的相应阶段。
Booch方法
包含“微开发过程”和“宏开发过程”两个过程。OOA 宏观开发过程如下:
标识类和对象;标识类和对象的语义;标识类和对象间的关系;进行一系列精化;实现类和对象。
Rumbaugh方法
Rumbaugh和他的同事提出的对象模型化技术(OMT)用于分析、系统设计和对象级设计。分析活动建立三个模型:
对象模型(描述对象、类、层次和关系);动态模型(描述对象和系统的行为);功能模型(类似于高层的DFD,描述穿越系统的信息流)。
Coad和Yourdon方法
Coad和Yourdon方法常常被认为是最容易学习的OOA方法。建模符号相当简单,其OOA过程如下:
(1) 使用“要找什么”准则标识对象;
(2) 定义对象之间的一般化/特殊化结构(又称为分类结构);
(3) 定义对象之间的整体/部分结构(又称为组合结构);
(4) 标识主题;
(5) 定义对象的属性及对象之间的实例连接;
(6) 定义服务及对象之间的消息连接。
Jacobson方法
也称为OOSE(面向对象软件工程),其特点是特别强调使用用例——用以描述用户和产品或系统间如何交互的场景。过程如下:
标识系统的用户和他们的整体责任;构造需求模型;构造分析模型 。
5.2UML简介
统一建模语言(UML,Unified Modeling Language);
它将Booch、Rumbaugh和Jacobson等各自独立的OOA和OOD方法中最优秀的特色组合成一个统一的方法。
1.UML特点
- 统一标准
- 面向对象
- 可视化,表达能力强大
- 独立于过程
- 容易掌握使用
- 与编程语言的关系
2.UML基本模型
UML符号为开发者或开发工具使用这些图形符号和文本语法为系统建模提供了标准。这些图形符号和文字所表达的是应用级的模型,在语义上它是UML元模型的实例。UML模型由事物、关系和图组成 。
5.3UML事物
事物是对模型中最具代表性成分的抽象,在UML中,可以分为结构事物、行为事物、分组事物和注释事物4类。
1.结构事物
结构事物是UML模型的静态部分,主要用来描述概念的或物理的元素,包括类、主动类、接口、对象、用例、参与者、协作、构件和节点等。
类(class)── 类用带有类名、属性和操作的矩形框来表示。
主动类(active class)── 主动类的实例应具有一个或多个进程或线程,能够启动控制活动。
接口(interface)── 描述了一个类或构件的一组外部可用的服务(操作)集。
接口定义的是一组操作的描述,而不是操作的实现。 一般将接口画成从实现它的类或构件引出的圆圈,接口体现了使用与实现分离的原则。
对象(object)── 对象是类的实例,其名字下边加下划线,对象的属性值需明确给出。
用例(use case)── 也称用况,用于表示系统想要实现的行为,即描述一组动作序列(即场景)。而系统执行这组动作后将产生一个对特定参与者有价值的结果。
参与者(actor)── 也称角色,是指与系统有信息交互关系的人、软件系统或硬件设备,在图形上用简化的小木头人表示。
协作(collaboration)── 用例仅描述要实现的行为,不描述这些行为的实现。这种实现用协作描述。
协作定义交互,描述一组角色实体和其他实体如何通过协同工作来完成一个功能或行为。类可以参与几个协作。
构件(component)── 也称组件,是系统中物理的、可替代的部件。它通常是描述一些逻辑元素的物理包。
节点(node)── 是在运行时存在的物理元素。它代表一种可计算的资源,通常具有一定的记忆能力和处理能力。
2.行为事物
行为事物是UML模型的动态部分,包括两类:
交互(interaction)── 交互由在特定的上下文环境中共同完成一定任务的一组对象之间传递的消息组成。如图所示。交互涉及的元素包括消息、动作序列(由一个消息所引起的行为)和链(对象间的连接)。
状态机(state machine)── 描述了一个对象或一个交互在生存周期内响应事件所经历的状态序列,单个类或者一组类之间协作的行为都可以用状态机来描述。状态机涉及到状态、变迁和活动,其中状态用圆角矩形来表示。
3.分组事物
分组事物是UML模型的组织部分。它的作用是为了降低模型复杂性。
UML中的分组事物是包(package)。
包是把模型元素组织成组的机制,结构事物、行为事物甚至其他分组事物都可以放进包内。
4.注释事物
注释事物是UML模型的解释部分,它们用来描述和标注模型的任何元素。
通常可以用注释修饰带有约束或者解释的图。
5.4UML关系
1.依赖关系
依赖(Dependency)是两个事物之间的语义关系,其中一个事物发生变化会影响到另一个事物的语义,它用一个虚线箭头表示。
虚线箭头的方向从源事物指向目标事物,表示源事物依赖于目标事物。
CourseSchedule依赖Course
2.关联关系
关联(association)是一种结构关系,它描述了两个或多个类的实例之间的连接关系,是一种特殊的依赖。
普通关联
普通关联是最常见的关联关系,只要类与类之间存在连接关系就可以用普通关联表示。普通关联又分为二元关联和多元关联。
二元关联描述两个类之间的关联,用两个类之间的一条直线来表示,直线上可写上关联名,并用实心三角指示关联名指的是哪一个方向。如果关联含义清晰的话可以不写。如图先生类和学徒类之间的关联,该关联表明一位先生教授多名学徒,这些学徒受教于一位先生。
关联与两端的类连接的地方叫关联端点,在关联两端连接的类各自充当了某种角色,有关的信息(角色名、可见性、多重性等)可附加到各个端点上。
多重性(multiplicity):多重性表明在一个关联的两端连接的类实例个数的对应关系,即一端的类的多少个实例对象可以与另一端的类的一个实例相关。如果图中没有明确标出关联的多重性,则默认的多重性为1
角色:关联端点上还可以附加角色名,表示类的实例在这个关联中扮演的角色(驾驶员、运输车)。
UML还允许一个类与它自身关联。航班与乘务组是多对多的关联,乘务长与乘务员是1对多的关联,乘务长与乘务员之间存在管理关系。
多元关联
多元关联是指3个或3个以上类之间的关联。
多元关联由一个菱形,以及由菱形引出的通向各个相关类的直线组成,关联名可标在菱形的旁边,在关联的端点也可以标上多重性等信息。
图表示哪个程序员用哪种程序语言开发了哪个项目
限定关联
限定关联通常用在一对多或多对多的关联关系中,可以把模型中的多重性从一对多变成一对一,或将多对多简化成多对一。
在类图中把限定词(qualifier)放在关联关系末端的一个小方框内。
例如,某操作系统中一个目录下有许多文件,一个文件仅属于一个目录,在一个目录内文件名确定了唯一的一个文件。利用限定词“文件名”表示了目录与文件之间的关系,这样就利用限定词把一对多关系简化成了一对一关系。注意,限定词“文件名”应该放在靠近目录的那一端。
关联类
在关联关系比较简单的情况下,关联关系的语义用关联关系的名字来概括。但在某些情况下,需要对关联关系的语义做详细的定义、存储和访问,为此可以建立关联类(association class),用来描述关联的属性。关联中的每个链与关联类的一个实例相联系。关联类通过一条虚线与关联连接。
如图是一个公司类与属下一个或多个员工之间的关联,通过关联类给出关联“job”的细节。
聚合
聚合(Aggregation)也称为聚集,是一种特殊的关联。它描述了整体和部分之间的结构关系。
两种特殊的聚合关系:共享聚合(shared aggregation)和复合聚合(composition aggregation)。
如果在聚合关系中处于部分方的实例可同时参与多个处于整体方实例的构成,则该聚合称为共享聚合。
如果部分类完全隶属于整体类,部分类需要与整体类共存,一旦整体类不存在了,则部分类也会随之消失,或失去存在价值,则这种聚合称为复合聚合。
导航
导航(navigability)是关联关系的一种特性,它通过在关联的一个端点上加箭头来表示导航的方向。导航主要在设计阶段使用,当关联具有双向可导航性时,可以省略指示导航方向的箭头,此时隐指双向可导航。
在图a所示的关联中,课程与学生之间是多对多的关系,这个关联的链由一组 (课程实例,学生实例)对组成的元组组成。如果想知道某门课程有哪些学生选修,或某个学生选修了哪些课程,就需遍历该链的所有元组。UML通过在关联端点加一个箭头来表示导航,导航能从该链的所有元组中得到给定的元组。
例如,图b给出的学生和课程之间的导航表明,当指定一门课程时,就能直接导航出选修这门课程的所有学生,不用遍历全部元组,但当指定一个学生时,不能直接导航出该学生选修的所有课程,只能通过遍历全部元组才能得到结果。这种导航是单向的。
同样,图c给出了学生到课程的(单向)导航,即当指定一个学生时就能直接导航出该学生所选的所有课程。
图d则表示学生与课程之间的导航是双向。
3.泛化关系
•泛化(generalization)关系就是一般类和特殊类之间的继承关系。
•在UML中,一般类亦称泛化类,特殊类亦称特化类。
•泛化针对类型而不针对实例,因为一个类可以继承另一个类,但一个对象不能继承另一个对象。
普通泛化
普通泛化与前面讲过的继承基本相同。但在泛化关系中常遇到抽象类。一般称没有具体对象的类为抽象类。抽象类通常作为父类,用于描述其他类(子类)的公共属性和行为。
在图形上,抽象类的类名下附加一个标签值{abstract},如图所示。下方的两个折角矩形是注释,分别说明了两个子类的drive操作功能。抽象类中的操作用于指定它的所有具体子类应具有的行为。这些操作在每个具体子类中有具体的实现。每一个具体子类可创建自己的实例。
•普通泛化可以分为多重继承和单继承。多重继承是指一个子类可同时继承多个上层父类。
受限泛化
•受限泛化关系是指泛化具有约束条件。
一般有4种约束:交叠(overlapping)、不相交(disjoint)、完全(complete)和不完全(incomplete)
4.实现关系
•实现(implement)是泛化关系和依赖关系的结合,也是类之间的语义关系,通常在以下两种情况出现实现关系:
(1)接口和实现它们的类或构件之间;
(2)用例和实现它们的协作之间。
图用TV类和Radio类来实现接口ElectrialEquipment中规定的所有动作的情形
5.5UML的图
这位更是重量级,分两大类:结构图、行为图
1.用例图
用例模型描述的是参与者(actor)所理解的系统功能。用例模型用于需求分析阶段,它的建立是系统开发者和用户反复讨论的结果,描述了开发者和用户对需求规格达成的共识。在UML中,一个用例模型由若干个用例图来描述,用例图的主要元素是用例和执行者。用例图是包括执行者、由系统边界(一个矩形)封闭的一组用例,执行者和用例之间的关联、用例间关系以及执行者的泛化的图。
用例之间可以有泛化、扩展、使用(包含)三种关系。
- 泛化关系:用例泛化是指一个用例可以被特别列举为一个或多个子用例。
- 扩展关系:向一个用例中加入一些新的动作后构成了另一个用例,这两个用例之间的关系就是扩展关系,后者通过继承前者的一些行为得来,通常把后者称为扩展用例。
- 使用(包含)关系:当一个用例使用另一个用例时,这两个用例之间就构成了使用关系。当有一大块相似的动作存在于几个用例,又不想重复描述该动作,将重复的部分分离为一个用例,两用例间关系称为使用关系。
2.类图
类图描述类和类与类之间的静态关系,它是从静态角度表示系统的,因此类图属于一种静态模型。类图是构建其他图的基础,没有类图就没有状态图、协作图等其他图,也就无法表示系统其他方面的特性。
类图显示了类(及其接口)、类的内部结构以及与其他类的联系。联系是指类元之间的联系,在类的建模中可以使用关联、聚合和泛化(继承)关系。
关联类是指表示其他类之间关联关系的类。当一个关联具有自己的属性并需要存储它们时,就需要用关联类建模。关联类用虚线连接在两个类之间的联系上。
3.顺序图和通信图
•通信图是顺序图的一种变化形式,用于描述相互协作的对象间的交互关系和链接关系。
4.状态图
状态图描述一个特定对象的所有可能的状态以及引起状态转换的事件。大多数面向对象技术都用状态图表示单个对象在其生命期中的行为。一个状态图包括一系列状态、事件以及状态之间的转移。
•所有对象都具有状态,状态是对象执行了一系列活动的结果。当某个事件发生后,对象的状态将发生变化。在状态图中定义的状态可能有:初态(初始状态)、终态(最终状态)、中间状态和复合状态。
•在一张状态图中只能有一个初态,而终态则可以有多个。
中间状态用圆角矩形表示,可能包含三个部分,第一部分为状态的名称;第二部分为状态变量的名字和值,这部分是可选的;第三部分是活动表,这部分也是可选的。
中间状态 电梯的状态图(状态图没有终点) 带有事件说明的状态转换5.活动图
活动图用来捕捉用例的活动,使用框图的方式显示动作及其结果。
活动图是一个流图,描述了从活动到活动的流。
它是另一种描述交互的方式,它描述采取何种动作,动作的结果是什么(动作状态改变),何时发生(动作序列),以及在何处发生(泳道)。
6.构件图和部署图
构件图描述软件构件及构件之间的依赖关系,显示代码的静态结构。
构件是逻辑架构中定义的概念和功能(例如,类、对象及它们之间的关系)在物理架构中的实现。典型情况下,构件是开发环境中的实现文件。
软件构件可以是下述的任何一种构件。
•源构件:源构件仅在编译时才有意义。典型情况下,它是实现一个或多个类的源代码文件。
•二进制构件:典型情况下,二进制构件是对象代码,它是源构件的编译结果。
•可执行构件:可执行构件是一个可执行的程序文件,它是链接所有二进制构件所得到的结果。一个可执行构件代表在处理器(计算机)上运行的可执行单元。
画图系统的构件图
银行储蓄系统的构件图
•部署图描述处理器、设备和连接,它显示系统硬件的物理拓扑结构及在此结构上执行的软件。
•部署图可以显示计算节点的拓扑结构和通信路径、节点上运行的软件以及软件包含的逻辑单元。
?
•UML中有两种类型的交互图:顺序图和协作图。
•顺序图描述对象之间的动态交互关系,着重表现对象间消息传递的时间顺序。顺序图中的符号如下:
UML定义的三种消息:
简单消息:表示简单的控制流,它只是表示控制从一个对象传给另一个对象,而没有描述通信的任何细节。
同步消息:表示嵌套的控制流,操作的调用是一种典型的同步消息。调用者发出消息后必须等待消息返回,只有当处理消息的操作执行完毕后,调用者才可以继续执行自己的操作。
异步消息:表示异步控制流,发送者发出消息后不用等待消息处理完就可以继续执行自己的操作。异步消息主要用于描述实时系统中的并发行为
5.6使用和扩展UML
使用UML的准则
1.不要试图使用所有的图形和符号
应该根据项目的特点,选用最适用的图形和符号。一般来说,应该优先选用简单的图形和符号,例如,用例、类、关联、属性和继承等概念是最常用的。
2.不要为每个事物都画一个模型
应该把精力集中于关键的领域。最好只画几张关键的图,经常使用并不断更新、修改这几张图。
3.应该分层次地画模型图
根据项目进展的不同阶段,用正确的观点画模型图。如果处于分析阶段,应该画概念层模型图;当开始着手进行软件设计时,应该画设计层模型图;当考察某个特定的实现方案时,则应画实现层模型图。
使用UML的最大危险是过早地陷入实现细节。为了避免这一危险,应该把重点放在概念层和说明层。
4.模型应该具有协调性
模型必须在每个抽象层次内和不同的抽象层次之间协调
5.模型和模型元素的大小应该适中
过于复杂的模型和模型元素难于理解也难于使用,这样的模型和模型元素很难生存下去。如果要建模的问题相当复杂,则可以把该问题分解成若干个子问题,分别为每个子问题建模,每个子模型构成原模型中的一个包,以降低建模的难度和模型的复杂性。
扩展UML的机制
•为避免使UML变得过于复杂,UML并没有吸收所有面向对象的建模技术和机制,而是设计了适当的扩展机制,使得它能很容易地适应某些特定的方法、机构或用户的需要。利用扩展机制,用户可以定义和使用自己的模型元素。
•扩展的基础是UML的模型元素,利用扩展机制可以给这些元素的变形加上新的语义。新语义可以有三种形式:重新定义,增加新语义或者对某种元素的使用增加一些限制。相应地,有下述三种扩展机制。
构造型 (stereotype)
构造型是在一个已定义的模型元素的基础上构造的一种新的模型元素。构造型的信息内容和形式与已存在的基本模型元素相同,但是含义和使用不同。
标记值 (tagged vaue)
标记值可以用来存储元素的任意信息,对于存储项目管理信息尤其有用的,如元素的创建日期、开发状态、截止日期和测试状态。
标记值用字符串表示,字符串有标记名、等号和值。它们被规则地放置在大括弧内。
约束 (constraint)
约束是用文字表达式表示的语义限制。约束用大括弧内的字符串表达式表示。约束可以附加在表元素、依赖关系,或注释上。
6.面向对象分析
6.1面向对象分析概述
面向对象分析就是抽取和整理用户需求并建立问题域精确模型的过程。
1.确定系统边界
系统边界是系统的所有内部成分与系统以外各种事物的分界线。系统只通过边界上有限数量的接口与外部的系统参与者(人员、组织、设备或外系统)进行交互。
2.三种模型
用例模型:用例和场景表示的功能模型;
对象模型:用类和对象表示的静态模型;
交互模型:由状态图和顺序图表示的动态模型。
用例模型从用户角度描述系统功能,是整个后续工作的基础,也是测试与验收的依据 ;对象模型是核心模型,解决任何问题,几乎都需要从客观世界实体及实体间相互联系中抽象出极有价值的对象模型;当问题涉及交互作用和时序时,交互模型是重要的。
6.2建立用例模型
建立用例模型的过程
确定业务参与者──标识目标系统将支持的不同类型的用户,可以是人、事件或其他系统。
通过关注系统的业务参与者,我们可以将重点放在如何使用系统,而不是如何构造系统上,并且有助于进一步明确系统的范围和边界。
当系统比较庞大和复杂时,要搞清楚系统的需求往往比较困难,通过明确参与者,可以针对参与者确定系统需求,有助于保证系统需求的完整性
从以下三个方面识别参与者:
人员或组织:直接使用系统的人员或组织
外部系统:所有与本系统交互的外部系统
设备:所有与系统交互的设备
可通过以下资料来确定系统的参与者:
标识系统范围和边界的环境图;
现有系统(如果有的话)的文档和用户手册;
项目会议和研讨会的记录;
现有的需求文档、工作手册等。
•环境图是分析参与者和发现潜在用例的极好来源,它不仅可以用在结构化分析方法中,也可以用于面向对象的分析方法中。
•通过环境图,可以确定系统的主要输入输出,通过提交和接收输入输出的各方确定潜在的用例。
•对用例的完整描述包括用例名称、执行者、前置条件、后置条件、一个主事件流、零到多个备选事件流。
•主事件流表示正常情况下执行者与系统之间的信息交互及动作序列,备选事件流则表示特殊情况或异常情况下的信息交互及动作序列。
•应给出每个用例的规格说明。
•用例图是若干个参与者和用例,以及它们间的关系构成的图形表示。
•每个系统通常都有一个总体视图(Global View of Actors and Use Cases),如果总体视图过于复杂,则可以创建多个用例图,每个用例图关注系统的某一方面。
•通常是围绕参与者创建用例图。
确定业务需求用例──参与者需要系统提供的完整功能。
环境图是分析参与者和发现潜在用例的极好来源,它不仅可以用在结构化分析方法中,也可以用于面向对象的分析方法中。
通过环境图,可以确定系统的主要输入输出,通过提交和接收输入输出的各方确定潜在的用例。
创建用例图──标识参与者与用例之间、用例与用例之间的关系。
用例图是若干个参与者和用例,以及它们间的关系构成的图形表示。
每个系统通常都有一个总体视图(Global View of Actors and Use Cases),如果总体视图过于复杂,则可以创建多个用例图,每个用例图关注系统的某一方面。
通常是围绕参与者创建用例图。
6.3建立对象模型
在系统分析阶段,对象建模的主要任务是建立问题域的概念模型。
这个模型描述了现实世界中的“类与对象”以及它们之间的关系。
在UML中,通过建立类图来表示对象模型。
对象模型的5个层次
Coad & Yourdon提出,复杂问题(大型系统)的对象模型应该由下述5个层次组成:主题层(也称为范畴层)、类-对象层、结构层、属性层和服务层,如图所示。
划分主题
在开发大型、复杂系统的过程中,为了降低复杂程度,人们习惯于把系统再进一步划分成几个不同的主题。
应该按问题领域而不是用功能分解方法来确定主题。此外,应该按照使不同主题内的对象相互间依赖和交互最少的原则来确定主题。
主题可以采用UML中的包来展现。
确定类与对象
1.找出候选的类与对象
类与对象是对问题域中有意义的事物的抽象,它们既可能是可见的物理实体,也可能是抽象的概念。我们可以将客观事物分为以下五类:
可感知的物理实体,如教学楼、教室等。
人或组织的角色,如教师、计算机系等。
应该记忆的事件,如演出、交通事故等。
两个或多个对象的相互作用,通常带有交易或接触的性质,如购买、教学等。
需要说明的概念,如保险法、政策等。
另一种更简单的非正式分析方法,是以自然语言书写的需求陈述为依据,把陈述中的名词作为类与对象的候选者,用形容词作为确定属性的线索,把动词作为服务(操作)的候选者。
例如,在选课系统中,可以初步确定Teacher(教师)、Student(学生)、Course(课程)、CourseTask(课程任务,指一门课程划分为多个任务)、StudentList(学生名册)、ScoreReport(成绩单)等类与对象。
2.筛选出正确的类与对象
严格考察每个候选对象,从中去掉不正确的或不必要的类与对象,仅保留确实应该记录其信息或需要其提供服务的那些类与对象。
3.区分实体类、边界类和控制类
在类分析时首先从问题域的实体类入手,如果在建立分析对象模型时区分实体类、边界类和控制类,将有助于理解系统。
实体类表示系统将跟踪的持久信息;边界类表示参与者与系统之间的交互;控制类负责用例的实现。其图形表示如图所示。
确定结构
1.确定泛化(继承)关系
学习当前领域的分类学知识。领域分类法往往比较正确地反映了事物的特征、类别以及各种概念的一般性与特殊性。
按常识考虑事物的分类。如果问题域没有可供参考的分类方法,可以按自己的常识,从各种不同的角度考虑事物的分类,从而发现泛化(继承)关系。
考察类的属性与操作,“自上而下”地从一般类发现特殊类。
“自下而上”地从特殊类抽取出一般类
2.确定关联
标识关联的启发式准则如下:
(1) 识别各类对象之间的静态关系
(2) 识别关联的属性与操作
(3) 分析关联的多重性
(4) 进一步分析关联的性质
确定属性
应该仅考虑与具体应用直接相关的属性,不要考虑那些超出所要解决的问题范围的属性。
在分析过程中应该首先找出最重要的属性,以后再逐渐把其余属性增添进去。
在分析阶段不要考虑那些纯粹用于实现的属性。
标识属性的启发性准则如下:
(1) 每个对象至少需包含一个属性,例如_id。
(2) 属性取值必需适合对象类的所有实例。例如,属性“会飞”并不属于所有的鸟,有的鸟不会飞,因此可以建立鸟的泛化结构,把不同的鸟划分到“会飞的鸟”和“不会飞的鸟”两个子类中。
(3) 出现在泛化关系中的对象所继承的属性必须与泛化关系一致。
(4) 系统的所有存储数据必须定义为属性;
(5) 对象的导出属性应当略去。例如,“年龄”是由属性“出生日期”导出,它不能作为基本属性存在。
(6) 在分析阶段,如果某属性描述了对象的外部不可见状态,应将该属性从分析模型中删去。
选课系统中类与对象的属性
确定服务
6.4建立动态模型
顺序图
用例图中的事件流是由文本表示的,事件流描述的是用例实现的过程,也称为场景(scenarios),可以用顺序图表示场景。
顺序图按照时间顺序显示对象之间的交互关系。它描述场景中的对象和类以及在完成场景中定义的功能时对象间要交换的信息。
通信图
协作图也称通信图,是顺序图的另一种表示形式,用于描述相互协作的对象间的交互关系和链接关系。
一般情况下,当表示涉及很多对象的模型时,协作图比顺序图更形象。
另外,与顺序图不同,对象之间的实线可能表明这些对象的类之间需要关联。
状态图
状态图由对象的各个状态和连接这些状态的转换组成。
通常,用一张状态图描绘一类对象的行为,它确定了由事件序列引出的状态序列。
不是任何一个类都需要有一张状态图描绘它的行为,只针对具有明显的状态特征并且具有比较复杂的状态—事件—响应行为的类,才需要画状态图。
在选课系统中,CourseTask类的对象具有比较明显的状态特征,其状态有:初始状态、可选状态、人满状态、关闭状态。
7.软件体系结构与设计模式
7.1软件体系结构的基本概念
1.什么是体系结构
一个程序或计算机系统的软件体系结构是指系统的一个或者多个结构。结构中包括软件的构件、构件的外部可见属性以及它们之间的相互关系。外部可见属性则是指软件构件提供的服务、性能、使用特性、错误处理、共享资源使用等。
这一定义强调在任一体系结构表述中“软件构件”的角色。
软件体系结构是具有一定形式的结构化元素,即构件的集合,包括处理构件、数据构件和连接构件。处理构件负责对数据进行加工,数据构件是被加工的信息,连接构件把体系结构的不同部分组合连接起来。
这一定义注重区分处理构件、数据构件和连接构件。
虽然软件体系结构的定义在变化,但其意图是清晰的。体系结构设计是一系列决策和基本原理的集合,这些决策的目标在于开发高效的软件体系结构。在体系结构设计中所强调的基本原理是系统的可理解性、可维护性和可扩展性。
2.体系结构模式、风格和框架的概念
1.模式
软件设计模式是从软件设计过程中总结出来的,是针对特定问题的解决方案。建筑师C.Alexander对模式给出的经典定义是:每个模式都描述了一个在我们的环境中不断出现的问题及该问题解决方案的核心。在软件系统中,可以将模式划分为以下3类。
(1)体系结构模式(architectural pattern):表达了软件系统的基本结构组织形式或者结构方案,包含了一组预定义的子系统,规定了这些子系统的责任,同时还提供了用于组织和管理这些子系统的规则和向导。典型的体系结构模式如OSI参考模型。
(2)设计模式(design pattern):为软件系统的子系统、构件或者构件之间的关系提供一个精炼之后的解决方案,描述了在特定环境下,用于解决通用软件设计问题的构件以及这些构件相互通信时的各种结构。有代表性的设计模式是Erich Gamma及其同事提出的23种设计模式。
(3)惯用法(idiom):是与编程语言相关的低级模式,描述如何实现构件的某些功能,或者利用编程语言的特性来实现构件内部要素之间的通信功能。
2.风格
风格是带有一种倾向性的模式。同一个问题可以有不同的解决问题的方案或模式,但我们根据经验,通常会强烈倾向于采用特定的模式,这就是风格。
每种风格描述一种系统范畴,该范畴包括:
(1)一组构件(如数据库、计算模块)完成系统需要的某种功能;
(2)一组连接件,它们能使构件间实现“通信”、“合作”和“协调”;
(3)约束,定义构件如何集成为一个系统;
(4)语义模型,它能使设计者通过分析系统的构成成分的性质来理解系统的整体性质。
体系结构风格定义了一个系统家族,即一个体系结构定义一个词汇表和一组约束。词汇表中包含一些构件和连接件类型,而这组约束指出系统是如何将这些构件和连接件组合起来的。体系结构风格反映了领域中众多系统所共有的结构和语义特性,并指导如何将各个模块和子系统有效地组织成一个完整的系统。对体系结构风格的研究和实践为大粒度的软件复用提供了可能。
3.框架
随着应用的发展和完善,某些带有整体性的应用模式被逐渐固定下来,形成特定的框架,包括基本构成元素和关系。框架是特定应用领域问题的体系结构模式,框架定义了基本构成单元和关系后,开发者就可以集中精力解决业务逻辑问题。
在组织形式上,框架是一个待实例化的完整系统,定义了软件系统的元素和关系,创建了基本的模块,定义了涉及功能更改和扩充的插件位置。典型的框架例子有MFC框架和Struts框架。
3.体系结构的重要作用
体系结构的重要作用体现在以下三个方面 :
(1)体系结构的表示有助于风险承担者(项目干系人)进行交流。
(2)体系结构突出了早期设计决策。
(3)软件体系结构是可传递和可复用的模型。
7.2典型的软件体系结构风格
1.数据流风格
当输入数据经过一系列的计算和操作构件的变换形成输出数据时,可以应用这种体系结构。管道/过滤器、批处理序列都属于数据流风格。 管道/过滤器结构如下图所示。
从上图可看出,管道/过滤器结构拥有一组被称为过滤器(filter)的构件,这些构件通过管道(pipe)连接,管道将数据从一个构件传送到下一个构件。每个过滤器独立于其上游和下游的构件而工作,过滤器的设计要针对某种形式的数据输入,并且产生某种特定形式的数据输出。
如果数据流退化成为单线的变换,则称为批处理序列(batch sequential)。这种结构接收一批数据,然后应用一系列连续的构件(过滤器)变换它。
管道/过滤器风格具有以下优点:
(1)使得软构件具有良好的隐蔽性和高内聚、低耦合的特点。
(2)允许设计者将整个系统的输入/输出行为看成是多个过滤器的行为的简单合成。
(3)支持软件复用。只要提供适合在两个过滤器之间传送的数据,任何两个过滤器都可被连接起来。
(4)系统维护和增强系统性能简单。新的过滤器可以添加到现有系统中来;旧的可以被改进的过滤器替换掉。
(5)允许对一些如吞吐量、死锁等属性的分析。
(6)支持并行执行。每个过滤器是作为一个单独的任务完成,因此可与其他任务并行执行。
管道/过滤器风格主要缺点如下:
(1)通常导致进程成为批处理的结构。这是因为虽然过滤器可增量式地处理数据,但它们是独立的,所以设计者必须将每个过滤器看成一个完整的从输入到输出的转换。
(2)不适合处理交互的应用。当需要增量地显示改变时,这个问题尤为严重。
(3)因为在数据传输上没有通用的标准,每个过滤器都增加了解析和合成数据的工作,这样就导致了系统性能下降,并增加了编写过滤器的复杂性。
2.调用—返回风格
1.主程序/子程序体系结构
这种传统的程序结构将功能分解为一个控制层次,其中“主”程序调用一组程序构件,这些程序构件又去调用别的程序构件,如下图所示。这种结构总体上为树状结构,可以在底层存在公共模块。
主程序/子程序体系结构的优点如下:
(1)可以使用自顶向下,逐步分解的方法得到体系结构图,典型的拓扑结构为树状结构。基于定义—使用关系对子程序进行分解,使用过程调用作为程序之间的交互机制。
(2)采用程序设计语言支持的单线程控制。
其主要缺点如下:
(1)子程序的正确性难于判断。需要运用层次推理来判断子程序的正确性,因为子程序的正确性取决于它调用的子程序的正确性。
(2)子系统的结构不清晰。通常可以将多个子程序合成为模块。
2.向对象风格
系统的构件封装了数据和必须应用到该数据上的操作,构件间通过消息传递进行通信与合作。与主程序/子程序的体系结构相比,面向对象风格中的对象交互会复杂一些。面向对象风格与网络应用的需求在分布性、自治性、协作性、演化性等方面具有内在的一致性。
面向对象风格具有以下优点:
(1)因为对象对其他对象隐藏它的表示,所以可以改变一个对象的表示,而不影响其他对象。
(2)设计者可将一些数据存取操作的问题分解成一些交互的代理程序的集合。
其缺点如下:
(1)为了使一个对象和另一个对象通过过程调用等进行交互,必须知道对象的标识。只要一个对象的标识 改变了,就必须修改所有其他明确调用它的对象。
(2)必须修改所有显式调用它的其他对象,并消除由此带来的一些副作用。例如,如果A使用了对象B,C也使用了对象B,那么,C对B的使用所造成的对A的影响可能是料想不到的。
3.层次结构
层次结构的基本结构如下图所示。在这种体系结构中,整个系统被组织成一个分层结构,每一层为上层提供服务,并作为下一层的客户。
这种风格支持基于可增加抽象层的设计。允许将复杂问题分解成一个增量步骤序列的实现。由于每一层最多只影响两层,同时只要给相邻层提供相同的接口,允许每层用不同的方法实现,同样为软件复用提供了强大的支持。
层次结构具有以下优点:
(1)支持基于抽象程度递增的系统设计,使设计者可以把一个复杂系统按递增的步骤进行分解。
(2)支持功能增强,因为每一层至多和相邻的上下层交互,因此,功能的改变最多影响相邻的内外层。
(3)支持复用。只要提供的服务接口定义不变,同一层的不同实现可以交换使用。这样,就可以定义一组标准的接口,从而允许各种不同的实现方法。
其缺点如下:
(1)并不是每个系统都可以很容易地划分为分层的模式,甚至即使一个系统的逻辑结构是层次化的,出于对系 统性能的考虑,系统设计师不得不把一些低级或高级的功能综合起来。
(2)很难找到一个合适的、正确的层次抽象方法。
3.仓库风格
数据库系统、超文本系统和黑板系统都属于仓库风格。在这种风格中,数据仓库(如文件或数据库)位于这种体系结构的中心,其他构件会经常访问该数据仓库,并对仓库中的数据进行增加、修改或删除操作。右图为一个典型的仓库风格的体系结构。
上图中,可把中心存储库变换成“黑板”,黑板构件负责协调信息在客户间的传递,当用户感兴趣的数据发生变化时,它将通知客户软件。黑板系统的组成如下图所示。黑板系统的传统应用是信号处理领域,如语音和模式识别。另一应用是松耦合代理数据共享存取。
7.3特定领域的软件体系结构
特定的应用还需要特定的体系结构模型。这些体系结构模型称为领域相关的体系结构。
有两种领域相关的体系结构模型:类属模型(generic model)和参考模型(reference model)。
1.类属模型
类属模型是从许多实际系统中抽象出来的一般模型,它封装了这些系统的主要特征。
例如,许多图书馆开发了自己的图书馆馆藏/流通系统,若把它们的共同功能抽取出来并创建一个让所有图书馆都认可的系统体系结构模型,这就是类属模型。
类属模型的一个最著名的例子是编译器模型,由这个模型已开发出了数以千计的编译器。
2.参考模型
参考模型源于对应用领域的研究,它描述了一个理想化的包含了系统应具有的所有特征的软件体系结构。
它是更抽象且是描述一大类系统的模型,并且也是对设计者有关某类系统的一般结构的指导。
参考模型的典型例子是开放式系统互联(OSI)参考模型。
以上两种不同类型的模型之间并不存在严格的区别,也可以将类属模型视为参考模型。
区别之一是类属模型可以直接在设计中复用,而参考模型一般是用于领域概念间的交流和对可能的体系结构做出比较。
另外,类属模型通常是经过“自下而上”地对已有系统的抽象,而参考模型是“由上到下”地产生的。
7.4分布式系统结构
在集中式计算技术时代广泛使用的是大型机/小型机计算模型。
20世纪80年代以后,集中式结构逐渐被以PC为主的微机网络所取代。个人计算机和工作站的采用,改变了大型机/小型机计算模型,从而产生了分布式计算模型。
分布式计算模型主要具有以下优点:
(1) 资源共享。分布式系统允许硬件、软件等资源共享使用。
(2) 经济性。
(3) 性能与可扩展性。
(4) 固有分布性。
(5) 健壮性。
1.多处理器体系结构
分布式系统的一个最简单的模型是多处理器系统,系统由许多进程组成,这些进程可以在不同的处理器上并行运行,可以极大地提高系统的性能。
由于大型实时系统对响应时间要求较高,这种模型在大型实时系统中比较常见。大型实时系统需要实时采集信息,并利用采集到的信息进行决策,然后发送信号给执行机构。虽然,信息采集、决策制定和执行控制这些进程可以在同一台处理器上统一调度执行,但使用多处理器能够提高系统性能。
2.客户/服务器体系结构
客户机/服务器(client/server,C/S)体系结构是基于资源不对等,且为实现共享而提出来的,由服务器、客户机和网络三部分组成。
在C/S体系结构中,客户机可以通过远程调用来获取服务器提供的服务,因此,客户机必须知道可用的服务器的名字及它们所提供的服务,而服务器不需要知道客户机的身份,也不需要知道有多少台服务器在运行。
传统的C/S体系结构分为两层。在这种体系结构中,一个应用系统被划分为客户机和服务器两部分。典型的两层C/S体系结构如下图所示。
两层C/S体系结构可以有两种形态:
(1)瘦客户机模型。在瘦客户机模型中,数据管理部分和应用逻辑都在服务器上执行,客户机只负责表示部分。瘦客户机模型的主要缺点:它将繁重的处理负荷都放在了服务器和网络上,服务器负责所有的计算,这将增加客户机和服务器之间的网络流量。目前个人计算机所具有的处理能力在瘦客户机模型中用不上。
(2)胖客户机模型。在这种模型中,服务器只负责对数据的管理。客户机上的软件实现应用逻辑和与系统用户的交互。胖客户机模型能够利用客户机的处理能力,比瘦客户机模型在分布处理上更有效。但另一方面,随着企业规模的日益扩大,软件的复杂程度不断提高,胖客户机模型逐渐暴露出了以下缺点:
开发成本较高。
用户界面风格不一,使用繁杂,不利于推广使用。
软件移植困难。
软件维护和升级困难。
为了解决以上问题,三层C/S体系结构应运而生。三层C/S体系结构中增加了应用服务器。可以将整个应用逻辑驻留在应用服务器上,而只有表示层存在于客户机上。
三层C/S体系结构将整个系统分成表示层、应用逻辑层和数据层三个部分,其数据处理流程如下图所示。
(1)表示层:表示层是应用系统的用户界面部分,担负着用户与应用程序之间的对话功能。它用于检查用户从键盘等输入的数据,显示应用程序输出的数据,一般采用图形用户界面(graphic user interface, GUI)。
(2)应用逻辑层:应用逻辑层为应用系统的主体部分,包含具体的业务处理逻辑。通常在功能层中包含有确认用户对应用和数据库存取权限的功能以及记录系统处理日志的功能。
(3)数据层:数据层主要包括数据的存储及对数据的存取操作,一般选择关系型数据库管理系统(RDBMS)。
浏览器/服务器(browser/server,B/S)风格是三层体系结构的一种实现方式,其具体结构为浏览器/Web服务器/数据库服务器。B/S体系结构如下图所示。
B/S体系结构主要是利用不断成熟的WWW浏览器技术,结合浏览器的多种脚本语言,用通用浏览器就实现了原来需要复杂的专用软件才能实现的强大功能,并节约了开发成本。从某种程度上来说,B/S结构是一种全新的软件体系结构。
B/S体系结构具有以下优点:
(1)基于B/S体系结构的软件,系统安装、修改和维护全在服务器端解决。
(2)B/S体系结构还提供了异种机、异种网、异种应用服务的联机、联网和统一服务的最现实的开放性基础。
与C/S体系结构相比,B/S体系结构也有许多不足之处。
(1)B/S体系结构缺乏对动态页面的支持能力,没有集成有效的数据库处理功能。
(2)采用B/S体系结构的应用系统,在数据查询等响应速度上,要远远地低于C/S体系结构。
(3)B/S体系结构的数据提交一般以页面为单位,数据的动态交互性不强,不利于在线事务处理(OLTP)应用。
3.分布式对象体系结构
在客户机/服务器模型中,客户机和服务器的地位是不同的。为了消除客户机与服务器之间的差别,提高系统的伸缩性以及有效地均衡负载,可采用分布式对象体系结构来设计系统。
分布式对象的实质是在分布式异构环境下建立应用系统框架和对象构件,它将应用服务分割成具有完整逻辑含义的独立子模块(称为构件),各个子模块可放在同一台服务器或分布在多台服务器上运行,模块之间通过中间件互相通信。
通常将这个中间件称为软件总线或对象请求代理,它的作用是在对象之间提供一个无缝接口。
分布式对象技术的应用目的是为了降低主服务器的负荷、共享网络资源、平衡网络中计算机业务处理的分配,提高计算机系统协同处理的能力,从而使应用的实现更为灵活。
分布式对象技术的基础是构件。构件是一些独立的代码封装体,在分布计算的环境下可以是一个简单的对象,但大多数情况下是一组相关的对象组合体,提供一定的服务。
分布式环境下,构件是一些灵活的软件模块,它们可以位置透明、语言独立和平台独立地互相发送消息,实现请求服务。
构件之间并不存在客户机与服务器的界限,接受服务者扮演客户机的角色,提供服务者就是服务器。
4.代理
代理可以用于构建带有隔离组件的分布式软件系统,该软件通过远程服务调用进行交互。代理者负责协调通信,诸如转发请求以及传递结果和异常等。
1991年,OMG基于面向对象技术,给出了以对象请求代理(ORB)为中心的分布式应用体系结构。
在OMG的对象管理结构中,ORB是一个关键的通信机制,它以实现互操作性为主要目标,处理对象之间的消息分布。在ORB之上有4个对象接口:
(1)对象服务:定义加入ORB的系统级服务,如安全性、命名和事务处理,它们是与应用领域无关的。
(2)公共设施:水平级的服务,定义应用程序级服务。
(3)领域接口:面向特定的领域。
(4)应用接口:面向指定的现实世界应用。是指供应商或用户借助于ORB、公共对象服务及公共设施而开发的特定产品。
7.5体系结构框架
1.MVC框架
MVC框架即模型—视图—控制器(model-view-controller)框架,它强调将用户输入、数据模型和数据表示的方式分开设计,一个交互式应用系统由模型、视图和控制器3个部件组成,分别对应于内部数据、数据表示和输入/输出控制部分。
1.模型对象
模型对象独立于外在显示内容和形式,代表应用领域中的业务实体和业务规则,是整个模型的核心。模型对象的变化通过事件处理通知视图和控制器对象。
2.视图对象
视图对象代表GUI对象,并且以用户需要的格式表示模型状态,是交互系统与外界的接口。视图对象可以包含子视图,子视图用于显示模型的不同部分。通常,每个视图对象对应一个控制器对象。
3.控制器对象
控制器对象代表鼠标和键盘事件。它处理用户的输入行为并给模型发送业务事件,再将业务事件解析为模型应执行的动作;同时,模型的更新与修改也将通过控制器来通知视图,从而保持各个视图与模型的一致性。
MVC的处理过程为:首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理;然后模型用业务逻辑来处理用户的请求并返回数据;最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。
其中,模型是核心数据和功能,视图只关心显示数据,控制只关心用户输入,这种结构由于将数据和业务规则从表示层分开,因此可以最大化地重用代码。
2.J2EE体系结构框架
J2EE的核心体系结构就是在MVC框架的基础上进行扩展得到的,如下图所示。
从上图可看出,J2EE模型是分层结构,中间的3层(表示层,业务层,集成层)包含应用程序构件,客户层和资源层处于应用程序的外围。
客户层:用户通过客户层与系统交互。该层可以是各种类型的客户端。例如,可编程客户端(如基于Java Swing的客户端或applet),纯Web浏览器客户端,WML移动客户端等。
资源层:资源层可以是企业数据库,电子商务解决方案中的外部企业系统,或者是外部SOA服务。数据可以分布在多个服务器上。
表示层:也称为Web层或服务器端表示层,用户通过表示层来访问应用程序。在基于Web的应用系统中,表示层由用户界面代码和运行于Web服务器或应用服务器上的过程组成。参考MVC框架,表示层包括视图构件和控制器构件。
业务层:业务层包含表示层中的控制器构件没有实现的一部分应用逻辑。它负责确认和执行企业范围内的业务规则和事务,并管理从资源层加载到应用程序高速缓存中的业务对象。
集成层:集成层负责建立和维护与数据源的连接。例如,通过JDBC与数据库进行通信,利用Java消息服务(JMS)与外部系统联合。
3.PCMEF与PCBMER框架
PCMEF框架
表示—控制—中介者—实体—基础(presentation-control-mediator-entity-foundation,PCMEF)是一个垂直层次的分层体系结构框架。每一层是可以包含其他包的包。PCMEF框架包含4层:表示层、控制层、领域层和基础层。领域层包含两个预定义包:实体(entity)包和中介者(mediator)包。
PCMEF框架中包的依赖性主要是向下依赖性。表示层依赖于控制层,控制层依赖于领域层,中介者包依赖于实体包和基础层, 如下图所示。
表示层:包含定义GUI对象的类。
控制层:处理表示层的请求,负责大多数程序逻辑、算法、主要计算以及为每个用户维持会话状态。
领域层:其实体包处理控制请求,中介者包用于创建一个协调实体类和基础类的通信通道。
基础层:负责与数据库和Web服务的所有通信。
PCBMER框架
PCBMER 框架由PCMEF框架扩展而成, 代表着表示—控制器—Bean—中介者—实体—资源(presentation-control-bean-mediator-entity-resource,PCBMER)。其核心体系结构框架如右图所示。
在上图中,把层表示为UML包(子系统,层),带箭头的虚线表示依赖关系。例如,表示层依赖控制器层和bean层,控制器层依赖bean层。PCBMER的层次不是严格线性的,上层可以依赖多个相邻下层。
bean层:表示那些预先确定要呈现在用户界面上的数据类和值对象。除了用户输入外,bean数据由实体对象(实体层)创建。
表示层:表示屏幕以及呈现bean对象的UI对象。
控制器层:表示应用逻辑。
实体层:响应控制器和中介者。
中介者层:建立了充当实体类和资源类媒介的通信管道。
资源层:负责所有与外部持久数据资源(数据库、Web服务等)的通信。
7.6设计模式
面向对象设计模式最初出现于70年代末80年代初。
Erich Gamma等4人合著的“Design Patterns: Elements of Reusable Object-Oriented Software”被认为是设计模式方面的经典著作。
目前,设计模式已经被广泛应用于多种领域的软件设计和构造中,许多当代的先进软件中已大量采用了软件设计模式的概念。
一般来说,一个模式有4个基本的要素:
(1)模式名称:用于描述模式的名字,说明模式的问题、解决方案和效果。
(2)问题:说明在何种场合使用模式。
(3)解决方案:描述设计的组成成分、它们之间的相互关系、各自的职责和合作方式。
(4)效果:描述了模式使用的效果及使用模式应当权衡的问题。
看ppt设计模式,很清楚的
1.创建型模式
1.1抽象工厂模式
1.2单例模式
2.结构型模式
2.1适配器模式
2.2外观模式
3.行为型模式
3.1责任链模式
3.2中介者模式
3.3观察者模式
8.面向对象设计
怎么感觉完全没学过这节??
8.1面向对象设计过程与准则
面向对象设计过程
(1) 建立系统环境模型。在设计的初始阶段,系统设计师用系统环境图对软件与外部实体交互的方式进行建模。下图给出了系统环境图的一般的结构。
(2) 设计系统体系结构。体系结构设计可以自底向上进行,如将关系紧密的对象组织成子系统或层;也可以自顶向下进行,尤其是使用设计模式或遗产系统时,会从子系统的划分入手。
(3) 对各个子系统进行设计。对于面向对象的系统,典型的子系统有问题域子系统、人机交互子系统和任务管理子系统。
(4) 对象设计及优化。对象设计以问题领域的对象设计为核心,其结果是一个详细的对象模型。对象设计过程包括使用模式设计对象、接口规格说明、对象模型重构、对象模型优化4组活动。
面向对象设计准则
1.模块化
传统的面向过程方法中的模块通常是函数、过程及子程序等,而面向对象方法中的模块则是类、对象、接口、构件等。
在面向过程的方法中,数据及在数据上的处理是分离的;而在面向对象方法中,数据及其上的处理是封装在一起的,具有更好的独立性,也能够更好地支持复用。
2.抽象
面向对象方法不仅支持过程抽象,而且支持数据抽象。类实际上就是一种抽象数据类型。可以将类的抽象分为规格说明抽象及参数化抽象。
类对外开放的公共接口构成了类的规格说明,即协议。这种接口规定了外部可以使用的服务,使用者无需知道这些服务的具体实现算法。通常将这类抽象称为规格说明抽象。
参数化抽象是指当描述类的规格说明时并不具体指定所要操作的数据类型,而是将数据类型作为参数。
3.信息隐藏
在面向对象方法中,信息隐藏通过对象的封装性实现。对于类的用户来说,属性的表示方法和操作的实现算法都应该是隐藏的。
4.弱耦合
耦合是指一个软件结构内不同模块之间互连的紧密程度。在面向对象方法中,对象是最基本的模块,因此,耦合主要指不同对象之间相互关联的紧密程度。
5.强内聚
内聚衡量一个模块内各个元素彼此结合的紧密程度。在面向对象设计中存在以下3种内聚:
(1) 服务内聚:一个服务应该完成一个且仅完成一个功能。
(2) 类内聚:设计类的原则是,一个类应该只有一个用途,它的属性和服务应该是高内聚的。类的属性和服务应该全都是完成该类对象的任务所必需的,其中不包含无用的属性或服务。如果某个类有多个用途,通常应该把它分解成多个专用的类。
(3) 一般—特殊内聚:设计出的一般—特殊结构,应该符合多数人的概念,更准确地说,这种结构应该是对相应的领域知识的正确抽取。
6.可重用
软件重用是提高软件开发生产率和目标系统质量的重要途径。
重用基本上从设计阶段开始。重用有两方面的含义:
一是尽量使用已有的类(包括开发环境提供的类库,及以往开发类似系统时创建的类),
二是如果确实需要创建新类,则在设计这些新类的协议时,应该考虑将来的可重复使用性。
8.2体系结构模块及依赖性
体系结构设计描述了建立计算机系统所需的数据结构和程序构件。一个好的体系结构设计要求软件模块的分层及编程标准的执行。
在面向对象软件中,常见的软件模块有类、接口、包和构件。
在设计阶段我们往往关注类、接口和包,在实现阶段关注构件,而在部署阶段则关注构件的部署,也就是将构件部署在哪些结点上。
1.类及其依赖性
1.1类
在面向对象的程序设计中,类和接口是程序的基本组成单元。
一个典型程序需要界面类专门负责表示用户界面信息,需要数据库类负责与数据库进行交互,需要有业务逻辑类负责算法计算等。
在计算机程序中,要设计和实现的所有类都具有唯一的名字,在不同的阶段或从不同的角度可以将它们称为设计类、实现类、系统类、应用类等。
1.2继承依赖性
依赖性管理中最棘手的问题是由于继承所引起的依赖性。继承是一种在父类和子类之间共享属性和行为的方式,所以运行时可以用一个子类对象代替其父类对象。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个子类对象是一种特殊的父类对象,它继承父类的所有特征,同时它又可以覆盖父类的方法,从而改变从父类继承的一些特征,并可以在子类中增加一些新的功能。这样,从客户的角度看,在继承树中为请求提供服务的特定对象不同,系统的运行行为可能会有所不同。
(1)多态继承
根据为请求提供服务的对象不同可以得到不同的行为,这种现象称为多态。在运行时对类进行实例化,并调用与实例化对象相应的方法,称为动态绑定、后期绑定或运行时绑定。相应地,如果方法的调用是在编译时确定的,则称为是静态绑定、前期绑定或编译时绑定。
多态并不是伴随着继承而出现。如果在子类中不覆盖父类中的任何方法,就不会产生多态行为。
很明显,继承会带来类和方法之间的依赖性。继承带来的依赖性有编译时继承依赖性和运行时继承依赖性。
① 编译时继承依赖性
右图所示的例子说明了一棵树中类之间的编译时依赖性。在这个例子中,B继承A,但没有覆盖A中的方法do1( )。因此,B和A之间没有运行时继承依赖性。也就是说,由于编译时依赖性的存在,A中do1( )方法的任何变化,都会被B在编译时(静态地)继承。
一般来说,所有的继承都会引入编译时依赖性。依赖性是可传递的,也就是说,如果C依赖B,B依赖A,那么C也依赖A。
② 运行时继承依赖性
下图举例说明了在一棵继承树中涉及客户对象访问类服务的运行时继承依赖性。图中类B的do1( )方法是从父类A继承来的,因此Test与B没有运行时继承依赖性,只是一个静态依赖性,通过从Test到A的关联来表明。如果在doTest方法中调用的是do2( )方法,或者在B中覆盖了A的do1( )方法,则从Test到A和B就会存在运行时依赖性。
(2)无多态继承
使用继承最简单的方式是子类不覆盖从父类继承来的方法,这样就不存在多态性继承问题。虽然无多态的继承有时并不是十分有用,但理解和管理起来是最容易的。
(3)扩展继承和约束继承
扩展继承是指子类继承父类的属性,并且提供额外属性来增强类定义。子类是父类的一种,如果子类覆盖了父类的方法,那么被覆盖的方法应该实现该方法的定义,并且能够在子类的语境中工作。
当一个类覆盖了继承来的方法,并对一些继承来的功能进行了限制,这时就产生了约束继承。这时,子类不再是父类的一种。有时,限制会造成继承方法的完全禁止。当方法的实现是空时,就会发生这种情况。
1.3交互依赖性
交互依赖性也称为方法依赖性,是通过消息连接产生的。如下图所示。
图中,CActioner使用方法do1( )来发送一条消息do3( )给EEmployee,因此,do1( )依赖于do3( )。依赖性向上传递给所属的类,因此,CActioner依赖于EEmployee。类似地,EOutMessage的do2( )调用EEmployee的方法do3( ),因此,EOutMessage依赖于EEmployee。
2.接口及其依赖性
2.1接口
在UML2.0中,接口是不可直接实例化的特性集合的声明,即其对象不能直接实例化,需要通过类来实现,实现接口的类需要实现接口中声明的方法。UML2.0对流行编程语言中的接口概念进行了扩展。接口中不仅可以声明操作,还可以声明属性。
由于允许在接口中存在属性,因此,在接口之间或者接口和类之间可能会产生关联。用另一个接口或类作为属性的类型可以表示关联。
在UML2.0中,可以通过关联实现从接口到类的导航。但在Java中是无法实现的,因为Java规定接口中的数据元素必须是常量。
接口与抽象类有相似之处,抽象类是至少包含一个没有实现的方法的类,如果在一个抽象类中所有的方法都没有实现,则称其为纯抽象类,从这一点上,接口和纯抽象类似乎没有区别。但实际上,接口和抽象类还是有着本质的区别。在只支持单继承的语言中,一个类只能有一个直接父类,但是却可以实现多个接口。
2.2实现依赖性
一个类可以实现多个接口,由类实现的接口集合称为该类的供给接口。在UML2.0中,将一个类和该类实现的接口之间的依赖性称为实现依赖性。
右图所示为实现依赖性的UML符号,在箭头末端的类实现了箭头所指向的接口。从图中可以看到,Class1实现了Interface1接口和Interface2接口,而Class2只实现了Interface2接口。
2.3使用依赖性
一个接口可以为其他类或接口提供服务,同时也可能需要其他接口的服务。一个接口所需要的其他接口所提供的服务称为这个类的需求接口。需求接口详细说明一个类或接口需要的服务,从而可以为其客户提供服务。在UML2.0中,通过类(接口)和它所需接口之间的依赖关系来说明需求接口,这称为使用依赖性。
下图所示为使用依赖性的UML符号,在箭头尾部的类或接口使用在箭头头部的接口。Class1使用Interface1,Interface1使用Interface2。在Java语言中,不允许接口之间的使用,只允许接口间的扩展继承。
Class1包含方法do1( ),而do1( )调用操作op1( )。在静态代码中,并不清楚需求接口的哪个实现提供了所需的服务,可以是实现Interface1的任何一个类实例。当Class1的一个执行实例设置数据成员myInterface的值时,具体实例才能确定,从而可以引用具体类的一个具体对象。
3.包及其依赖性
3.1包
包(package)又可称为层或子系统,是表示组织类的一种方式,用于划分应用程序的逻辑模型。包是高度相关的类的聚合,这些类本身是内聚的,但相对于其他聚合来说又是松散耦合的。
包可以嵌套。外层包可以直接访问包括在它的嵌套包中的任何类。包还可以导入其他包,例如,在包A中导入了包B,这意味着包A或者包A的元素可以引用包B或者包B的元素。因此,虽然一个类只属于一个包,但是它可以被导入其他包。包的导入操作会引入包之间的依赖性以及它们的元素之间的依赖性。
下图为UML包的例子。一个包可以不暴露任何成员,也可以明确标明它所包含的成员,或者用符号“Å”来表示。图中,包 B拥有类 X,包C拥有包 D,包E拥有包 F,包 F拥有类Y和类Z。
如果包A的一些成员在某种程度上引用了包B的某些成员(包A导入了包B的一些成员),这隐含着双重含义。
•包B的变化可能会影响包A,通常需要对包A重新进行编译和测试。
•包A只能和包B一起使用。
3.2包依赖性
本质上,两个包之间的依赖性来自于两个包中类之间的依赖性。类之间的循环依赖性是个特别棘手的问题,好在大多数情况下可以通过重新设计避免循环依赖性。
通过在上图中增加新包可以消除包之间的循环依赖性。方法为:在第1个例子中将包B依赖的包A的元素从包A中分离出来,组成包C,使得包B不再依赖包A,而是依赖包C;
4.构件及其依赖性
在面向对象的软件工程环境中,面向对象技术已达到了类级复用,而构件级复用则是比类级复用更高一级的复用,它是对一组类的组合进行封装(当然,在某些情况下,一个构件可能只包含一个单独的类),并代表完成一个或多个功能的特定服务,也为用户提供了多个接口。
一个构件可以是一个编译的类,可以是一组编译的类,也可以是其他独立的部署单元,如一个文本文件、一个图片、一个数据文件、一个脚本等。
8.3系统分解
8.4问题域部分的设计
8.5人机交互部分的设计
8.6任务管理部分的设计
8.7数据管理部分的设计
8.8对象设计
Part4.软件实现与测试
9.软件实现
10.软件测试方法
10.1软件测试的基本概念
什么是软件测试
软件测试是在软件投入生产性运行之前,对软件需求分析、设计规格说明和编码的最终复审,是软件质量控制的关键步骤。
软件测试是为了发现错误而执行程序的过程。
或者说,软件测试是根据软件开发各阶段的规格说明和程序的内部结构而精心设计一批测试用例(即输入数据及其预期的输出结果),并利用这些测试用例去运行程序,以发现程序错误的过程。
软件测试的目的和原则
基于不同的立场,存在着两种完全不同的测试目的。
从用户的角度出发,普遍希望通过软件测试检验软件中隐藏的错误和缺陷,以考虑是否可以接受该产品。
从软件开发者的角度出发,则希望测试成为表明软件产品中不存在错误的过程,验证该软件已正确地实现了用户的要求,确立人们对软件质量的信心。
根据以上测试目的,软件测试的原则如下:
(1)应当把“尽早地和不断地进行软件测试”作 为软件开发者的座右铭。
(2)测试用例应由测试输入数据和与之对应的预期输出结果这两部分组成。
(3)程序员应避免检查自己的程序。
(4)在设计测试用例时,应当包括合理的输入条件和不合理的输入条件。
(5)充分注意测试中的群集现象。把Pareto原理应用于软件测试。Pareto原理:测试发现的错误中的80%很可能是由程序中20%的模块造成的。
(6)严格执行测试计划,排除测试的随意性。
(7)应当对每一个测试结果作全面检查。
(8)妥善保存测试计划、测试用例、出错统计和最终分析报告,为维护提供方便。
测试工作量和测试人员
在整个软件开发中,测试工作量一般占30%~40%,甚至≥50%。
在人命关天的软件(如飞机控制、核反应堆等)中,测试所花费的时间往往是其它软件工程活动时间之和的三到五倍。
ex:Windows2000开发人员中:
项目经理约250人
开发人员约1700人
测试人员约3200人
软件测试的对象
软件测试应贯穿于软件定义与开发的整个期间。需求分析、概要设计、详细设计、程序编码等各阶段所得到的文档资料,包括需求规格说明、概要设计规格说明、详细设计规格说明以及源程序,都应成为软件测试的对象。
到程序的测试为止,软件开发工作已经经历了许多环节,每个环节都可能发生问题。为了把握各个环节的正确性,人们需要进行各种确认和验证工作。
确认(validation),是一系列的活动和过程,其目的是想证实在一个给定的外部环境中软件的逻辑正确性。它包括需求规格说明的确认和程序的确认,而程序的确认又分为静态确认与动态确认。
验证(verification),则试图证明在软件生存期各个阶段,以及阶段间的逻辑协调性、完备性和正确性。下图为软件生存期各个重要阶段之间所要保持的正确性。
测试方法与技术
机器测试与人工测试
机器测试
在设定的测试数据上执行被测程序的过程。又称动态测试。
人工测试
采用人工方法进行,目的在于检查程序的静态结构,找出编译不能发现的错误。
人工测试的分类
代码审查
以小组会的形式,发现程序在结构、功能、编码风格等方面存在的问题。可查出30%~70%的错误
走查
以小组会的形式进行,把测试数据“输入”到被测程序,并在纸上跟踪监视程序的执行情况,让人代替机器沿着程序的逻辑走一遍。
桌前检查
设计模块时,程序员自己检查。
测试信息流
测试与软件开发各阶段的关系
软件开发过程是一个自顶向下、逐步细化的过程,而测试过程则是依相反的顺序安排的自底向上、逐步集成的过程。低一级测试为上一级测试准备条件。
当然不排除两者平行地进行测试。
黑盒测试
黑盒测试是把测试对象看做一个黑盒子,测试人员完全不考虑程序内部的逻辑结构和内部特性,只依据程序的需求规格说明书,检查程序的功能是否符合它的功能说明。
黑盒穷举测试
对所有输入数据的各种可能值的排列组合都进行测试,来检查程序是否都能产生正确的输出。
实际上这是不可能的。
白盒测试
白盒测试是对软件的过程性细节做细致的检查。
这一方法是把测试对象看做一个打开的盒子或透明的盒子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序所有逻辑路径进行测试。
通过在不同点检查程序的状态,确定实际的状态是否与预期的状态一致。
因此,白盒测试又称为结构测试或逻辑驱动测试。
白盒测试主要是对程序模块进行检查:
对程序模块的所有独立的执行路径至少测试一次;
对所有的逻辑判定,取“真”与取“假”的两种情况都能至少测试一次;
在循环的边界和运行界限内执行循环体;测试内部数据结构的有效性等。
10.2白盒测试的测试用例设计
逻辑覆盖
逻辑覆盖是以程序内部的逻辑结构为基础的设计测试用例的技术,它属于白盒测试。
由于覆盖测试的目标不同,逻辑覆盖又可分为:
语句覆盖
语句覆盖就是设计若干个测试用例,运行被测程序,使得每一可执行语句至少执行一次。
在图例中,正好所有的可执行语句都在路径L1上,所以选择路径 L1设计测试用例,就可以覆盖所有的可执行语句。
满足语句覆盖的测试用例是:
【(2, 0, 4),(2, 0, 3)】
虽然语句覆盖检验了每一个可执行语句,但可能发现不了判断中逻辑运算的错误。语句覆盖是最弱的逻辑覆盖标准。
如将A>1&&B= =0 错写成 A>1||B= =0,测试用例【(2, 0, 4),(2, 0, 3)】依然成立。
判定覆盖(分支覆盖)
判定覆盖就是设计若干个测试用例,运行被测程序,使得程序中每个判断的取真分支和取假分支至少经历一次。判定覆盖又称为分支覆盖。
【(2, 0, 4),(2, 0, 3)】覆盖 ace【L1】
【(1, 1, 1),(1, 1, 1)】覆盖 abd【L2】
如果选择路径L3和L4,还可得另一组可用的测试用例:
【(2, 1, 1),(2, 1, 2)】覆盖 abe【L3】
【(3, 0, 3),(3, 1, 1)】覆盖 acd【L4】
只是判定覆盖,还不能保证一定能查出在判断的条件中存在的错误。
若将第二个判断中的条件x>1错写成x<1,上面两组测试用例,仍能得到同样结果。
条件覆盖
条件覆盖就是设计若干个测试用例,运行被测程序,使得程序中每个判断的每个条件的可能取值至少执行一次。
这组测试用例不但覆盖了所有判断的取真分支和取假分支, 而且覆盖了判断中所有条件的可能取值;
这组测试用例虽满足了条件覆盖, 但只覆盖了第一个判断的取假分支和第二个判断的取真分支。
因此,满足条件覆盖,并不一定能满足分支覆盖。
判定—条件覆盖
什么垃圾定义,就是判定(每个判定取真和取假)+条件(每个条件取真和取假)
所谓判定-条件覆盖就是设计足够的测试用例,使得判断中每个条件的所有可能取值至少执行一次,同时每个判断本身的所有可能判断结果至少执行一次。
判定-条件覆盖也有缺陷。从表面上看,它测试了所有条件的取值,但事实并非如此。因为往往某些条件掩盖了另一些条件。
对于表达式(A>1) and (B=0)来说,若(A>1)的测试结果为假,往往就不再测试(B=0)的取值了。
为彻底检查所有条件的取值,可将多重条件判定分解,形成由多个基本判断组成的流程图。这样就可以有效检查所有的条件是否正确了。
条件组合覆盖
条件组合覆盖就是设计足够的测试用例,运行被测程序,使得每个判断的所有可能的条件取值组合至少执行一次。
路径漏掉了L4
条件组合覆盖是一种相当强的覆盖准则,可以有效地检查各种可能的条件取值的组合是否正确。
它不但可覆盖所有条件的可能取值的组合,还可覆盖所有判断的可取分支,但可能有的路径会遗漏掉。
因此,满足条件组合覆盖的测试还不完全。
路径覆盖
路径测试是设计足够的测试用例,覆盖程序中所有可能的路径。若仍以最初的图为例,则可以选择如下的一组测试用例,覆盖该程序段的全部路径。
10.3基本路径覆盖
基本路径测试是在程序控制流图的基础上,通过分析控制构造的环路复杂性,导出基本可执行路径集合,从而设计测试用例的方法。
设计出的测试用例要保证在测试中程序的每一个可执行语句至少执行一次。
语句覆盖发现错误能力最弱。判定覆盖包含了语句覆盖, 但它可能会使一些条件得不到测试。条件覆盖对每一条件进行单独检查,一般情况它的检错能力较判定覆盖强,但有时达不到判定覆盖的要求。判定/条件覆盖包含了判定覆盖和条件覆盖的要求,但由于计算机系统软件实现方式的限制,实际上不一定达到条件覆盖的标准。条件组合覆盖发现错误能力较强, 凡满足其标准的测试用例,也必然满足前 4 种覆盖标准。
前 5 种覆盖标准把注意力集中在单个判定或判定的各个条件上,可能会使程序某些路径没有执行到。路径测试根据各判定表达式取值的组合,使程序沿着不同的路径执行,查错能力强。
实施基本路径测试需要利用程序环路复杂性计算的McCabe方法。基本路径测试法适用于模块的详细设计及源程序,其主要步骤如下:
(1)以详细设计或源代码作为基础,导出程序的控制流图;
(2)计算得到的控制流图G的环路复杂性V(G);
(3)确定线性无关的基本路径集;
(4)生成测试用例,确保基本路径集中每条路径的执行。
程序的控制流图:描述程序控制流的一种图示方法。
流图只有二种图形符号:
图中的每一个圆称为流图的结点,代表一条或多条语句。
流图中的箭头称为边或连接,代表控制流
任何过程设计都要被翻译成控制流图。
如何根据程序流程图画出控制流程图?
在将程序流程图简化成控制流图时,应注意:
(1)在选择或多分支结构中,分支的汇聚处应有一个汇聚结点。
(2)边和结点圈定的区域叫做区域,当对区域计数时,图形外的区域也应记为一个区域。
(3)如果判断中的条件表达式是由一个或多个逻辑运算符 (OR, AND, NAND, NOR) 连接的复合条件表达式,则需要改为一系列只有单条件的嵌套的判断。
例如:
1 |
|
程序环路复杂性
给定控制流图G的环路复杂性V(G)的计算方法定义为V(G)=E-N+2,E是流图中边的数量,N是流图中结点的数量;
或者
V(G)=P+1,P是流图G中判定结点的数量。
独立路径:包括一组以前没有处理的语句或条件的一条路径。或至少沿一条新的边移动的路径 ,即一条新的路径必须包含一条新边。
下例程序流程图描述了最多输入50个值(以–1作为输入结束标志),计算其中有效的学生分数的个数、总分数和平均值。
1.由过程描述导出控制流图
2.计算得到的控制流图的环路复杂性
1)V(G)= 6 (个区域)
2)V(G)=E–N+2=16–12+2=6
其中E为流图中的边数,N为结点数;
3)V(G)=P+1=5+1=6
其中P为判定节点数
3.确定线性无关的基本路径集
计算出的环路复杂性的值,就是该图已有的线性无关基本路径集中路径的数目。该图所有的6条路径是:
路径1:1-2-9-10-12
路径2:1-2-9-11-12
路径3:1-2-3-9-10-12
路径4:1-2-3-4-5-8-2…
路径5:1-2-3-4-5-6-8-2…
路径6:1-2-3-4-5-6-7-8-2…
4.准备测试用例,确保基本路径集中的每一条路径的执行
1)路径1(1-2-9-10-12)的测试用例:
score[k]=有效分数值,当k < i ;
score[i]=–1, 2≤i≤50;
期望结果:根据输入的有效分数算出正确的分数个数n1、总分sum和平均分average。
2)路径2(1-2-9-11-12)的测试用例:
score[ 1 ]= – 1 ;
期望的结果:average = – 1 ,其他量保持初值。
3)路径3(1-2-3-9-10-12)的测试用例:
输入多于50个有效分数,即试图处理51个分数,要求前51个为有效分数;
期望结果:n1=50、且算出正确的总分和平均分。
4)路径4(1-2-3-4-5-8-2…)的测试用例:
score[i]=有效分数,当i<50;
score[k]<0, k< i ;
期望结果:根据输入的有效分数算出正确的分数个数n1、总分sum和平均分average。
5)路径5的测试用例:
score[i]=有效分数, 当i<50;
score[k]>100, k< i ;
期望结果:根据输入的有效分数算出正确的分数个数n1、总分sum和平均分average。
6)路径6(1-2-3-4-5-6-7-8-2…)的测试用例:
score[i]=有效分数, 当i<50;
期望结果:根据输入的有效分数算出正确的分数个数n1、总分sum和平均分average。
5.图形矩阵
图形矩阵是在基本路径测试中起辅助作用的软件工具,利用它可以实现自动地确定一个基本路径集。
一个图形矩阵是一个方阵,其行/列数等于控制流图中的结点数。
每行和每列依次对应到一个被标识的结点,矩阵元素对应到结点间的连接(即边)。
在控制流图中对每一条边加上一个连接权,图形矩阵就成为测试过程中评价程序控制结构的工具。连接权提供了关于控制流的附加信息。最简单的情形,连接权为“1”,表示存在一个连接,或者为“0”,表示不存在一个连接。但在其他情况,连接权可以表示如下特性:连接(边)执行的可能性(概率)、通过一个连接需花费的时间、在通过一个连接时所需的存储、在通过一个连接时所需的资源。
为了举例说明,用最简单的权限(0或者1)来表明连接。下图为上图图形矩阵改画后的结果。每个字母用“1”取代,表明存在一个连接。在图中,“0”未画出。采用这种表示时,图形矩阵称为连接矩阵。
图中,若一行有2个或更多的元素,则这行所代表的结点一定是判定结点。因而通过计算排列在连接矩阵右边的算式,可以得到确定该图环路复杂性的另一种方法。
确定线性无关的基本路径集
计算出的环路复杂性的值,就是该图已有的线性无关基本路径集中路径的数目。该图所有的4条路径是:
路径1:4-14
路径2:4-6-7-14
路径3:4-6-8-10-13-4-14
路径4:4-6-8-11-13-4-14
10.4黑盒测试的测试用例设计
等价类划分
等价类划分是一种典型的黑盒测试方法,也是一种非常实用的重要测试方法,它是用来解决如何选择适当的子集,使其尽可能多地发现错误。
使用这一方法设计测试用例要经历划分等价类(列出等价类表)和选取测试用例两步。
1.划分等价类
所谓等价类是指某个输入域的子集合。在该子集合中,各个输入数据对于揭露程序中的错误都是等效的,并合理地假定:测试某等价类的代表值等价于对这一类其他值的测试。
或者说,如果某个等价类中的一个数据作为测试数据进行测试查出了错误,那么使用这一等价类中的其他数据进行测试也会查出同样的错误;
反之,若使用某个等价类中的一个数据作为测试数据进行测试没有查出错误,则使用这个等价类中的其他数据也同样查不出错误。
等价类的划分有两种不同的情况:
(1)有效等价类:是指对于程序的规格说明来说,是合理 的、有意义的输入数据构成的集合。利用它,可以检 验程序是否实现了规格说明预先规定的功能和性能。
(2)无效等价类:是指对于程序的规格说明来说,是不合 理的、无意义的输入数据构成的集合。程序员主要利用这一类测试用例检查程序中功能和性能的实现是否有不符合规格说明要求的地方。
在设计测试用例时,要同时考虑有效等价类和无效等价类的设计。
以下结合具体实例给出几条划分等价类的原则。
(1)如果输入数据规定了取值范围或值的个数,则可以确 定一个有效等价类和两个无效等价类。例如,在程序 的规格说明中,对输入数据有一句话:
“……项数可以从1到999……”
则有效等价类是“1≤项数≤999”,两个无效等价类是 “项数<1”及“项数>999”。在数轴上表示为
(2)如果规格说明规定了数据值的集合,或者是规定了“必须如何”的条件,这时可确定一个有效等价类和 一 个无效等价类。例如,在PASCAL语言中对变量标识符规定为“以字母打头的……串”,那么所有以字母打 头的串构成有效等价类,而不在此集合内(不以字母打头)的串归于无效等价类。
(3)如果规格说明中规定的是一个条件数据,则可确定一个有效等价类和一个无效等价类。例如:“……成人(年满18岁)须……”,则考虑成人为一有效等价类;未满18岁者为无效等价类。
(4)如果我们确知,已划分的等价类中各元素在程序中的处理方式不同,则应将此等价类进一步划分成更小的 等价类。
2.确定测试用例
在确定了等价类之后,建立等价类表,列出所有划分出的
等价类如下:
再从划分出的等价类中按以下原则选择测试用例。
(1)为每一个等价类规定一个唯一的编号。
(2)设计一个新的测试用例,使其尽可能多地覆盖尚未被覆盖的有效等价类,重复这一步,直到所有的有效等价类都被覆盖为止。
(3)设计一个新的测试用例,使其仅覆盖一个尚未被覆盖的无效等价类,重复这一步,直到所有的无效等价类都被覆盖为止。
3.用等价类划分法设计测试用例的实例
在某一PASCAL语言版本中规定:“标识符是由字母开头、后跟字母或数字的任意组合构成。有效字符数为8个,最大字符数为80个。”并且规定:“标识符必须先说明,再使用。”“在同一说明语句中,标识符至少必须有一个。”
为用等价类划分的方法得到上述规格说明所规定的要求,本着前述的划分原则,建立输入等价类表,如下表所示(表中括号中的数字为等价类编号)。
边界值分析
1.边界值分析方法的考虑
边界值分析也是一种黑盒测试方法,是对等价类划分方法的补充。人们从长期的测试工作经验中得知,大量的错误是发生在输入或输出范围的边界上,而不是在输入范围的内部。
这里所说的边界是指,相当于输入等价类和输出等价类而言,稍高于其边界值及稍低于其边界值的一些特定情况。
2.选择测试用例的原则
(1)如果输入数据规定了值的范围,则应取刚达到这个范围的边界的值,以及刚刚超越这个范围边界的值作为测试输入数据。
例如,若输入值的范围是“−1.0~1.0”,则可选取“−1.0”,“1.0”,“−1.001”,“1.001”作为测试输入数据。
(2)如果输入数据规定了值的个数,则用最大个数、最小个数、比最大个数多1、比最小个数少1的数作为测试数据。例如,一个输入文件有1~255个记录,设计测试用例时则可以分别设计有1个记录、255个记录以及0个记录和256个记录的输入文件。
(3)根据规格说明的每个输出数据,使用前面的原则(1)。
例如,某程序的功能是计算折扣量,最低折扣量是0元,最高折扣量是1 050元,则设计一些测试用例,使它们恰好产生0元和1 050元 的结果。
此外,还可考虑设计结果为负值或大于1 050元的测试用例。
由于输入值的边界不与输出值的边界相对应,所以要检查输出值的边界不一定可能,要产生超出输出值值域之外的结果也不一定办得到。尽管如此,必要时还需一试。
(4)根据规格说明的每个输出数据,使用前面的原则(2)。
例如,一个信息检索系统根据用户打入的命令,显示有关文献的摘要,但最多只显示4篇摘要。
这时可设计一些测试用例,使得程序分别显示1篇、4篇、0篇摘要,并设计一个有可能使程序错误地显示5篇摘要的测试用例。
(5)如果程序的规格说明给出的输入域或输出域是有序集合(如有序表,顺序文件等),则应选取集合的第一个元素和最后一个元素作为测试用例。
(6)如果程序中使用了一个内部数据结构,则应当选择这个内部数据结构的边界上的值作为测试用例。例如,如果程序中定义了一个数组,其元素下标的下界是0,上界是100,那么应选择达到这个数组下标边界的值,如0与100,作为测试用例。
(7)分析规格说明,找出其他可能的边界条件。
有二元函数f(x,y),其中x∈[1,12],y∈[1,31]。 则采用边界值分析法设计的测试用例是:
{ <1,15>, <0,15>, <12,15>, <13,15>, <6,15>, <6,1>, <6,0>, <6,31>, <6,32>}
推论:对于一个含有n个变量的程序,采用边界值分析法测试程序,会产生4n+1个测试用例。
10.5软件测试的策略
通常软件测试过程按4个步骤进行,即单元测试、组装测试、确认测试和系统测试。如下图所示。
单元测试
单元测试(unit testing)又称模块测试,是针对软件设计的最小单位—程序模块,进行正确性检验的测试工作。其目的在于发现各模块内部可能存在的各种差错。单元测试需要从程序的内部结构出发设计测试用例。多个模块可以平行地独立进行单元测试。
1.单元测试的内容
单元测试主要采用白盒测试方法设计测试用例,辅之以黑盒测试的测试用例,使之对任何合理的输入和不合理的输入,都能鉴别和响应。在单元测试中进行的测试工作如下图所示,需要在5个方面对被测模块进行检查。
(1)模块接口测试。在单元测试的开始,应对通过被测模块的数据流进行测试。对模块接口可能需要如下的测试项目:调用本模块时的输入参数与模块的形式参数的匹配情况;本模块调用子模块时,它输入给子模块的参数与子模块中的形式参数的匹配情况;是否修改了只作输入用的形式参数;全局量的定义在各模块中是否一致;限制是否通过形式参数来传送。
(2)局部数据结构测试。模块的局部数据结构是最常见的错误来源,应设计测试用例以检查以下各种错误:不正确或不一致的数据类型说明;使用尚未赋值或尚未初始化的变量;错误的初始值或错误的默认值;变量名拼写错;不一致的数据类型。可能的话,除局部数据之外的全局数据对模块的影响也需要查清。
(3)路径测试。选择适当的测试用例,对模块中重要的执行路径进行测试。应当设计测试用例查找由于错误的计算、不正确的比较或不正常的控制流而导致的错误。对基本执行路径和循环进行测试可以发现大量的路径错误。
(4)错误处理测试。比较完善的模块设计要求能预见出错的条件,并设置适当的出错处理,以便在一旦程序出错时,能对出错程序重作安排,保证其逻辑上的正确性。若出现下列情况之一,则表明模块的错误处理功能包含有错误或缺陷:出错的描述难以理解;出错的描述不足以对错误定义,不足以确定出错的原因;显示的错误与实际的错误不符;对错误条件的处理不正确;在对错误进行处理之前,错误条件已经引起系统的干预等。
(5)边界测试。在边界上出现错误是常见的, 要特别注意数据流、控制流中刚好等于、大于或小于确定的比较值时出错的可能性,对这些地方要仔细地选择测试用例,认真加以测试。
2.单元测试的步骤
通常单元测试是在编码阶段进行的。在源程序代码编制完成,经过评审和验证,肯定没有语法错误之后,就开始进行单元测试的测试用例设计。
模块并不是一个独立的程序,在考虑测试模块时,同时要考虑它和外界的联系,用一些辅助模块去模拟与被测模块相联系的其他模块。这些辅助模块分为如下两种。
(1)驱动模块(driver)——相当于被测模块的主程序,它接收测试数据,并把这些数据传送给被测模块,最后再输出实测结果。
(2)桩模块(stub)——也叫做存根模块,用以代替被测模块调用的子模块。桩模块可以做少量的数据操作,不需要把子模块所有功能都带进来,但不允许什么事情也不做。被测模块、与它相关的驱动模块及桩模块共同构成了一个“测试环境”,如下图所示。
组装测试
组装测试(integrated testing)也叫做集成测试或联合测试。通常,在单元测试的基础上,需要将所有模块按照设计要求组装成为系统,把模块组装为系统的方式有两种:一次性组装方式(big bang)和增值式组装方式。
1.一次性组装方式
它是一种非增值式组装方式,也叫做整体拼装。使用这种方式,首先对每个模块分别进行模块测试,然后再把所有模块组装在一起进行测试,最终得到要求的软件系统。例如,有一个模块系统结构,如下图(a)所示,其单元测试和组装顺序(b)所示。
上图中,模块d1,d2,d3,d4,d5是对各个模块作单元测试时建立的驱动模块,s1,s2,s3,s4,s5是为单元测试而建立的桩模块。这种一次性组装方式试图在辅助模块的协助下,在分别完成模块单元测试的基础上,将被测模块连接起来进行测试。但是,由于程序中不可避免地存在涉及模块间接口、全局数据结构等方面的问题,所以一次试运行成功的可能性不很大。
2.增值式组装方式
这种组装方式又称渐增式组装,首先是对一个个模块进行模块测试,然后将这些模块逐步组装成较大的系统,在组装的过程中边连接边测试,以发现连接过程中产生的问题。最后通过增值逐步组装成为要求的软件系统。增值组装有以下3种做法。
(1)自顶向下的增值方式。
这种组装方式是将模块按系统程序结构,沿控制层次自顶向下进行组装,其步骤如下:
① 以主模块为被测模块兼驱动模块,所有直属于主模块的下属模块全部用桩模块代替,对主模块进行测试。
② 采用深度优先(如下图)或宽度优先的策略,逐步用实际模块替换已用过的桩模块,再用新的桩模块代替它们的直接下属模块,与已测试的模块或子系统组装成新的子系统。
③ 进行回归测试(即重新执行以前做过的全部测试或部分测试),排除组装过程中引入新的错误的可能。
④ 判断是否所有的模块都已组装到系统中,若是则结束测试,否则转到②去执行。
自顶向下的组装和测试存在一个逻辑次序问题。在为了充分测试较高层的处理而需要较低层处理的信息时,就会出现这类问题。在自顶向下组装阶段,还需要用桩模块代替较低层的模块,根据不同情况,桩模块的编写,可能如下所示的几种选择。
为了能够准确地实施测试,应当让桩模块正确而有效地模拟子模块的功能和合理的接口,不能是只包含返回语句或只显示该模块已调用信息,不执行任何功能的哑模块。
(2)自底向上的增值方式。
这种组装方式是从程序模块结构的最底层的模块开始组装和测试。因为模块是自底向上进行组装,对于一个给定层次的模块,它的子模块(包括子模块的所有下属模块)已经组装并测试完成,所以不再需要桩模块。在模块的测试过程中需要从子模块得到的信息可以由直接运行子模块得到。
自底向上增值的步骤如下:
① 由驱动模块控制最底层模块的并行测试;也可以把最底层模块组合成实现某一特定软件功能的簇,由驱动模块控制它进行测试。
② 用实际模块代替驱动模块,与它已测试的直属子模块组装成为子系统。
③ 为子系统配备驱动模块,进行新的测试。
④ 判断是否已组装到达主模块。若是则结束测试,否则执行②。
自底向上进行组装和测试时,需要为被测模块或子系统编制相应的驱动模块。常见的几种类型的驱动模块如下图所示。
(3)混合增值式测试。
自顶向下增值的方式和自底向上增值的方式各有优缺点。自顶向下增值方式的缺点是需要建立桩模块。自底向上增值方式的缺点是“程序一直未能作为一个实体存在,直到最后一个模块加上去后才形成一个实体”。也就是说,在自底向上组装和测试的过程中,对主要的控制直到最后才接触到。
鉴于此,通常是把以上两种方式结合起来进行组装和测试。下面简单介绍3种常见的综合增值方式测试。
① 衍变的自顶向下的增值测试:它的基本思想是强化对输入/输出模块和引入新算法模块的测试,并自底向上组装成为功能相当完整且相对独立的子系统,然后由主模块开始自顶向下进行增值测试。
② 自底向上—自顶向下的增值测试:它首先对含读操作的子系统自底向上直至根结点模块进行组装和测试,然后对含写操作的子系统作自顶向下的组装与测试。
③ 回归测试:这种方式采取自顶向下的方式测试被修改的模块及其子模块,然后将这一部分视为子系统,再自底向上测试,以检查该子系统与其上级模块的接口是否适配。
3.组装测试的组织和实施
组装测试是一种正规测试过程,必须精心计划,并与单元测试的完成时间协调起来。在制定测试计划时,应考虑如下因素:
(1)采用何种系统组装方法进行组装测试。
(2)组装测试过程中连接各个模块的顺序。
(3)模块代码编制和测试进度是否与组装测试的顺序一 致。
(4)测试过程中是否需要专门的硬件设备。
解决了上述问题之后,就可以列出各个模块的编制、测试计划表,标明每个模块单元测试完成的日期、首次组装测试的日期、组装测试全部完成的日期,以及需要的测试用例和所期望的测试结果。
在完成预定的组装测试工作之后,测试小组应负责对测试结果进行整理、分析,形成测试报告。测试报告中要记录实际的测试结果,在测试中发现的问题,解决这些问题的方法以及解决之后再次测试的结果。此外,还应提出目前不能解决、还需要管理人员和开发人员注意的一些问题,提供测试评审和最终决策,以提出处理意见。
确认测试
确认测试(validation testing)又称有效性测试。它的任务是验证软件的有效性,即验证软件的功能和性能及其他特性是否与用户的要求一致。在确认测试阶段需要做的工作如下图所示。
从上图中可看出,首先要进行有效性测试以及软件配置复审,然后进行验收测试和安装测试,在通过了专家鉴定之后,才能成为可交付的软件。
1.进行有效性测试(黑盒测试)
有效性测试是在模拟的环境(可能就是开发的环境)下,运用黑盒测试的方法,验证被测软件是否满足需求规格说明书列出的需求。为此,需要首先制订测试计划,规定要进行测试的种类。还需要制订一组测试步骤,描述具体的测试用例。通过实施预定的测试计划和测试步骤,确定软件的特性是否与需求相符,确保所有的软件功能需求都能得到满足,所有的软件性能需求都能达到,所有的文档都正确且便于使用。同时,对其他软件需求,如可移植性、兼容性、出错自动恢复、可维护性等,也都要进行测试,确认是否满足。
2.软件配置复查
软件配置复查的目的是保证软件配置的所有成分都齐全,各方面的质量都符合要求,具有维护阶段所必须的细节,而且已经编排好分类的目录。
除了按合同规定的内容和要求,由人工审查软件配置之外,在确认测试的过程中,应当严格遵守用户手册和操作手册中规定的使用步骤,以便检查这些文档资料的完整性和正确性。必须仔细记录发现的遗漏和错误,并且适当地补充和改正。
3.a测试和b测试
在软件交付使用之后,用户将如何实际使用程序,对于开发者来说是无法预测的。如果软件是为多个用户开发的产品,让每个用户逐个执行正式的验收测试是不切实际的。很多软件产品生产者采用一种称之为a测试和b测试的测试方法,以发现可能只有最终用户才能发现的错误。
a测试是由一个用户在开发环境下进行的测试,也可以是公司内部的用户在模拟实际操作环境下进行的测试。软件在一个自然设置状态下使用,开发者坐在用户旁边,随时记下错误情况和使用中的问题。 a测试的目的是评价软件产品的FLURPS(即功能、局域化、可使用性、可靠性、性能和支持),尤其注重产品的界面和特色。
b测试是由软件的多个用户在一个或多个用户的实际使用环境下进行的测试。这些用户是与公司签定了支持产品预发行合同的外部客户。与a测试不同的是,开发者通常不在测试现场,由用户记下遇到的所有问题。开发者在综合用户的报告之后进行修改,最后将软件产品交付给全体用户使用。b测试主要衡量产品的FLURPS,着重于产品的支持性,包括文档、客户培训和支持产品生产能力。只有当a测试达到一定的可靠程度时,才能开始b测试。
由于b测试的主要目标是测试可支持性,所以b测试应尽可能由主持产品发行的人员管理。
4.验收测试
在通过了系统的有效性测试及软件配置审查之后,应开始系统的验收测试(acceptance testing)。验收测试是以用户为主的测试,软件开发人员和QA(质量保证)人员也应参加。由用户参加设计测试用例,使用用户界面输入测试数据,并分析测试的输出结果。一般使用生产中的实际数据进行测试,在测试过程中,除了考虑软件的功能和性能外,还应对软件的可移植性、兼容性、可维护性、错误的恢复功能等进行确认。
5.确认测试的结果
在全部确认测试的测试用例运行完后,所有的测试结果可以分为两类。
(1)测试结果与预期的结果相符,这说明软件的这部分功能或性能特征与需求规格说明书相符合,从而这部分程序可以接受。
(2)测试结果与预期的结果不符,这说明软件的这部分功能或性能特征与需求规格说明不一致,因此,需要开列一张软件各项缺陷表或软件问题报告,通过与用户的协商,解决所发现的缺陷和错误。
系统测试
系统测试(system testing)是将通过确认测试的软件,作为整个计算机系统的一个元素,与计算机硬件、外设、某些支持软件、数据、人员等其他系统元素结合在一起,在实际运行(使用)环境下,对计算机系统进行一系列的组装测试和确认测试。
系统测试的目的在于通过与系统的需求定义作比较,发现软件与系统定义不符合或与之矛盾的地方。系统测试的测试用例应根据系统的需求分析说明书设计,并在实际使用环境下运行。
测试的类型
软件测试实际上是由一系列不同的测试组成。几种常见的软件测试及它们与各个测试步骤中的关系如右图所示。
上图中各类测试的定义如下:
(1)功能测试(function testing):功能测试是在规定的一段时间内运行软件系统的所有功能,以验证这个软件系统有无严重错误。
(2)回归测试(regression testing):这种测试用于验证对软件修改后有没有引出新的错误,或者说,验证修改后的软件是否仍然满足系统的需求规格说明。
(3)可靠性测试(reliability testing):如果系统需求说明书中有对可靠性的要求,则需进行可靠性测试。通常使用平均失效间隔时间(MTBF)与因故障而停机的时间(MTTR)来度量系统的可靠性。
(4)强度测试(stress testing):也称压力测试,是要检查在系统运行环境恶劣的情况下,系统可以运行到何种程度的测试。因此,进行强度测试,需要提供非正常数量、频率或总量资源来运行系统。实际上,这是对软件的“超负荷”环境或临界环境的运行检验。
(5)性能测试(performance testing):是要检查系统是否满足在需求说明书中规定的性能。特别是对于实时系统或嵌入式系统,软件只满足要求的功能而达不到要求的性能是不可接受的,所以还需要进行性能测试。
(6)恢复测试(recovery testing):恢复测试是要证实在克服硬件故障(包括掉电、硬件或网络出错等)后,系统能否正常地继续进行工作,并不对系统造成任何损害。
(7)启动/停止测试(startup/shutdown testing):这类测试的目的是验证在机器启动及关机阶段,软件系统正确处理的能力。包括反复启动软件系统(例如,操作系统自举、网络的启动、应用程序的调用等),以及在尽可能多的情况下关机。
(8)配置测试(configuration testing):这类测试是要检查计算机系统内各个设备或各种资源之间的相互连接和功能分配中的错误。配置测试主要包括以下3种。
① 配置命令测试:验证全部配置命令的可操作性(有效性);特别对最大配置和最小配置要进行测试。软件配置 和硬件配置都要测试。
② 循环配置测试:证明对每个设备物理与逻辑的、逻辑 与功能的每次循环置换配置都能正常工作。
③ 修复测试:检查每种配置状态及哪个设备是坏的,并 用自动的或手工的方式进行配置状态间的转换。
(9)安全性测试(security testing):检验在系统中已经存在的系统安全性和保密性措施是否发挥作用,有无漏洞。为此要了解破坏安全性的方法和工具,并设计一些模拟测试用例对系统进行测试,力图破坏系统的保护机构以进入系统。
(10)可使用性测试(usability testing):可使用性测试主要从使用的合理性、方便性等角度对软件系统进行检查,以发现人为因素或使用上的问题。
(11)可支持性测试(supportability testing):验证系统的支持策略对于公司与用户方面是否切实可行。它所采用的方法是试运行支持过程(如对有错部分打补丁的过程,热线界面等),对其结果进行质量分析,评审诊断工具、维护过程、内部维护文档;衡量修复一个明显错误所需的平均最少时间。还有一种常用的方法是,在发行前把产品交给用户,向用户提供支持服务的计划,从用户处得到对支持服务的反馈。
(12)安装测试(installation testing):安装测试的目的不是查找软件错误,而是查找安装错误。在安装软件系统时,会有多种选择。要分配和装入文件与程序库,布置适用的硬件配置,进行程序的连接。而安装测试是要查找出在这些安装过程中出现的错误。
(13)互连测试(interoperability testing):验证两个或多个不同的系统之间的互连性。这类测试对支持标准规格说明,或承诺支持与其他系统互连的软件系统有效。
(14)兼容性测试(compatibility testing):验证软件产品在不同版本之间的兼容性。有两类基本的兼容性测试:向下兼容和交错兼容。向下兼容测试是测试软件新版本,保留它早期版本的功能的情况;交错兼容测试是要验证共同存在的两个相关但不同的产品之间的兼容性。
(15)容量测试(volume testing):容量测试是要检验系统的能力最高能达到什么程度。
(16)文档测试(documentation testing):检查用户文档(如用户手册)的清晰性和精确性。用户文档中所使用的例子必须在测试中一一试过,确保叙述正确无误。
10.6人工测试
人工测试不要求在计算机上实际执行被测程序,而是以一些人工的模拟技术和一些类似动态分析所使用的方法对程序进行分析和测试。
静态分析
静态分析是要对源程序进行静态检验。通常采用以下方法进行。
1.生成各种引用表
在源程序编制完成后生成各种引用表,这是为了支持对源程序进行静态分析。这些表可用手工方式从源程序中提取所需的信息,也可借助于专用的软件工具自动生成。引用表按功能分类,有以下3种。
(1)直接从表中查出说明/使用错误,如循环层次表、变量交叉引用表、标号交叉引用表等。
(2)为用户提供辅助信息,如子程序(宏、函数)引用表、等价(变量、标号)表、常数表等。
(3)用来作错误预测和程序复杂度计算,如操作符和操作数的统计表等。
常用的引用表有如下几种:
(1)标号交叉引用表:它列出在各模块中出现的全部标号。在表中标出标号的属性:已说明、未说明、已使用和未使用。表中还有在模块以外的全局标号、计算标号等。
(2)变量交叉引用表:即变量定义与引用表。在表中标明各变量的属性:已说明、未说明、隐式说明,以及类型及使用情况。进一步还可区分是否出现在赋值语句的右边,是否属于COMMON变量、全局变量或特权变量等。
(3)子程序、宏和函数表:在表中,各个子程序、宏和函数的属性:已定义、未定义和定义类型;参数表:输入参数的个数、顺序和类型;输出参数的个数、顺序和类型;已引用、未引用、引用次数等。
(4)等价表:表中列出在等价语句或等值语句中出现的全部变量和标号。
(5)常数表:在表中列出全部数字常数和字符常数,并指出它们在哪些语句中首先被定义,即首先出现在哪些赋值语句的左部或哪些数据语句或参数语句中。
2.静态错误分析
静态错误分析用于确定在源程序中是否有某类错误或“危险”结构,它有以下几种。
(1)类型和单位分析:为了发现源程序中数据类型、单位上的不一致性,建立一些程序语言的预处理程序,分析程序中在“下标”类型及循环控制变量方面的类型错误,以及通过使用一般的组合/消去规则,确定表达式的单位错误。
(2)引用分析:沿着程序的控制路径,检查程序变量的引用异常问题。
(3)表达式分析:对表达式进行分析,以发现和纠正在表达式中出现的错误,包括:
① 在表达式中不正确地使用了括号造成错误;
② 数组下标越界造成错误;
③ 除式为零造成错误;
④ 对负数开平方,或对p求正切值造成错误;
⑤ 浮点数计算的误差。
(4)接口分析:分析接口的一致性错误,包括:
① 模块之间接口的一致性和模块与外部数据库之间接口 的一致性;
② 过程和函数过程之间接口的一致性,全局变量和公共数据区在使用上的一致性。
人工测试的几种形式
静态分析中进行人工测试的主要方法有桌前检查、代码评审和走查。经验表明,使用这种方法能够有效地发现30%~70%的逻辑设计和编码错误。
1.桌前检查
桌前检查(desk checking)是一种传统的检查方法,由程序员自己检查自己编写的程序。程序员在程序通过编译之后,进行单元测试设计之前,对源程序代码进行分析、检验并补充相关的文档,目的是发现程序中的错误。检查项目包括如下内容:
(1)检查变量的交叉引用;
(2)检查标号的交叉引用;
(3)检查子程序、宏结构、函数;
(4)常量检查;
(5)标准检查;
(6)风格检查;
(7)比较控制流;
(8)选择、激活路径;
(9)对照程序的规格说明,详细阅读源代码;
(10)补充文档;
2.代码评审
代码评审(code reading review)是由若干程序员和测试员组成一个评审小组,通过阅读、讨论和争议,对程序进行静态分析的过程。
代码评审分两步:
小组负责人提前把设计规格说明书、控制流程图、程序文本及有关要求、规范等分发给小组成员,作为评审的依据;
召开程序评审会。在会上,由程序员逐句讲解程序的逻辑。在此过程中,程序员或其他小组成员可以提出问题,展开讨论,审查错误是否存在。
在会前,应当给评审小组每个成员准备一份常见错误的清单。这个常见错误清单也叫做检查表,它把程序中可能发生的各种错误进行分类,对每一类列举出尽可能多的典型错误,然后把它们制成表格,供会审时使用。这种检查表类似于本章单元测试中给出的检查表。在代码评审之后,需要做以下几件事。
(1)把发现的错误登记造表,并交给程序员。
(2)若发现错误较多,或发现重大错误,则在改正之后,再次组织代码评审。
(3)对错误登记表进行分析、归类、精练,以提高审议效果。
3.走查
走查(walkthroughs)与代码评审基本相同,其过程分为两步。
(1)把材料先发给走查小组每个成员,让他们认真研究程序,然后再开会。开会的议程与代码评审不同,不是简单地读程序和对照错误检查表进行检查,而是让与会者“充当”计算机,即首先由测试组成员为被测程序准备一批有代表性的测试用例,提交给走查小组。走查小组开会,集体扮演计算机角色,让测试用例沿程序的逻辑运行一遍,随时记录程序的踪迹,供分析和讨论用。
(2)人们借助于测试用例的媒介作用,对程序的逻辑和功能提出各种疑问,结合问题开展热烈的讨论和争议,能够发现更多的问题。
10.7调试
调试(debug)也称排错或纠错,它是紧跟在测试之后要做的工作,但与测试不同之处在于:测试着重于发现软件中有错,发现异常或软件运行的可疑之处;而调试的任务在于为错误确切地定位,找到出错的根源,并且通过修改程序将其排除。
一般地,调试的步骤如下:
(1)针对测试提供的信息,分析错误的外部表现形式,确定程序出错的位置。
(2)研究程序的相关部分,找出导致错误的内在原因。
(3)修改相关的程序段,如果是设计导致的错误,则需修改相关的设计,以排除错误。
(4)重复执行以前发现错误的测试,以确认:
① 该错误确已通过修改而消除;
② 这次修改并未引进新的错误。
(5)如果重新测试表明修改无效,发生错误的现象仍然出现,则要撤销上述修改,再次进行信息分析,实施上述过程,直至修改有效为止。
Part5.软件维护与软件管理
11.软件维护
11.1软件维护的概念
软件维护在软件运行/维护阶段对软件产品所进行的修改就是所谓的维护。根据维护工作的性质,软件维护的活动可以分为以下4种类型。
1.改正性维护
改正性维护(corrective maintenance)为了识别和纠正软件错误、改正软件性能上的缺陷、排除实施中的误使用,应进行的诊断和改正错误的过程。例如,改正性维护可以是改正原来程序中开关使用的错误;解决开发时未能测试各种可能情况带来的问题等。
2.适应性维护
随着信息技术的飞速发展,软件运行的外部环境(新的硬、软件配置)或数据环境(数据库、数据格式、数据输入/输出方式、数据存储介质)可能发生变化,为了使软件适应这种变化,而修改软件的过程叫做适应性维护(adaptive maintenance)。例如,需要对已运行的软件进行改造,以适应网络环境或已升级改版的操作系统要求。
3.完善性维护
为了满足新的功能与性能要求,需要修改或再开发软件,以扩充软件功能、增强软件性能、改进加工效率、提高软件的可维护性。这种情况下进行的维护活动叫做完善性维护(perfective maintenance)。例如,完善性维护可能是修改一个计算工资的程序,使其增加新的扣除项目;缩短系统的应答时间,使其达到特定的要求等。
4.预防性维护
预防性维护(preventive maintenance)是指把今天的方法学用于昨天的系统以满足明天的需要。也就是说,采用先进的软件工程方法对需要维护的软件或软件中的某一部分(重新)进行设计、编码和测试。
各类维护占总维护工作量的比例
在整个软件维护阶段花费的全部工作量中,预防性维护只占很小的比例,而完善性维护占了几乎一半的工作量。
维护工作量在软件生存期中所占比例
软件维护活动花费的工作量占整个生存期工作量的70%以上(工作量的比例直接反映了成本的比例)
影响维护工作量的因素
在软件维护中,影响维护工作量的因素主要有以下6种:
(1)系统规模。
(2)程序设计语言。
(3)系统年龄大小。
(4)数据库技术的应用水平。
(5)所采用的软件开发技术及软件开发工程化的程度。
(6)其他:如应用的类型、数学模型、任务的难度、IF嵌套深度、索引或下标数等,对维护工作量都有影响。
软件维护的策略
根据影响软件维护工作量的各种因素,针对3种典型维护,James Martin等提出了一些策略,以控制维护成本。
1.改正性维护
应用一些诸如数据库管理系统、软件开发环境、程序自动生成系统和高级(第四代)语言等新技术可大大提高可靠性,并减少进行改正性维护的需要。此外,还可考虑利用应用软件包、防错性程序设计、通过周期性维护审查等策略。
2.适应性维护
这一类的维护不可避免,但可以采用以下策略加以控制。
(1)在配置管理时,把硬件、操作系统和其他相关环境因素的可能变化考虑在内,可以减少某些适应性维护的工作量。
(2)把与硬件、操作系统,以及其他外围设备有关的程序归到特定的程序模块中。可把因环境变化而必须修改的程序局部于某些程序模块之中。
(3)使用内部程序列表、外部文件,以及处理的例行程序包,可为维护时修改程序提供方便。
(4)使用面向对象技术,增强软件系统的稳定性,易于修改和移植。
3.完善性维护
利用前两类维护中列举的方法,也可以减少这一类维护。特别是数据库管理系统、程序生成器、应用软件包,可减少系统或程序员的维护工作量。
此外,建立软件系统的原型,把它在实际系统开发之前提供给用户。用户通过研究原型,进一步完善他们的功能要求,可以减少以后完善性维护的需要。
11.2软件维护活动
软件维护申请报告
所有软件维护申请应按规定的方式提出。软件维护组织通常提供维护申请报告(maintenance request form,MRF),或称软件问题报告,由申请维护的用户填写。
如果遇到一个错误,用户必须完整地说明产生错误的情况,包括输入数据、错误清单以及其他有关材料。
如果申请的是适应性维护或完善性维护,用户必须提出一份修改说明书,列出所有希望的修改。维护申请报告将由维护管理员和系统监督员来研究处理。
维护申请报告是由软件组织外部提交的文档,它是计划维护工作的基础。软件组织内部应相应地做出软件修改报告(software change report,SCR),指明:
● 所需修改变动的性质;
● 申请修改的优先级;
● 为满足某个维护申请报告,所需的工作量;
● 预计修改后的状况。
软件修改报告应提交修改负责人,经批准后才能开始进一步安排维护工作。
软件维护工作流程
在每次软件维护任务完成后,最好进行一次情况评审,对以下问题做一总结:
在目前情况下,设计、编码、测试中的哪一方面可以改进?
哪些维护资源应该有,但没有?
工作中主要的或次要的障碍是什么?
从维护申请的类型来看是否应当有预防性维护?
情况评审对将来的维护工作如何进行会产生重要的影响,并可为软件机构的有效管理提供重要的反馈信息。
维护档案记录
内容包括程序名称、源程序语句条数、机器代码指令条数、所用的程序设计语言、程序安装的日期、程序安装后的运行次数、与程序安装后运行次数有关的处理故障次数、程序改变的层次及名称、修改程序所增加的源程序语句条数、修改程序所减少的源程序语句条数、每次修改所付出的“人时”数、修改程序的日期、软件维护人员的姓名、维护申请报告的名称、维护类型、维护开始时间和维护结束时间、花费在维护上的累计“人时”数、维护工作的净收益等。对每项维护任务都应该收集上述数据。
维护评价
评价维护活动可参考的度量值有:
- 每次程序运行时的平均出错次数;
- 花费在每类维护上的总“人时”数;
- 每个程序、每种语言、每种维护类型的程序平均修改次数;
- 因为维护,增加或删除每个源程序语句所花费的平均“人时”数;
- 用于每种语言的平均“人时”数;
- 维护申请报告的平均处理时间;
- 各类维护申请的百分比。
11.3程序修改的步骤及副作用
为了正确、有效地进行程序修改,需要经历3个步骤:分析和理解程序、实施修改以及重新验证程序。
分析和理解程序
经过分析,全面、准确、迅速地理解程序是决定维护成败和质量好坏的关键。在这方面,软件的可理解性和文档的质量非常重要。为此必须:
(1)研究程序的使用环境及有关资料,尽可能得到更多的背景信息;
(2)理解程序的功能和目标;
(3)掌握程序的结构信息,即从程序中细分出若干结构成分,如程序系统结构、控制结构、数据结构和输入/输出结构等;
(4)了解数据流信息,即所涉及的数据来自何处,在哪里被使用;
(5)了解控制流信息,即执行每条路径的结果;
(6)如果设计存在,则可利用它们来帮助画出结构图和高层流程图;
(7)理解程序的操作(使用)要求。
为了容易地理解程序,要求自顶向下地理解现有源程序的程序结构和数据结构,为此可采用如下几种方法。
(1)分析程序结构图。
(2)数据跟踪。
(3)控制跟踪。可采用符号执行或实际动态跟踪的方法,了解数据是如何从一个输入源到达输出点的。
(4)在分析的过程中,应充分阅读和使用源程序清单和文档,分析现有文档的合理性。
(5)充分使用由编译程序或汇编程序提供的交叉引用表、符号表,以及其他有用的信息。
(6)如有可能,争取参加开发工作。
修改程序
对程序的修改,必须事先做出计划,有准备地、周密有效
地实施修改。
1.设计程序的修改计划
程序的修改计划要考虑人员和资源的安排。修改计划的内容主要包括以下几项:
(1)规格说明信息:数据修改、处理修改、作业控制语言修改、系统之间接口的修改等。
(2)维护资源:新程序版本、测试数据、所需的软件系统、计算机时间等。
(3)人员:程序员、用户相关人员、技术支持人员、厂家联系人、数据录入员等。
(4)提供:纸质、计算机媒体等。
针对以上每一项,要说明必要性、从何处着手、是否接受、日期等。通常,可采用自顶向下的方法,在理解程序的基础上做如下工作:
(1)研究程序的各个模块、模块的接口及数据库,从全局的观点提出修改计划。
(2)依次把要修改的、以及那些受修改影响的模块和数据结构分离出来。
(3)详细地分析要修改的,以及那些受变更影响的模块和数据结构的内部细节,设计修改计划,标明新逻辑及 要改动的现有逻辑。
(4)向用户提供回避措施。用户的某些业务因软件中发生问题而中断,为不让系统长时间停止运行,需把问题局部化,在可能的范围内继续开展业务。
2.修改代码,以适应变化
(1)正确、有效地编写修改代码;
(2)要谨慎地修改程序,尽量保持程序的风格及格式,要在程序清单上注明改动的指令;
(3)不要匆忙删除程序语句,除非完全肯定它是无用的;
(4)不要试图共用程序中已有的临时变量或工作区,为了避免冲突或混淆用途,应自行设置自己的变量;
(5)插入错误检测语句;
(6)保持详细的维护活动和维护结果记录;
(7)如果程序结构混乱,修改受到干扰,可抛弃程序重新编写。
修改程序的副作用及其控制
所谓程序修改的副作用是指因修改软件而造成的错误或其他不希望发生的情况,有以下3种副作用:
1.修改代码的副作用
在使用程序设计语言修改源代码时,都可能引入新的错误。例如,删除或修改一个子程序、删除或修改一个标号、删除或修改一个标识符、改变程序代码的时序关系、改变占用存储的大小、改变逻辑运算符、修改文件的打开或关闭、改进程序的执行效率,以及把设计上的改变翻译成代码的改变、为边界条件的逻辑测试做出改变时,都容易引入错误。
2.修改数据的副作用
在修改数据结构时,有可能造成软件设计与数据结构不匹配,因而导致软件出错。修改数据的副作用是修改软件信息结构导致的结果。例如,在重新定义局部的或全局的常量、重新定义记录或文件的格式、增大或减小一个数组或高层数据结构的大小、修改全局或公共数据、重新初始化控制标志或指针、重新排列输入/输出或子程序的参数时,容易导致设计与数据不相容的错误。数据副作用可以通过详细的设计文档加以控制。
3.修改文档的副作用
对数据流、软件结构、模块逻辑或任何其他有关特性进行修改时,必须对相关技术文档进行相应修改。如果对可执行软件的修改不反映在文档里,会产生文档的副作用。例如,对交互输入的顺序或格式进行修改,如果没有正确地记入文档中,可能引起重大的问题。过时的文档内容、索引和文本可能造成冲突,引起用户业务的失败和不满。因此,必须在软件交付之前对整个软件配置进行评审,以减少文档的副作用。
为了控制因修改而引起的副作用,要做到:
(1)按模块把修改分组;
(2)自顶向下地安排被修改模块的顺序;
(3)每次修改一个模块;
(4)对于每个修改了的模块,在安排修改下一个模块之前,要确定这个修改的副作用,可以使用交叉引用表、存储映象表、执行流程跟踪等。
重新验证程序
1.静态确认
修改的软件,通常伴随着引起新的错误的危险。为了能够做出正确的判定,验证修改后的程序至少需要两个人参加。要检查:
(1)修改是否涉及规格说明?修改结果是否符合规格说明?有没有歪曲规格说明?
(2)程序的修改是否足以修正软件中的问题?源程序代码有无逻辑错误?修改时有无修补失误?
(3)修改部分对其他部分有无不良影响(副作用)?对软件进行修改,常常会引发别的问题,因此,有必要 检查修改的影响范围。
2.确认测试
在充分进行了以上确认的基础上,要用计算机对修改程 序进行确认测试。
(1)确认测试顺序:先对修改部分进行测试,然后隔离修改部分,测试程序的未修改部分,最后再把它们集成起来进行测试。这种测试称为回归测试。
(2)准备标准的测试用例。
(3)充分利用软件工具帮助重新验证过程。
(4)在重新确认过程中,需邀请用户参加。
3.维护后的验收
在交付新软件之前,维护主管部门要检验:
(1)全部文档是否完备,并已更新;
(2)所有测试用例和测试结果已经正确记载;
(3)记录软件配置所有副本的工作已经完成;
(4)维护工序和责任是明确的。
11.4软件的维护性
软件维护性的定义
软件维护性是指当对软件实施各种类型的维护而进行修改时,软件产品可被修改的能力。
许多软件的维护十分困难,原因在于这些软件的文档和源程序难于理解,又难于修改。从原则上讲,软件开发工作应严格按照软件工程的要求,遵循特定的软件标准或规范进行。但实际上往往由于种种原因并不能真正做到,如文档不全、质量差、开发过程不注意采用先进的方法,忽视程序设计风格等,因此,造成软件维护工作量加大,成本上升,修改出错率升高。此外,许多维护要求并不是因为程序中出错而提出的,而是为适应环境变化或需求变化而提出的。由于维护工作面广,维护难度大,稍有不慎,就会在修改中给软件带来新的问题或引入新的差错,所以,为了使得软件能够易于维护,必须考虑使软件具有可维护性。
软件维护的子特性:
软件维护性度量的任务是对软件产品的维护性给出量化的评价。
软件维护的度量也分为内部维护性度量和外部维护性度量,两者的差别如下表。
11.5提高软件维护性的方法
使用提高软件质量的技术和工具
1.模块化
模块化技术的优点是如果需要改变某个模块的功能,则只要改变这个模块,对其他模块影响很小;如果需要增加程序的某些功能,则仅需增加完成这些功能的新的模块或模块层;程序的测试与重复测试比较容易;程序错误易于定位和纠正;容易提高程序效率。
2.结构化程序设计
结构化程序设计不仅使得模块结构标准化,而且将模块间的相互作用也标准化了,因而把模块化又向前推进了一步。采用结构化程序设计可以获得良好的程序结构。
3.使用结构化程序设计技术,提高现有系统的可维护性
(1)采用备用件的方法——当要修改某一个模块时,用一个新的结构良好的模块替换掉整个模块。
(2)采用自动重建结构和重新格式化的工具(结构更新技术)。
(3)改进现有程序的不完善的文档。
(4)使用结构化程序设计方法实现新的子系统。
(5)采用结构化小组。
实施开发阶段产品的维护性审查
质量保证审查除了保证软件得到适当的质量外,还可以用来检测在开发和维护阶段内发生的质量变化。一旦检测出问题来,就可以采取措施纠正,以控制不断增长的软件维护成本。为了保证软件的可维护性,有4种类型的软件审查。
1.检查点审查
保证软件质量的最佳方法是在软件开发的最初阶段就把质量要求考虑进去,并在开发过程每一个阶段的终点,设置检查点进行检查。
检查的目的是要证实,已开发的软件是否符合标准,是否满足规定的质量需求。
在不同的检查点,检查的重点不完全相同,例如,在设计阶段,检查重点是可理解性、可修改性、可测试性。可理解性检查的重点是程序的复杂性。如下图所示。
2.验收检查
验收检查是一个特殊的检查点的检查,是交付使用前的最后一次检查,是软件投入运行之前保证可维护性的最后机会。以下是验收检查必须遵循的最小验收标准。
(1)需求和规范标准
① 需求应当以可测试的术语进行书写,按优先次序排列和定义。
② 区分必须的、任选的、将来的需求。
③ 包括对系统运行时的计算机设备的需求;对维护、测试、操作,以及维护人员的需求;对测试工具等的需求。
(2)设计标准
① 程序应设计成分层的模块结构。每个模块应完成唯一的功能,并达到高内聚、低耦合。
② 通过一些知道预期变化的实例,说明设计的可扩充性、可缩减性和可适应性。
(3)源代码标准
① 尽可能使用程序设计语言的标准版本。
② 所有的代码都必须具有良好的结构。
③ 所有的代码都必须文档化,在注释中说明它的输入、输出,以及便于测试/再测试的一些特点与风格。
(4)文档标准
文档中应说明程序的输入/输出、使用的方法/算法、错误恢复方法、所有参数的范围、默认条件等。
3.周期性地维护审查
检查点复查和验收检查,可用来保证新软件系统的可维护性。对已有的软件系统,则应当进行周期性的维护检查。
软件在运行期间,必须对软件做周期性的维护审查,以跟踪软件质量的变化。
周期性维护审查实际上是开发阶段检查点复查的继续,并且采用的检查方法、检查内容都是相同的。
4.对软件包进行检查
软件包是一种标准化了的、可为不同单位、不同用户使用的封装软件。
使用单位的维护人员首先要仔细分析、研究开发商提供的用户手册、操作手册、培训教程、新版本说明、计算机环境要求书,以及开发商提供的验收测试报告等,在此基础上,深入了解本单位的希望和要求,编制软件包的检验程序。该检验程序检查软件包程序所执行的功能是否与用户的要求和条件相一致。
改进文档
程序文档是对程序总目标、程序各组成部分之间的关系、程序设计策略、程序实现过程的历史数据等的说明和补充。程序文档对提高程序的可理解性有着十分重要作用。在软件维护阶段,利用历史文档,可以大大简化维护工作。
历史文档有如下3种:
(1)系统开发日志:它记录了项目的开发原则、开发目标、优先次序、选择某种设计方案的理由、决策策略、使用的测试技术和工具、每天出现的问题、计划的成功和失败之处等。
(2)错误记载:它把出错的历史情况记录下来,对于预测今后可能发生的错误类型及出错频率有很大帮助。也有助于维护人员查明出现故障的程序或模块,以便去修改或替换它们。
(3)系统维护日志:记录了在维护阶段有关系统修改和修改目的的信息。包括修改的宗旨、修改的策略、存在的问题、问题所在的位置、解决问题的办法、修改要求和说明、注意事项、新版本说明等信息。