软件工程学习
绪论
0.1软件工程的背景
从定义上说,软件工程是一门工程学,因此它和其它工程学科有一系列相同的职责。一个相同的特征是都必须有一个彻底说明要做什么东西的过程,及需求分析;但另外,软件项目受制于非常频繁的变更
0.2 软件工程的活动
定义要使用的软件开发过程
管理项目的开发活动
表述想要的软件产品
设计产品
产品实现,即编程
测试产品的单独模块
产品集成并做整体测试
产品维护
软件工程涉及4P,即People (人),Process (过程), Project (项目), Product (产品)
软件开发工作的产品不仅仅包含目标代码和源代码。文档、测试结果和生产率的度量也是。 0.3 过程
瀑布过程、迭代过程、增量过程、螺旋过程、…
0.4 项目(Project)
项目是为了生产所需要的工件应该采取的一组活动的集合。它包括和客户交流、撰写文 档、设计、编码和对产品做测试。
0.5 人员 (People)
软件项目中涉及到人与人之间的交互作用对于项目的成功与否有着深切的影响。人员问题影响项目的另一个因素与项目的风险承担者有关。
0.6 产品 (Product)
软件过程各阶段的输出都是产品。
第一章:过程
1.1 区分各种开发的过程,指出各自的优缺点
瀑布过程模型:概念分析,分析,设计,实现(编码),单元测试,集成,系统集成,维护;易于控制文档
迭代过程模型:不断重复的瀑布过程被称为迭代模型;能够在每次迭代中都收集到过程中产生各种度量。
螺旋过程模型:该过程需要经历多次需求分析/设计/实现/测试这组顺序活动。这样
做的主要原因是基于规避风险的需要;缺点:每次都要保持文档的一致性。
增量过程模型:在迭代过程模型基础上,每次迭代只是前一次的基础上增加少量功
能。
统一软件开发过程(面向对象开发过程)
1.2 量化定义软件的质量
1.3 理解所需要的文档
第二章:项目管理
2.1 项目管理简介
2.1.1 项目管理的含义
项目管理是指在有限的时间和资金内管理产品的生产过程。因为需要人力资源,所以项目管理不仅涉及到技术和组织方面的技能,而且涉及到管理人员的艺术。
2.1.2 项目管理的要素
结构(涉及到组织元素)
管理过程(参与人员的职责和监督)
开发过程(方法、工具、语言、文档和支持)
进度(开展工作各部分的时间安排)
2.1.3 主要变量:成本、性能、质量和进度
2.1.4 项目管理过程的典型路线图
1. 了解项目内容、范围和期限
2. 确定开发过程(方法、工具、语言、文档和支持)
3. 设定组织结构
4. 明确管理过程(各参与人员的职责)
5. 制定时间表(工作各部分的时间安排)
6. 制定人力计划
7. 风险管理
8. 确定要生产的文档
9. 开始执行项目
2.2管理项目人员
2.2.1 专业精神
2.2.2 人员管理的重要性
开发软件需要的首要因素是人员
2.2.3 企业的视角
2.2.4 管理层的视角
2.2.5 工程师的视角
2.3 组织人员的选择
2.3.1 沟通管理
2.3.2 职责结构的选择
2.3.3 项目人员的来源
2.4 识别和规避风险
2.4.1 风险定义
风险就是在项目过程中有可能发生的某些意外事情,而且在最糟的情况下将对项目产生巨大的负面影响甚至导致失败。风险有两种类型
能够避免或则绕开的风险(“被消除”),比如项目领导离开公司
不能避免的风险,比如没法做的事情
识别第二类风险可以在项目浪费资源之前就终止它。
2.4.2 风险管理概论
识别(心态:设法持续的识别风险)
规避计划
划分优先级
消除或者减弱风险
2.4.3 风险识别
2.4.4 风险规避
风险规避是指减轻甚至消除风险的过程。有两种方法:一时改变项目的需求,这样引起 风险的问题就不再存在;第二种方法是通过技术开发解决这个问题。
2.5 选择开发工具和支持
2.5.1 过程方法:可选的方法有瀑布、螺旋、统一和增量
2.5.2 工具
2.5.3 抉择:开发还是购买
2.5.4 语言选择
2.5.5 文档
2.5.6 支持服务
一个项目需要众多人的支持,包括系统管理员、网络管理员、数据库管理员、秘书等等。项目经理需要确保能够获得这些人员的服务。
2.6 创建时间表:概要的计划
1. 标出必须关注的里程碑
通常包括交付日期
2. 基于上述内容引入所需要的里程碑
例如:在交付之前做好系统测试
3. 列出第1次迭代
通常应该在功能上保持简单
好处:磨练自身的开发过程
4. 列出识别和规避风险的任务
从项目启动就开始
5. 标出中期附近还没有分配任务的时间(例如,周)
6. 完成时间表
第三章 需求分析(一)
本章讨论对于软件需求的整体分析。
3.1.1 需求分析的含义
要建造某个事物,必须首先了解这个“事物”是什么样子。了解并用文档描述这个事物的过程被称为“需求分析”。需求通常表达应用是用来做什么的;需求分析的输出是一份文档,通常被称为需求规格说明书或者软件需求规格说明书(SRS)。
3.1.2 C需求和D需求
将需求分为两个层次,第一层记录了客户的需要和要求,使用他们明白的语言进行描述。这个结果被称为“客户需求”或者“C需求”。C需求的首要读者是客户群体,其次是开发人员。第二层需求则按明确的、有组织的形式来记录。他们被称为“开发人员需求”或者“D需求”。D需求的首要读者是开发人员,其次是客户群体。
3.1.3 为什么必须书写需求
没有这样的文档,小组无法真正了解需要完成的目标是什么,无法正确地检查产品,无法正确地测试产品,无法跟踪生产效率,无法获得足够的实践数据,无法估计下一项工作的规模和工作量,更无法满足客户的要求。
3.1.4 典型的需求分析过程路线图
1.识别“客户”
2.访谈“客户代表”
识别需要和要求
使用工具帮助表达
绘制GUI草图
确定硬件环境
3. 用标准文档格式撰写C需求
4. 检查C需求(当客户批准后)
5. 构建D需求
3.1.5 需求分析的挑战和益处
使开发人员和客户将要开发的应用取得理解并达成一致。含有缺陷的需求将导致项目代价增加20到50倍。
3.2 与客户交流
3.2.1 需求的来源
人是需求的唯一来源
3.2.2 识别风险承担者
对于项目的成果承担风险和享有利益的人被称做风险承担者。因此,它们是应用的“客户”:使用该软件的人,公司的业主,应用开发人员etc.
3.3 描述客户(C)需求
3.3.2 用例
需求通常可以被看成是应用和应用的外部代理(例如用户)之间的交互。用用例图可以描述。
3.3.3 数据流图(为与客户沟通所用)
某些需求可以是很自然地描述为进程元素之间的数据流。在数据流图中,节点表示进程单元,用圆形或者矩形表示。节点之间的箭头代表数据流,箭头上标注数据的类型。数据存储用一对水平的平行线表示,平行线之间是数据储存的名字,代表数据存放的位置。外部代理用矩形表示。
3.3.4 状态变迁图(为与客户沟通所用)
对于事件驱动型的应用,采用状态变迁图来描述是非常有效的。(自动机和Petri网的变形)
3.3.5 草拟用户界面的其它接口
客户通常在看到应用的图形界面(GUI)时才能够想象这个应用未来的样子,所以帮助客户描述应用的一个好办法就是开发GUI的草稿。
3.3.6
1/2
如果需求涉及到用户和应用软件之间的交互,则通过用例进行描述
1. 给用例命名。
2. 识别用例的“主角”。(外部的使用角色——通常是一个人)
3. 描述用户和应用的活动系列
尽量减少分支
使用通用的形式
2/2
如果需求涉及到进程元素,并需要获得输入并产生输出,则使用数据流图进行描C需求表述总结和指南
述。
1. 识别进程元素(通常是概要的):使用圆形或者矩形表示
2. 识别数据的来源和去向:使用位于一对平行线之间的名称来表示
3. 在进程之间标识数据的流向,说明每个数据的类型
若需要涉及到应用软件的全部状态(或者部分状态)
1. 识别状态 (每个状态通常都是一个动词的被动形式):使用圆角的矩形来表
示。
2. 用特殊的箭头标识初始状态
3. 识别引起状态变迁的事件(指模块外部发生的事情):使用带有标识的箭头来
表示它
4. 识别子状态:使用嵌套的矩形表示。
3.4 应用于C需求的方法论、工具和网络
有大量的方法论可以用于表述需求。这里仅给出关于其中一部分方法论的总结。
结构化分析给数据流和功能分解定义了正式的形式。特别是结构化分析和设计技术
是处理体系规格的一种系统方法。结构化分析方法主要是从系统功能的角度来观察应用软件,这个功能视图最终将被分层的详细功能所实现;
实时系统可以通过状态变迁图得到有效的描述。
3.5
3.5.1 快速原型、可行性研究和概念证明 “快速原型”是对目标软件的部分实现,通常会涉及到重要的图形用户界面
(GUI)部分。建立快速原型能够有效地获取客户的需求,并且识别和规避部分
项目的风险。增进对客户真实需求的充分理解能够省去因需求误会导致的昂贵
的返工费用,并可提前消除未来可能引发的障碍。(原型越详细,客户的需求就
越容易被理解)。
3.5.2 可行性研究
有些时候我们根本无法确定客户所提出的需求是否能够真正实现。换句话说,项目都存在一个整体风险,而不是若干集中于某些特定需求的风险。而且,如果这个风险成为现实的话,那么这个项目就变的不可行——不值得开发。在这种情况下,就必须对项目进行可行性研究
第四章 需求分析(二)
4.1 详细D需求简介
4.1.1 详细D需求的含义
软件工程师基于一个基础来进行软件设计和实现,这个基础就是由详细需求构成的。它们也称为“细节需求”、“功能规格说明”、“面向开发人员的需求”或者“D需求”。D需求
是一个系统所必须的详细属性和功能的完整列表,它描述了最终的需求细节。其中的每条需求都进行了编号、标识,并且在开发过程中始终保持跟踪。D需求是从C需求扩展而来的,与C需求保持一致。
4.1.2 D需求分析的典型路线图
选择D需求的组织方式
根据用例生成序列图
3a. 从C需求中和客户处获得D需求
3b. 概述测试计划
3c. 核查
交由客户确认
4.2 D需求的类型
1. 功能性需求
应用软件的功能
2. 非功能性需求
2.1 性能
速度
能力(通信速率)
储存占用
a. 内存,b. 硬盘
2.2 可靠性和易用性
2.3 出错性
2.4 接口需求
应用软件与客户如何交互
与其它应用如何接口
2.5 限制
精确性
工具和语言限制
设计限制
采用标准
使用的硬件平台
3. 反面需求
反面需求说明软件不应该去做那些事情。从理论上说,反面需求应该有无数条,我们选择那些可以反面说明真正的需求,以及能够消除可能存在的误解的反面需求。
4.3 D需求的预期属性
我们已知遗漏需求可能对项目造成巨大的影响。为了确保细节得到足够的覆盖,这里 我们介绍D需求应该具备的属性。特别要注意,D需求应该保持整体完整性和一致性。每一条需求都应该能够追溯到设计和实现,能够测试它们的正确性,而且按照恰当的优先顺序来实现这些需求。在实施质量评审时,我们将对需求的这些属性进行评审和检查。
4.3.1跟踪功能性需求
把需求隐射到相关的设计和实现模块中的能力称之为“可追塑性”。实现这种映射的方法之一是将每条功能性需求对应到目标语言的具体函数中。随着项目的发展,需求文档需要与设计和实现部分保持一致。有些时候为了满足一条需求做出的修改可能会影响到其它需求的实现。正是因为这种多对多的关系非常难以管理,所以我们要设法在需求和函数之间一一映射。
4.3.1.2 跟踪非功能性需求(可以是性能要求)
上述讨论关注的是功能性需求。设计阶段的目标之一是把每一条非功能性需求对应到单独的设计元素中。对于性能需求,需要去测试速度最慢的处理单元。为了确认非功能性需求,需要把每条需求和一份测试计划联系起来,并最好是在撰写需求的时候进行这个工作。
4.3.2 可测试性和清晰性
一条需求必须能够通过测试加以验证,表明该需求得到了正确的实现。只有当一个D需求描述的清晰而且没有二义性,我们才能肯定它是否被正确地实现
4.3.3 优先级
通常很难按照进度实现应用软件计划的预算的全部需求。如果进度、成本预算和质量水平都不能变动的话,惟一选择就是改变产品的能力,即:削减预计实现的部分需求。选择那些需求进行削减的过程是有计划地进行的。其中的一种技术就是划分详细需求的优先级别。但是通常区分所有需求的优先级往往会很浪费时间,所以,很多组织把需求分为三个类别(有时是四个):“基本的”、“希望的”和“可选的”。首先我们要确保项目实现“基本的”需求。需求的优先级会影响到设计,因为希望的和可选的需求通常暗示了应用软件发展的方向。
4.3.4 完整性
我们尽力想要使每一条详细需求保持独立,但是在现实中这是不可能的,因为一个需求常常要提到其它需求。一个需求集合的完整性是指该集合中不存在会对集合中所叙述的需求造成危害的遗漏情况。
4.3.5 出错条件
对于每一条需求,我们都要思考如果它在错误的环境下实现时会发生什么事情。良好的需求分析应该能够正确处理“非法”输入。
4.3.6 一致性
一组D需求是一致的,指的是它们之间没有矛盾冲突。但是随着D需求的数量的增长,保持它的一致性将会变得越来越困难。可以按照面向对象的方式来组织需求避免不一致性,
面向对象的方式把D需求划分为若干类。
4.3.7 总结详细需求的撰写过程
撰写一条详细需求(1)
1.把需求归类于功能性或者非功能性的
IEEE SRS 提示了大部分非功能性需求
选择组织功能性需求的方法
2. 详细的控制规模
一条功能性需求基本对应于类中的一个方法
过大:难以管理
过小:不值得单独跟踪
3.尽量保持可追溯性
确保适于跟踪至设计和实现
4. 使之可被测试
草拟测试要点,用于确认需求得到满足
撰写一条详细需求(2)
5. 确保需求含义清晰、不模糊
意图不会被误解
6. 划定需求的优先级
例如:最高(“最基本的”);最低(“可选的”);中等(“希望的”)
7. 检查需求集合的完整性
对每条需求,确保其它必要的相关需求都已经包括在集合之中
8. 包含出错条件
说明非正常情况的具体特征
包括关键位置的编程错误
9. 检查一致性
确保每条需求不会对其它任何需求的任何方面造成矛盾
备注:
1. 关于需求的组织方法在开始撰写D需求之前就必须确定下来
2. 评估一条需求是否可追溯就是构想该需求的设计并且想象设计如何能够满足这条需
求。如果这条需求直接对应类中的一个方法,那么这种追溯性的评估最为简单。
3. 它有助于在撰写需求的同时概括描述该需求的测试要点。这样不仅能够使需求的含义
更加清晰,而且还可以检查需求是否可测试
4. 很多需求依赖于特定的数据,因此我们需要指出当输入数据错误或者不一致的情况下
这条需求应该如何处理。对于关键需求,还应该包括由于低劣的设计和编程引起的错
误
4.4 序列图
序列图是对控制流的一种图形化表示方法,对于表示用例的执行步骤尤其有用。并且序列图除了可以用于进行需求分析,如本章所描述,还可以通过更详细的形式来用于设计。
序列图要求我们用对象的角度进行思考。在序列图中,每个所涉及的对象的生命周期都分别用一条垂直的实线来表示,实线的顶端标有对象的名称和它所属的类的名称。对象之间的每次交互则通过一条带有箭头的水平线来表示,这条带箭头的线把呼叫服务的对象和提供服务的对象连接起来。(操作:表示由箭头线起点的对象发起并由箭头线终点的对象承担的工作。通常操作在设计阶段都会转化为一个具体的函数)在需求分析阶段,序列图只是用于识别关键的类。
4.4 组织D需求的方式
4.5.1 组织详细需求的重要性
随着需求的增长,未经组织的需求将会无法管理
4.5.2 组织详细需求的方法
主要来说,D需求可以按照下面的几种方法进行组织:
根据“特性”(希望外界可以觉察到的服务,往往通过刺激——反应这两方面来
定义)。
这种方法就是通常所认为的按照“需求”进行组织的方式——也就是说,需求根据应用软件可感知的特性来进行组织。不过请注意,这种组织方式并没有真正提供任何系统的组织方式,因为它允许应用软件某个部分的特性跳到另一个完全不同的部分的特性。组织详细需求的途径:
根据“模式”(比如:雷达系统可能有训练、正常和紧急三种模式)
根据“用例”(有时也称做“场景”)。这种方法是统一软件过程推荐的需求组织
方式。
根据“类”。这是一种面向对象的分类方法,使用这种组织方式,我们可以把需
求划分成各个类。
根据“功能的分层结构”(即:把应用软件分解成一组高层的功能集合,继而又
把它们分解成为子功能集合,以此类推)。这种方法就是传统地给详细需求按顺序排列的方法
根据“状态”(表明应用与每个状态的具体需求)。
3. 详细需求(非OO)
3.1 外部接口
3.2 功能
3.3 性能需求 3.4 逻辑数据库要求 3.5 设计限制 3.5.1 遵循的标准 3.6 软件系统属性 3.6.1 可靠性 3.6.2 易用性 3.6.3 安全性 3.6.4 可维护性 3.6.5 可移植性 3.7 详细需求的组织方式 3.7.1 系统模式——或者 3.7.2 用例——或者 3.7.3 对象——或者 3.7.4 特性——或者 3.7.5 刺激——或者 3.7.6 反应——或者 3.7.7 功能分层结构——或者 3.7.8 其它
3. 详细需求(OO形式) 3.1 外部接口 3.1.1 用户界面 3.1.2 硬件接口 3.1.3 软件接口 3.1.4 通信接口 3.2 类/对象 3.2.1 类/对象1
3.2.1.1 属性(直接的或者继承的) 3.2.1.1.1 属性1….
3.2.1.2 函数(服务,方法,直接的或者继承的) 3.2.1.2.1 功能性需求 …….. 3.3 性能需求 3.4 设计限制
3.5 软件系统属性 3.6 其他需求
D需求的组织方式通常与应用软件将要采用的体系结构有着密切的关系。例如,如果设计采用面向对象的方式,那么需求的组织方式就应该考虑基于“用例”或者“分类”的方式,因为这样便于进行追溯。
4.5.3 基于用例组织详细需求
统一软件开发工程提出一种观点:许多需求是按照自然的操作序列发生的。统一软件开发过程推荐按照用例来组织绣球。如果用这种方式组织需求,小型的用例可以用于生成大型的用例,即使用用例“泛化”。
4.5.4 基于类组织的详细需求
面向对象的方式组织需求:首先确定类别——等同于类的选择——然后把每条需求分别放置在前面所得出的类别或者类中。做这件事情有两种方法:一是把类仅仅看做是组织需求的方式,但是不认为它们有必要用于实际的设计。另一种方法则在实际的面向对象的设计(和实现)中采用需求分析过程所产生的类。
按照类来组织需求的缺点是由于后期可能改变类所带来的风险,这样就会打破了需求和类之间的对应关系。
根据将在设计和实现中使用的类来组织需求的最大优点是它为需求、设计和实现提供了紧密的对应关系。这也是采用OO范型的一个关键益处。此外,与真实世界的概念保持一致的类比那些没有这样做的类更有可能被重用。对许多应用来说,基于类的分类方法带来的优点远远超过了它的缺点。
使用OO的方式获取功能性D需求的典型步骤 1. 首先列出用例中提及的类。
2. 这样得出的类往往并不完整,还需要进一步寻找剩余的“领域”类,这个过程将在
后面作出介绍。接着对这些类进行检查。
3. 对于得到的每一个类,需求工程师要记录下应用软件需要它提供的所有功能。主要
是通过属性和函数这样的形式来达到这个目的的。这个类的对象需要处理的事件也应该具体列出。
随着过程的进展,对D需求进行检查。
理想情况下,应该同时提供每个D需求的测试计划, 4. 然后根据C需求来对D需求做出检查 5. D需求经过客户验证之后进行发布。
4.5.5 类的识别
用于组织需求的类必须经过认真和恰当的识别。这个步骤是通过确定应用软件的“领域”
类来完成的——领域类指的是那些特定领域应用软件专用的类。
用例是用于识别领域类的主要来源。从用例中获取所需的类之后,继续补充关键领域类的一个有效方法是进行“罗列和删除”。这个过程包括(1)先列出所有可以想到的合理的侯选类,(2)然后大规模地削减这个列表,直到剩下几个基本的类为此。
然后对这些侯选类进行筛选。首先需要指出的是,以后阶段增加一个类比从设计和实现阶段中删除一个类要简单的多,所以只要对侯选类这些领域类的作用有所怀疑,我们就要把它从清单上去掉。
总结
10. 建立一组广泛的、没有重叠的用例。 11. 为每个用例绘制序列土
识别类和对象的时候需要特别小心 12. 收集序列图中用到的所有类 13. 确定其它基本的领域类
14. 根据这些类划分详细的功能性需求
5.1 把每个属性作为一个需求 5.2 列出这个类必须生成的具体对象 5.3 列出这个类的对象需要的所有函数 5.4 列出这个类的对象必须做出反应的事件 4.5.6 对给定的需求选择正确的类
如果按照类组织D需求,遇到的最大困难可能就是决定把一条需求放在哪个类中进行说明。
4.5.7 划分实体(实例)的类别
应用软件中存在特定的实体(或者叫实例对象,相对于特定的类来说)。 4.5.8 连接到测试文档
当撰写D需求的时候,应该针对这条需求做一些测试方面的工作。在描述需求的同时准备测试文档有下面几个优点:第一,这样做能够帮助我们进一步明确详细需求。第二,它把项目测试阶段的一部分工作提前到需求阶段。
第五章 软件体系结构
第1部分:基础篇
5.1系统工程和软件体系结构介绍 5.1.1 宏图:系统工程
系统工程是将应用分解成软件部分和硬件部分的分析与设计过程。分解的方式有赖于客户的需求,有些则由工程师自行设计。
系统工程分析通常从整个系统的需求开始,通过界定软件和硬件间的平衡点,进而将系统分解成软件和硬件两部分。在此之后,将针对系统的软件部分开始应用软件的工程开发过程,并着手进行软件的需求分析,等。
5.1.2 “软件体系结构”的含义
“体系结构”等同于“顶层设计”。对一切应用软件来说,软件体系结构最显著的特征是其在多人开发情况下不相互依赖性。究其原因,是因为大型应用软件被分解为许多组成部分,并分别对每个部分进行设计和实现(“模块化”),之后,再将各部分组装起来。体系结构的选择提供了这种模块化的方式。
5.1.3 体系结构选择的目标 可扩展性
易于添加新的性能 可改变性
易于适应需求的变更 简单
易于理解 易于实现 有效
速度快:较高的执行或编译速度 规模小:较少的运行时间或较少的代码量 5.1.4 分解
首先,了解一下进行软件分解的目的。
内聚:是指一个模块内部各组成部分之间相互联系的程度。耦合:是指一个模块与其它模块之间的相互联系程度。高内聚、低偶合的模块化才是有效的模块化。有效的模块化使把复杂问题分解为简单问题成为可能。也是应用软件进行进一步的改进所必须的。低耦合/高内聚的软件体系结构使设计修改能在尽量不影响其他部分的前提下更容易进行。
第2部分:提高篇
5.2 模型、框架和设计模式
分解只是进行设计的一个基本步骤。我们需要协调分解和用例、类、状态转换之间的关系。我们称这些视角为“模型”。
详细设计:包括除确定体系结构和代码实现之外的所有设计工作。详细设计介绍了如何定义与体系结构类、领域类相关联的类。
5.2.1 使用模型
用例模型是组用例集合。主要用于阐明应用思想要完成什么样的功能。最初的用例版本应该满足C需求(有时又称为“业务用例、业务需求”)。随着项目的进行,我们将通过逐
步细化的、特定形式的序列图来刻画用例模型。
类模型:迄今为止,已经碰到过大量的类模型(类图)。它们主要用来解释构成应用的各组成模块。类模型又称为对象模型。类模型中应该体现出类的属性和方法。
构件模型:是一组数据流图的集合。它描述了应用通过数据流动来完成其它功能的途径。 状态模型:是一组状态/转换图的集合。状态模型描述了应用工作的时机。 5.2.2 统一建模语言
在一个类模型的最高层次上,UML使用术语包(package)来作为体系结构的组成元素。包是一组类的集合。
抽象类即指那些不能被实例化为对象的类,在UML中用斜体表示。聚合,用端点带有空棱形的线段表示,表明一个类包含另一个类,这种聚合一般通过在该类中定义一个以被包含类为类型的属性的方式来实现。依赖,用带箭头的虚线线段来表示,通常表示一个类中的某个方法依赖于另一个类的性质。
5.2.3 框架
框架是一组可用于不同应用中的类的集合。框架中的类通常是相互关联系的。它们通常是一些抽象类,可通过继承方式被使用。
5.2.4 体系结构的分类 数据流结构
1. 批处理序列 2. 管道和过滤器 独立构件
1. 并行通信处理器 2. 客户-服务器系统 3. 事件系统 虚拟机
1. 解释器 2. 基于规则的系统 仓库结构
1. 数据库 2. 超文本系统 3. 黑板 层结构
5.2.5 设计模式I:引言
设计模式是由用来解决某些普遍设计问题的构件,特别是类和对象构成。设计模式适用用于构件软件体系结构阶段和详细设计阶段。下图列出了在构建体系结构阶段具有特殊用
途的设计模式。
设计目标
针对一组不同类的对象提供一个接口 使得一个对象根据它的当前状态来决定其行为表现 将访问对象组的方法封装到一个集合中,使得客户端代码可以
在运行期间选择一种访问方式
促进对一个数据源变化感兴趣的多个实体作出变化响应
解释采用一种正式的语法来书写的语句
5.2.6 构件
指一些能够重用的实体,它们在不需要了解软件知识的前提下就能被使用。构件通常以聚合的方式使用其它构件,并通过事件触发机制来与其它构件之间相互通信。
5.3 软件体系结构的方案及其它类模型
软件设计师通常会开发一个拥有5到7个组成部分的理想模型来描述应用系统的工作方式。下表是一些体系结构模型的概括:
体系结构
类别 数据流
子类别 批处理序列 管道和过滤器
独立构件
并行通信进程 客户服务器系统 事件系统
虚拟机
解释器 基于规则的系统
数据库 超文本系统 黑板系统
分层体系
结构 5.3.1 数据流结构
某些应用非常适合于以数据在处理单元之间的流动方式这一角度来进行观察。数据流
常用的设计模式
Observer Facade State Interpreter
Observer,
Iterator
表示一次业务成功完成
大多数的设计模式都由一
个抽象层和一个非抽象层共同组成
注释
可应用Decorator模式
设计模式 Façade(外观模式) State(状态模式) Iterator(迭代器模式) Observer(观察模式) Interpreter(解释器模
式)
仓库体系
结构
图(DFDs)提供了从数据流动角度观察应用图例。数据流图中的每一个处理单元都被设计成不依赖于其它单元的独立体。数据起源于用户,最终又流回用户,或被帐户数据库所吸收。
5.3.2 独立构件
“独立构件”结构包括那些并行(至少大体上)操作的构件和它们之间持续的通信。 5.3.2.1 客户—服务器结构和Façade(外观)设计模式
在客户—服务器结构关系中,服务器构件响应客户的请求而提供服务。客户—服务器结构涉及的双方构件之间的关系具有低耦合的优点。在软件工程领域中,这种关系通常应用于需要多人参加的开发项目中。类集合的实现工作很自然地分配给每个人开发人员或者每组开发人员。通常,开发人员都需要使用到其它开发人员开发的类。或者说,开发人员所负责的类集合经常被分为客户方和服务方两个组。这种做法的问题在于随项目的进展,这些类所提供的服务总是处在随时准备变化的状态中。
当接口较“窄”时,作为服务器的构件则能有效地发挥作用。“窄”的意思是接口(实际上是一些函数的集合)不包含不必要的成分,并且功能均被良好定义且只集中于一点。Façade开发模式即为一个建立了这样的窄接口的有类组成的包。Façade将只对外公布包中的一个对象,同时隐藏其余对象方式作为与其他包中对象通信的规范。一个包有且只有一个被公开的对象,并且这个对象通常是Façade类的对象。
在Façade开发模式下,对包中任一对象的引用均可由调用Façade对象中的一个方法所代替,然后再由这个方法来引用所要求的实际对象。
5.3.2.2 并行通信处理器结构
当多个应用要求并行处理时,并行通信处理器结构是一种常用的选择。并行通信处理器结构适用于那些用来协调概念上相互独立的任务的实际方案。并行通信处理器的优势在于它能够使用系统更加接近于所模拟的对象行为。
5.3.2.2.1 Observer(观察者)设计模式
“独立构件”结构经常会包含一组数据资源及多个使用这些数据的客户,当数据信息发生变化时,这些客户的信息也要随之更新。
Observer设计模式作为一种体系结构被用来解决这类需求问题。观察者们清楚各自部分的更新要求。
5.3.2.3 事件系统结构和State(状态)设计模式
时间系统把应用看成是一个构件的集合,每个构件直至发生对它有影响的事件时才有所动作。
当一个系统的行为实际上是随着其内部状态的转换而改变时,我们进行系统设计时就可以考虑采用State(状态)设计模式。State设计模式能够解决如何在不知道对象状态的条件下就使用该对象的问题。
5.3.3 虚拟机
虚拟机结构将应用系统看成是一个用特定语言编写的程序。由于需为这个特定语言创建一个解释器,因此,虚拟机结构对于那些开发过程中需要用某种特定语言来编写若干“程序”来实现一些应用的开发项目来说就非常的适用。
5.3.4 仓库(Repository)结构
围绕数据建立起来的体系结构是仓库结构。最常见的仓库结构是那些为处理交易而设计的数据库系统。黑板体系结构是为人工智能应用系统开发的,是行为满足登记(posting)规则的仓库。
5.3.4.1 使用Iterator(遍历器)设计模式遍历访问仓库成员
仓库结构经常应用于那些要求对象聚合(集合)的应用系统中。为了维护聚合对象,系统要求能够“遍历访问”聚合中的全部成员。如果聚合对象必须通过多种不同方式才能进行访问,那么我们就应该采用Iterator(遍历器)设计模式来实现。
5.3.5 分层体系结构
分层体系结构是软件制成品的集合,尤其是类集合成的联合体。在分层结构的一般形式中,一个层次至少使用一个其它的层,且至少被一个其它的层所使用。逐层地构建应用系统能够在很大程度上简化开发过程。某些层,如框架层,可以为多个应用提供服务。
客户—服务器结构是一种常见的分层方式。这种方式中,客户方依靠服务器方提供它所需要的服务。客户方软件通常驻留在用户计算机上。
5.3.6 在同一应用系统中应用多种体系结构 5.3.7 选择体系结构的实际步骤 做法:选择体系结构1
1. 将应用分解为自包含的模块
2. 与标准的体系结构进行比较,改进分解
处理单元上是否需要批量处理,然后执行数据处理?
1. 批量序列数据流
处理单元是否等待数据,然后执行数据处理?
1. 管道和过滤器数据流 是否并行执行进程?
1. 并行通信处理器
进程是否需要支持用户的请求
1. 客户—服务器
事件发生时,进程是否需要立即响应
1. 事件系统
每次执行是否需要解释一个脚本
1. 解释器
应用是否主要关注于数据库
1. 仓库
是否需要按层组织
1. 分层结构
做法:选择体系结构2
3.从上述体系结构中选定一种体系结构
4.加入在需求分析阶段得到的适合于所选结构的类
例如,数据流—控制元素之间的数据流向 例如,事件系统——控制状态转换 5. 应用已知的框架和/或设计模式
如果可指出有益的一类。 6. 将类划分为包
理想情况下,大约为4~8个(大型应用的嵌套包) 应采用能反映出应用内容的词语给包命名。 7. 确保各部分的高内聚,外部的低耦合。调整选择结果 8. 考虑为所有包引进“Façade”对象作为控制接口
5.4 用于体系结构的符号、工具和标准 5.4.1 符号
统一建模语言(UML)是一种被广泛接受的用来描述面向对象设计的图形符号。 5.4.2 许多计算机辅助软件开发(CASE)工具被运用于软件开发过程。其中一些工具主要用于描述类及类之间的关系。
第六章 详细设计
第一部分:基础篇 6.1 详细设计简介 6.1.1 “详细设计”的含义
详细设计是指紧挨着体系结构选择阶段之后的所进行的技术性的活动。详细设计的目的是为项目的实现做好完善的准备工作。换句话说,程序员们只需要关注于纯粹的代码问题就可以实现一个详细设计。
6.1.2 详细设计与用例、体系结构的关系
详细设计与用例、体系结构的关系可以用设计一座桥梁来比喻。用例就像是桥梁设计需求的一部分。首先,从需求出发,工程师们选择一种桥梁结构。然后,他们进行详细设计以
便于所选择的结构能够满足所需求的用例。
在软件设计中,每个阶段都聚集一些补充的类。第一步:用例作为需求的一部分进行了详细说明;第二步:通过用例和其它的资源一起标识出领域类;第三步:确定软件的体系结构。最后,确认体系结构和详细设计能够支持用例需求。对软件设计而言,我们应确认在详细设计中所定义的类和方法能够执行所要求的用例。
6.1.3 “详细设计”过程的一个典型路线图
详细设计开始于体系结构设计阶段所做的决定,结束时可以得到一张为编码阶段准备的完整的设计图。
1.开始于体系结构模型
类的模型:领域类和结构类 总体的状态模型 总体的数据流模型 用例模型
2.引进连接领域类和结构类的那些类和设计模式 首先考虑风险最大的方面:尝试选择方案
3.细化模型,做到一致,确保完整 4.指定类变量
5.指定方法的前置和后置条件,流程图和伪码 6.拟定单元测试计划 7.评审测试计划和设计 8.发布实现
6.1.4 统一开发过程中的设计
统一软件开发过程中的设计
分析
1. 概念的和抽象的 2. 适合于多个设计
3. 《控制》(control)《实体》(entity) 和《边界》(boundary)原型 4. 较少的正式性 5. 较小的开发代价 6. 概括设计
7. 从概念上的思考中形成 8. 进程间的信息具有低优先级 9. 相对地不受限制 10.较少地关注于序列图
11.很少的层
设计
1. 具体的:实现的设计图
2. 只针对一个实现
3. 没有类原型的限制
4. 较多的正式性
5. 较大的开发代价
6. 明示设计(体系结构作为一个视图)
7. 可以使用工具(例如可视的,双向工程的工具)
8. 进程间的信息具有高优先级
9. 受分析和体系结构的限制
10.较多地关注序列图
11.较多的层
统一软件开发过程支持分析阶段的类有三种类型(“原型”):实体类(entity)、边界类(boundary)和控制类(control),然而在设计类中并没有这样的限制。实体类表示概念的本质,不太可能随着时间或在系统之间发生改变;边界类处理实体对象之间的通信;控制类包含与实体类相关的方法,这些方法通常是针对使用这些实体类的应用。
面向接口的设计
面向功能的设计有很多种方式。其中之一是使用抽象的概念。
构件重用
一些工程(电子工程、机械工程等)原则上依赖于构件的使用,而这些构件能够被分 别独立获得。在面向体系结构的章节中我们曾经讨论了框架。框架是指一种为重用设计的构件集合。我们开发的是支持体系结构的框架,由此它们是可以有效地被重用。
6.2 详细设计中的序列图和数据流图
序列图和数据流图可以用来描述需求。
做法:细化详细设计的模型1/2:序列图
1. 从那些和用例对应的且为详细需求或者体系结构设计(如果有的话)创建的序列
图开始
2. 如果必要的话,引进补充的用例,以便描述设计的各组成部分是如何与应用的其
它部分进行交互的。
3. 提供比较完整的、详细的序列图
确保已经指定准确的对象及它们的类
选择特定的方法名称代替使用自然语言的名称
做法:细化详细设计的模型2/2:数据流图
1. 收集为详细的需求或者体系结构(如果有的话)构造的数据流图(DFDs)
2. 如果必要的话,引进补充的DFDs以便解释数据和处理流程。
3. 指出这些DFDs和其它模型的那些部分相关联。
例如、“下面的DFD用于类模型中的每一个Account对象”
4.提供DFDs的全部细节
指出在每个节点的处理过程的本质
指出所传输数据的种类
如果处理过程的说明要求更详细的话,将过程节点展开成DFDs。
6.2.1 详细的序列图
我们已经知道用例可以被用来表达需求而且我们也运用它们来判定系统中的关键的领域类。现在,我们将从用例中取出序列图并将提供包含执行序列图所需要的方法的那些类。接下来,类模型和用例模型(以序列图的形式)都要细化完成,。状态图(如果必要的话)也要进一步完善。而数据流图是另一个模型。
6.2.2 详细的数据流图
对于详细设计而言,应该更详细地说明数据模型,然后映射到函数或者类和方法。
6.3 类和函数的说明
详细设计的目的是为了提供一张完整的设计图,然后依据这张设计图构建程序。 做法:说明一个类
1. 收集SRS中列出的属性
如果SRS是用类来组织的,这将很容易做到
2.为设计添加所需要的附加属性
3.为这个类的每一张需求指定一个相应的方法
如果SRS是用类来组织的,这将很容易做到
4.为设计指定所需要的附加方法
5.说明类不变量(invariants)
做法:说明一个函数
1. 注意这个函数(方法)能满足SRS或SDD的哪些部分
2. 说明这个函数必须满足的不变量表达式
3. 说明方法的前置条件(假设什么)
4. 说明方法的后置条件(影响什么)
5. 为被使用的特定算法提供伪码或流程图
除非算法非常直接
一张详细的类图应该包括所有的属性和操作的名称、签名、可见性和返回类型的。存取
方法通常被省略。
6.3.1 类不变量
不变量指的是一个定值(assertion),它在整个指定的计算过程中都保持真值。类不变量对类的对象而言是有效需求,它们通常以类属性的方式体现出来。这些需求可以直接映射到应用的需求上,因此可以在SRS中发现。或者也可以由设计指定。类的不变量可以用常量值的形式。
6.3.2 函数不变量、前置条件和后置条件
控制函数行为的一个方法是使用函数不变量。函数不变量是指对函数保证遵从的变量之间关系的一个肯定。有效说明函数的一个方法是使用前置条件和后置条件。前置条件说明的是在函数执行之前存在的变量和常量之间的关系;而后置条件说明函数执行之后变量和常量之间的关系。
6.4 算法说明
在确定各个函数之后,用某种方式在没有源代码的情况下描述出算法是非常理想的做法。因为这样做的优点在于开发人员可以在不考虑程序复杂性的情况下单独对算法进行检查,从而在算法缺陷转移到代码缺陷之前就能够捕捉或避免它们。函数越是关键,这种做法的意义就越大。具有复杂分支的方法是流程图中最基本的备选对象。
6.4.1 流程图
流程图是最古老的算法图形化描述方式之一。
6.4.2 伪码
伪码是算法的一种文字表示方法,这种表示方法不需要过多的编程语言细节。
6.4.3 何时使用流程图和伪码
在多种情况下使算法变得明晰
对详细设计的文档编制过程施加更多的规程约束
提供额外的一些层次,在这些层次上可以进行检查
1. 在编码之前帮助捕捉错误
2. 增强产品的可靠性
经常缩减整体成本
创建了需要维护的附加的文档层次
在转变为代码的过程中引入了错误的可能性
可能需要工具来提取伪码和促进流程图的绘制
第二部分:提高篇
6.5 设计模式II:详细设计的技巧
没个设计模式都包含两部分内容:第一部分是典型的类模型,用来描述类及类之间的关系;第二部分解释模式是如何执行的,通常采用用例模型来描述
做法:在详细设计中应用设计模式
1.熟悉各类设计模式所能解决的问题
至少要求清楚创建型、结构型和行为模式之间的区别。
顺次考虑详细设计的每一部分。
2.判断当前的设计问题是否有需要处理:(C)创建某些复杂的事物、(S)表示一个复杂的结构或者(B)捕捉行为。
3.判断是否存在一种设计模式能够解决当前所面临的问题
尝试按分类寻找设计模式(C、S或B)。
使用本书或其它参考资料的设计模式。
4.确定是否利大于弊。
利:通常包括增强灵活性。
弊:通常包括增加复杂性、降低效率。
6.5.1 详细设计中的创建型模式
创建型模式用于对象的创建,包括运行时选定并协作的一系列对象。如果不使用创建型模式的话,那么我们将不得不为运行时所需要的每个对象或者每一系列对象都分别编写代码。创建型模式一般用一个单独的对象或者方法来创建对象,而并不是直接通过类构造函数来创建对象。
6.5.1.1 Singleton(单件)模式
问题:假设类C只能有一个对象。
Singleton模式:Singleton的目的通过将类的构造函数设置为私有的(或者是受保护的)方式来实现。这样可以确保除了通过C类自己的方法以外,没有其它的方法可以创建C类的对象。C类定义了一个静态的数据结构,其类型为C,这个数据结构就是Singleton,我们将其命名为theCObject。 C类还定义了一个公有的方法getTheCObject(),如果theCObject已经存在,那么getTheCObject()方法将返回这个对象;如若不然,getTheCObject()方法将创建theCObject对象并将其返回。因此,为了得到C类的这个惟一的对象,我们只需要调用C.getTheCObject()。
C类的静态对象theCObject可以设置初始值为null,而第一次调用getTheCObject()方法时使用new C()创建它。无论theCObject的值是否为null,都将检查前面所提到的它的“存在”与否。
6.5.1.2 Factory(工厂)模式
问题:编写客户代码使其在运行时创建基类对象而不必引用(言及)基类的任何子类。 Factory模式:为基类定义一个可以用来创建基类对象的方法,我们将这个方法命名为getNewBaseObject( )。在客户代码中,我们将用getNewBaseObject( )方法代替newBase( ), 这样做正好体现了虚函数的能力。程序运行时,b.getNewBaseObject( )将创建一个基类的子
类对象,该子类正是实例b的所属类型。
6.5.1.3 Prototype(原型)模式
问题:假设我们需要设计一个办公应用程序来处理Document对象和Customer对象。假设这个办公应用程序的每一次执行在整个运行期间都只能使用Document类的一个子类和Customer类的一个子类。而应用程序在运行时才能确定这些子类。因此,运行时指定的是Document和Customer对象的四种可能组合中的一种。——创建基类对象,而不是创建运行时所需要的具体类型的对象。这样将严重妨碍重复的代码应用于所有的Document对象的能力。
Prototpye模式:解决这个问题的一个方法是在应用程序中使用一个特定的静态Document对象(也叫做“prototype原型”,我们将其命名为documentPrototypeS)和一个Customer的prototype对象(我们称之为customerPrototypeS)。另外,Document的每个子类必须具有一个clone( )方法:该方法可以得到一个Document对象的完整拷贝;同样,Customer的子类也应该有clone( )方法。
6.5.1.4 Abstract Factory(抽象工厂)和Builder(生成器)模式
问题:需要在运行期间创建一系列相关对象。
Abstract Factory模式:Abstract Factory是在客户代码中使用的。
6.5.2 详细设计中的结构模式
结构型设计模式把诸如表、集合和树等一些复杂对象表示成一些具有易用接口的对象。
6.5.2.1 Composite(组合)模式和Decorator(装饰)模式
问题:假设我们需要描述一组树状结构的对象集合(例如,一个公司的组织结构图)或链接对象集合。
Composite和Decorator设计模式:这组解决方案的核心在于使用继承和嵌入方法。这种方法显示了一些构件嵌入了另一些构件。
……
6.5.3 详细设计中的行为型设计模式
…….
第七章 单元实现
7.1 简介
7.1.1单元实现的含义
“实现”就是指编程。“单元”指的是能够进行单独维护的实现的最小组成部分,它们可能是一个单独的方法,也可能是一个类。
7.1.2 实现的目标
“实现”试图使用详细设计里明确说明的方法满足需求。尽管详细设计已经是一份能够满足变成所需要的文档了,但是程序员们一般还是需要同时检查详细设计之前的所有文档
(包括体系结构、D需求、C需求),以便协调文档之间不一致的地方。
7.1.3 单元实现过程的典型路线图
1.定义编码标准(由于已经指定了编码标准,所以代码有着统一的外观)
2.对于每个类——实现其方法(体系结构决定框架和应用包是什么,并且用需求分析和详细设计里确定的方法来实现每个包的每一个类。而框架包必须在应用包建立之前建立。)
3.类检查(每个类一旦编写完毕就立即进行检查。)
4.进行单元测试(然后测试每个类)
5.供集成发布(接着发布包和类,进而集成到演化中的应用程序)
做法:为实现做准备
1.确认你必须实现的详细设计
只为一个已经建立了文档的设计(SDD的组成部分)进行编码
2.估计时间开销,并按如下方式分类:
剩余的详细设计:详细设计的评审;编码;代码的评审;编译及改正语法错误;
单元测试和修改测试阶段发现的错误
3.准备使用一种统一的形式记录错误
严重性:重大的未满足需求的、不重要的或两者都不是
类型:错误、命名、环境、系统、数据、其它
4.掌握所需的标准
编码标准
你必须保留个人文档
5.根据以往的经验值估算规模和时间
6.以+-LOC的分段安排工作