软件工程
一、软件工程概述
软件=程序+数据+文档
软件工程:用工程化的方法指导计算机软件开发和维护的一门工程学科
软件工程包括软件开发技术(过程、方法和工具)与软件工程管理
软件生命(存)周期
软件生命周期:软件产品从形成概念开始,经过开发、使用和维护,直到最后退役的全过程
分为三个时期:软件定义、软件开发和运行维护(也称为软件维护)。每个时期又进一步划分为若干个阶段
阶段 | 关键问题 | 结束标准 |
---|---|---|
问题定义 | 问题是什么? | 关于规模和目标的报告书 |
可行性研究 | 有可行的解吗? | 系统的高层逻辑模型: 数据流图、成本/效益分析 |
需求分析 | 系统必须做什么? | 系统的逻辑模型: 数据流图、数据字典、算法描述 |
总体设计 | 概括地说,应该如何解决这个问题? | 可能的解法:系统流程图、成本/效益分析 推荐的系统结构:层次图或结构图 |
详细设计 | 怎样具体地实现这个系统? | 编码规格说明:HIPO图或PDL |
编码/单元测试 | 正确的程序模块 | 源程序清单:单元测试方案和结果 |
综合测试 | 符合要求的软件 | 综合测试方案和结果:完整一致的软件配置 |
维护 | 持久地满足用户需要的软件 | 完整准确的维护记录 |
例:教材销售系统
学生购买学校教材的手续可能是:
- 学生自己写一个希望买什么书的列表,先找系办公室开购书申请(用于确定学生可购买的书,希望用软件实现,完全由教材科控制)
- 凭申请找教材科开购书证明(确定是否卖完,希望用计算机实现)
- 向财务交付书款获得领书单,然后到书库找保管员领书
关于系统规模和目标的报告书
项目名称:教材销售系统
问题:人工发售教材手续繁杂,且易出错
项目目标:建立一个高效率、无差错的教材销售系统
项目规模:利用现有计算机,软件开发费用不超过5000元
初步想法:验证学生可买什么书
建议在系统中增加对缺书的统计与采购功能
可行性研究:建议进行大约10天的可行性研究,研究费用不超过500元
-
软件维护
-
任务:使系统持久地满足用户的需要
- 改正性维护,诊断和改正在使用过程中发现的软件错误
- 适应性维护,修改软件以适应环境的变化
- 完善性维护,根据用户的要求改进或扩充软件,工作量最大
- 预防性维护,修改软件为将来的维护活动做准备
每一项维护活动实质上是经历了一次压缩和简化了的软件定义和开发的全过程
-
结果:完整准确的维护记录
-
软件工程方法
软件过程模型
瀑布模型
传统的瀑布模型
计划:时间计划、成本计划、人力资源计划等
实际的瀑布模型
后续阶段发现前面阶段的错误,使用反馈环返回修改
- 特点:
- 阶段间具有
顺序性
和依赖性
,不允许同时进行- 前一阶段的工作完成之后,才能开始后一阶段的工作
- 前一阶段的输出文档就是后一阶段的输入文档
- 推迟实现的观点
- 对于规模较大的软件项目来说,往往编码开始得越早最终完成开发工作所需要的时间反而越长
- 质量保证的观点
- 每个阶段都必须完成规定的文档,是“文档驱动”的模型
- 每个阶段结束前都要对所完成的文档进行评审,尽早发现问题,改正错误
-
优点:
- 强迫开发人员采用规范的方法
- 严格地规定了每个阶段必须提交的文档
- 要求每个阶段交出的所有产品都必须经过质量保证小组的仔细验证
-
缺点:
- 客户要
准确、完整、全部
的表达出自己的要求 - 缺乏灵活性。一旦需求存在偏差,最终只能终止或者失败(偏差随着系统推进,层层放大,失之毫厘谬以千里)
- 用户只能通过文档了解产品,不经过实践的需求时不切实际的
- 客户要
-
适合瀑布模型的项目特征
- 需求:预知,需求明确
- 方案:技术、方法、框架,方案成熟明确
- 类似项目:小型项目
V模型
强调了测试在软件开发过程中的重要性
- 适合V模型的项目特征
- 需求:很明确
- 方案:很明确
- 类似项目:系统性能、安全有严格要求等
增量模型
增量模型把软件产品作为一系列的增量构件来设计、编码、集成和测试
- 特点:
该模型假设需求是可以分段的,称为一系列增量产品,每一增量可以单独开发
比如,使用增量模型开发字处理软件时:
第一个增量构件往往提供软件的基本需求,提供最核心本的文件管理、编辑和文档生成功能。例输入、插入、新建、存储
第二个增量构件提供更完善的编辑和文档生成功能,比如菜单,复制、粘贴、另存为等
第三个增量构件实现拼写和语法检查功能
第四个增量构件完成高级的页面排版功能
- 优点:
- 作为瀑布模型的变体,拥有瀑布模型的全部优点
- 第一个可以交付的版本所需要的成本和时间少,迅速占领市场
- 承担的风险小
- 当配备的人员有限,不能在设定的期限内完成产品时,可以提供先推出核心产品的途径
- 逐步增加产品功能可以使用户有较充裕的时间学习和适应新产品
- 难点:
- 软件体系结构必须是开放的
- 如果分增量块过多的话,会增加管理成本
- 不同的构件并行地构建有可能加快工程进度冒无法集成到一起的风险
增量模型整体以瀑布模型为基准,对瀑布模型的拓展。在概要设计完成后,使用增量模型,分模块进行增量组件的开发。增量组件称为构件,方便软件复用
- 适合增量模型的项目特征
- 需求:需求会变动
- 方案:对完成期限有严格要求,开发早期阶段获得投资回报,对于市场和用户把握需要逐步了解进行已有产品升级或新版本开发非常合适
- 类似项目:适用于商业软件的开发
实例
微软“同步—稳定的产品开发模型”
- 将项目分成若干个里程碑
- 定义稳定、灵活的体系结构,并为构件和子系统的开发提供统一的接口
- 开发构件,维持一个可发布的系统版本
可以准确把握项目进展情况
增强开发人员的信心和成就感
可以随时根据市场情况及时作出调整
快速原型模型
第一步是快速的建立一个能反应用户主要需求的原型系统,让用户在计算机上试用它,通过实践来了解目标系统的概貌
-
步骤
-
获取用户需求,描述规格说明书
使用原型(样本)思想获取用户需求
- 原型也可能是公司现有的一个类似软件,或只是界面,或只是文档。可能是最终产品能完成的功能的一个子集
- 一旦原型完全符合用户需求,开发人员根据其写说明书
-
根据规格说明书开发维护软件
-
-
特点:
- 软件产品的开发基本上是线性顺序进行的
- 快速原型的本质是“快速”。应该尽可能快地建造出原型系统,以加速软件开发过程,节约成本
- 在整个开发过程中,初始及多次迭代的原型可能跟用户的需求完全不同,引起客户不满。但同时用户也能随时看到实物和进展
-
适合快速原型模型的项目特征
- 需求:不明确,不能完整、准确的定义
- 希望:减少项目需求的不确定性
- 方案:项目很小,较简单。有快速原型开发工具或软件类似产品
螺旋模型
增加了风险分析过程的瀑布模型和快速原型模型混合
风险分析是对工程建设项目投资决策或企业生产经营决策可能造成的失误和带来的经济损失所进行的估计
- 特点:
每完成一项任务,都要先进行风险识别,然后风险分析,对存在的风险尽力进行风险控制。若风险很大,无法解决,甚至可以停止工作的进行
图中的四个象限代表了以下活动:
- 制定计划:确定软件目标,选定实施方案,弄清项目开发的限制条件;(左上)
- 风险分析:分析评估所选方案,考虑如何识别和消除风险;(右上)
- 实施工程:实施软件开发和验证(右下)
- 客户评估:评价开发工作,提出修正建议,制定下一步计划。(左下)
-
优点:
- 主要优势在于它是风险驱动的。在评估和风险分析阶段都可作出项目是否继续,以规避无法承担的风险
- 螺旋循环的次数指示了已消耗的资源
- 对可选方案和约束条件的强调有利于已有软件的重用,也有助于把软件质量作为软件开发的一个重要目标
- 减少了过多测试或测试不足所带来的风险
- 维护只是模型的另一个周期,维护和开发之间没有本质区别
-
缺点:
- 采用螺旋模型需要具有相当丰富的风险评估经验和专门知识,在风险较大的项目开发中,如果未能够及时标识风险,势必造成重大损失
- 过多的迭代次数会增加开发成本,延迟提交时间
-
适合螺旋模型的项目特征
- 特别适用于庞大、复杂并具有高风险的系统
- 适用于内部开发的大规模软件项目
喷泉模型
瀑布模型的一种变体,旨在解决瀑布模型中的一些缺陷,特别是对需求变更的不灵活性
喷泉模型允许在项目的任何阶段对需求进行调整和修改,使得开发过程更加灵活
- 特点:
- 各阶段相互重叠,反映了软件过程并行性的特点
- 以分析为基础,资源消耗呈塔形,在分析阶段消耗的资源最多
- 反映了软件过程迭代的自然特性,从高层返回低层没有资源消耗
- 强调增量式开发,它依据分析一部分就设计一部分的原则,不要求一个阶段的彻底完成。整个过程是一个迭代的逐步细化的过程
- 是对象驱动的过程,对象是所有活动作用的实体,也是项目管理的基本内容
- 在实现时,由于活动不同,可分为对象实现和系统实现,不但反映了系统的开发全过程,而且也反映了对象族的开发和复用的过程
- 适合喷泉模型的项目特征
- 需求可能频繁变更
- 强调增量式开发(快速交付)
总结
模型名称 | 技术特点 | 适用范围 |
---|---|---|
瀑布模型 | 简单,分阶段,阶段间存在因果关系,各个阶段完成后都有评审,允许反馈,不支持用户参与,要求预先确定需求 | 需求易于完善定义且不易变更的软件系统 |
增量模型 | 软件产品是被增量式地一块块开发的,允许开发活动并行和重叠 | 技术风险较大、用户需求较为稳定的软件系统 |
快速原型模型 | 不要求需求预先完备定义,支持用户参与,支持需求的渐进式完善和确认,能够适应用户需求的变化 | 需求复杂、难以确定、动态变化的软件系统 |
螺旋模型 | 结合瀑布模型、快速原型模型和迭代模型的思想,并引进了风险分析活动 | 需求难以获取和确定、软件开发风险较大的软件系统 |
UML
二、需求分析——用例模型&分析模型
用例模型
- 从描述语言和视角:
用例模型主要使用客户的语言进行描述,它代表了系统的外部视图。这意味着用例模型从客户的角度出发,详细描述了系统应该提供的功能和行为
分析模型则使用开发人员的语言进行描述,它提供了系统的内部视图。分析模型更注重于系统的内部结构和实现细节,帮助开发人员理解如何构建系统
- 从构造方式和结构:
用例模型是通过用例来构造的,这些用例提供了外部视图的结构。每个用例都描述了一个特定的系统行为或功能,以及与之相关的输入、输出和前置后置条件
分析模型则是通过构造型的类或包来构造的,这些类或包提供了内部视图的结构。分析模型更关注于系统的组件、接口和交互关系,以支持系统的设计和实现
- 用途也存在差异:
用例模型主要用于客户和开发人员之间签订合同时明确系统应该和不应该做什么
分析模型则主要为开发人员所用,以帮助他们理解如何构造系统,即怎样设计和实现系统
用例图
- 组成
参与者(活动者,Actor)
用例(Use Case)
关系(Relationship)
关系类型 | 说明 | 表示符号 |
---|---|---|
关联 | 参与者和用例间的关系,调用了(参与者要使用系统的功能) | ![]() ![]() |
泛化(继承) | 表示两个类之间的继承关系,其中一个类是另一个类的特殊情况。 | ![]() |
包含 | 表示一个用例包含了另一个用例,用于将一个用例的功能分解为多个较小的、更具体的用例。 | ![]() |
拓展 | 表示一个用例在某些条件下可以扩展另一个用例的行为,用于描述用例之间的可选行为。 | ![]() |
- 关联——调用本身
参与者和用例间的关系
- 泛化(继承)——“is a…”
参与者之间或用例之间
- 包含(Include)——“has a…”
用例之间的关系
箭头指向分解出来的功能用例
箭头出发的用例为基用例。包含用例是必选的,如果缺少包含用例,基用例就不完整。
- 拓展——特殊情况
用例之间的关系
箭头指向基础用例
用例文档
-
用例描述文档组成:
- 用例名称:与用例图同,并写相应编号
- 简要说明/描述:简要描述功能
- 优先级:标识软件客户对该用例实现状况的期许(满意度1-5、不满意度1-5),数字越大,优先级越高
- 参与者(执行者):使用该用例的人或系统等
- 前置条件:在用例启动时参与者(actor)与系统应置于什么状态。此状态是系统可识别的
- 后置条件:用例结束时系统应置于什么状态。即用例结束时的系统状态或持久数据情况
-
基本事件流:对用例中常规、预期路径的描述。由若干步骤构成一个完整的交互过程
- 使用主动语句,以执行者或系统为主语
- 不涉及到界面细节
- 使用业务语言,而不使用专业术语
-
异常事件流:对用例执行中一些异常情况进行描述
-
业务规则:用例执行中与业务有关的一些规则要求
-
扩展点:包含用例或扩展用例,此处写出用例名
-
涉及的业务实体:建立了对象模型后完善
用例图示例:
活动图
- 组成
初始节点和终点
活动节点
转换
决策与分支、合并
分岔与汇合
对象流(可选)
泳道(可选)
- 初始节点:
- 终点:
- 活动节点:表示一个活动,一个活动表示一个或多个动作的集合
- 转换:
- 决策与分支、合并:分支之间是互斥的
- 分岔:表示一个控制流被两个或多个控制流代替,经过分岔后,这些控制流是并发进行的
- 汇合:与分岔相反,表示两个或多个控制流被一个控制流代替
- 对象流:活动和对象之间的关系
- 泳道:活动的负责者
活动图示例:
带泳道和对象流的活动图示例:
分析模型
类图
- 类
命名
简单名:Order
路径名:java::awt::Rectanget
businessRule::Order
包名::类名
属性
属性名的第一个字母小写
[可见性] 属性名 [:类型] [=初始值] [{特性}]
a:int |
public(+):即模型中的任何类都可以访问该属性
private(-):表示不能被别的类访问
protected(#):表示该属性只能被该类及其子类访问
Package(~):这个类只能由同一包中的其他类访问
- 组成
类
接口
关系
注释
约束
包
- 接口:包含抽象方法的声明,但不包含具体实现。接口可以实现多态
-
关系:类和类之间的线就是关系
-
注释:文档不在正式程序中,只做注释说明
-
约束:{}包含,定义关系约束或者类约束
-
包:用于逻辑上将复杂的类图模块化,从而更好地组织和理解代码结构。通过将功能和结构相似的类放入同一个包内,可以使得整个系统的结构更加清晰和易于理解
- 关系
关联(普通关联、同类对象角色关联、限定关联):表现在代码实现中,一个对象会作为另一个对象的属性
聚集(聚合、组合):关联的一种特殊情况。聚集表示类与类之间的关系是整体与部分的关系
依赖
依赖关系不会增加属性
泛化
实现
- 普通关联:表明两个类之间存在某种形式的交互或依赖
多重性:某个类的对象可以和其他类的多个对象联系
1对1:学生和学生证
1对多:一个学院有多名学生
多对多:一个学生可以选择多门课,一门课也有多名学生
固定值(1)
无限定的多个(*)
一个取值范围:0…1,1…*,0…*,2…5
- 自反关联:表示类与自身的关联,即同类不同对象间的联系
- 限定关联:利用限定词把一对多关系简化成了一对一关系
- 关联类:为了说明关联的性质可能需要一些附加信息。这些信息放到关联的任一方都不合适,可以引入一个关联类来记录这些信息关联类与一般的类一样,也有属性、操作和关联
- 聚合:处于部分方的对象可同时参与多个处于整体方对象的构成(弱)
- 组合:如果部分类完全隶属于整体类,部分与整体共存,整体不存在了部分也会随之消失(强)
- 依赖:描述两个类之间的使用关系,两个类之间是没有关系的,但是一个类的实现需要另一个类的协助,这就产生了依赖
依赖关系的代码表现:
局部变量、方法参数、对静态方法的调用
关联是很稳定的关系,依赖是弱关系。
表现在代码实现中,一个对象会作为另一个对象的属性是关联
一个对象的实现用到另一个对象的方法是依赖
关联有可能是双向关系
依赖不可能是双向关系
- 泛化:就是通常所说的继承关系,泛化针对类型而不针对实例,通常包含类与类之间的继承关系和类与接口实现关系
没有具体对象的类称为抽象类,一般作为父类,用于描述其他类(子类)的公共属性和行为。在类名、操作下方附加一个标记值**{abstract}**表示,也可用斜体表示类名称和属性、方法
- 实现:对应于类和接口之间的关系
总结
两个类之间,分析是否为泛化、实现、依赖。
如果不是,分析是否是聚合、组合。
如果不是,考虑是否存在关联类、自身关联。
如果不是,为普通关联。
类图示例:
名词识别法构建类图
-
人员:系统需要保存或管理其信息的人员,或在系统中中扮演一定角色的人员
-
组织:在系统中发挥一定作用的组织机构
-
物品:需要由系统管理的各种物品,包括无形事物
-
设备:在系统中被使用或由系统进行监控的设备、仪器等,系统运行中的硬件设备(如打印机)除外。
-
事件:需要由系统长期记忆的事件
- 从文档中寻找类(名词)
- 确定类之间的关系
- 确定操作(动词)
- 精化类和类间的关系;绘制类图
顺序图
- 组成
对象–Object
生命线–Lifeline
控制焦点(激活)–Activation
消息–Message
-
对象:通常将发起交互的对象放在左边,将接收消息的对象放在右边。
-
生命线:表示对象存在的时间,如果对象生命期结束,则用注销符号表示
-
控制焦点(激活期):生命线下方的矩形框,表示一个控制焦点,表示对象在现在要产生一个交互活动,发消息或者调用
-
调用消息(同步消息):必须得到回应才能进行下一项操作
-
发送消息(异步消息):只关注消息发送,不关注消息反馈,在发送后继续自己的操作
-
返回消息(虚线表示):表示消息的返回。一般同步的返回不需画出,直接隐含,也可使用返回消息强调返回结果值。异步返回需要返回消息
顺序图示例:
补充
-
消息编号
-
顺序编号:在每个消息的前面加上一个用冒号隔开的顺序号来表示其顺序
-
嵌套编号:把属于同一个对象发送和接收的消息放在同一层进行编号
-
-
选择、循环表示
交互片段可以用于描述系统中不同组件或对象之间的交互流程
-
表示分支的操作符
alt:支持多条件
opt:支持单条件
-
表示循环的操作符:loop
-
分析类
- 边界类:处理系统环境和系统内部间的通信
- 用户界面类
- 系统/设备接口类
- 控制类:定义控制逻辑和事务逻辑
- 实体类:记录系统所需要维护的数据和对这些数据的处理行为
状态图
- 组成
初态
终态
中间状态
转换线
事件(信号事件:异步、调用事件:同步、变化事件、时间事件)
- 初态
- 终态
-
中间状态:用圆角矩形表示,分成上、中、下3部分:
-
上面部分-----为状态的名称;
-
中间部分-----为状态下关键变量的名字和值(可无)
-
下面部分-----是活动表:当前状态下需要做的一些事情,不会改变状态(可无)
-
活动表的语法格式:
事件名(参数表)/动作表达式
常用的3种标准事件:
entry事件指定进入该状态的动作;
exit事件指定退出该状态的动作;
do事件则指定在该状态下的动作。
事件表达式的语法:
事件说明[守卫条件]/动作表达式
守卫条件是一个布尔表达式。如果同时使用事件说明和守卫条件,则当且仅当事件发生且布尔表达式为真时,状态转换才发生。如果只有守卫条件没有事件说明,则只要守卫条件为真状态转换就发生
事件说明和[守卫条件]至少写一个,动作表达式可以没有
- 信号事件:异步
- 变化事件:用关键字When,后面跟布尔表达式
- 时间事件:用关键字After
状态图示例:
总结
三、概要设计
架构设计——系统分成几个层次,采用什么样的体系架构
模块设计——每个层次应该包括那些模块构成,需要创建哪些功能模块
接口设计——模块之间的关系如何,如何进行信息传递
数据设计——数据结构如何,通过最底层的数据库对整个程序功能进行支撑
模块设计的原理——模块化
评价标准:耦合度、内聚度
耦合:如果改变程序中的一个模块,要求另一个模块也同时发生改变,就认为这两个模块发生了耦合
内聚:衡量一个模块内部各个元素(属性、方法)彼此结合的紧密程度。内聚要高,每个模块完成一个相对独立的特定子功能
低耦合、高内聚
耦合度
- 非直接耦合/完全独立(no direct coupling)
不可能存在
- 数据耦合(data coupling)
两模块间通过参数交换信息,且交换的信息仅是数据
维护更容易,对一个模块的修改不会使另一个模块产生退化错误
- 控制耦合(control coupling)
两个模块彼此间传递的信息中有控制信息
模块B种存在if语句,并且判断条件和A相关。
控制耦合多进行解耦,一般会在业务层根据控制条件,调用不同的类。
控制耦合往往是多余的,把模块适当分解之后通常可以用数据耦合代替它
被调用的模块需知道调用模块的内部结构和逻辑,降低了重用的可能性
- 特征耦合(stamp coupling)
把整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元素
- 公共环境耦合(common coupling)
一个模块往公共环境送数据,另一个模块从公共环境取数据。即允许一组模块访问同一全局性的数据结构
- 内容耦合(content coupling)
最高程度的耦合,有下列情况即内容耦合:
-
一个模块访问另一个模块的内部数据
-
一个模块不通过正常入口转到另一个模块的内部;如使用goto
-
两个模块有一部分程序代码重叠
-
一个模块有多个入口
数据耦合传参
控制耦合通过if语句
特征耦合给一堆数据,只用一个
公共环境耦合大家都用公共数据
内容耦合,代码互相夹杂
内聚度
- 偶然内聚(coincidental cohesion)
模块的各成分之间毫无关系;或发现一组语句在两处或多处出现,于是把这些语句作为一个模块以节省内存
- 评价
模块内各元素之间没有实质性联系,很可能在一种应用场合需要修改这个模块,在另一种应用场合又不允许这种修改,从而陷入困境
可理解性差,可维护性产生退化
模块是不可重用的
- 逻辑内聚(logical cohesion)
一个模块完成的任务在逻辑上属于相同或相似的一类
- 评价
接口难以理解,造成整体上不易理解
完成多个操作的代码互相纠缠在一起,即使局部功能的修改有时也会影响全局,导致严重的维护问题
难以重用
- 解决方案
模块分解
- 时间内聚(temporal cohesion)
模块包含的任务必须在同一段时间内执行
- 评价
时间关系在一定程度上反映了程序某些实质,所以时间内聚比逻辑内聚好一些
模块内操作之间的关系很弱,与其他模块的操作却有很强的关联
时间内聚的模块不太可能重用
- 过程内聚(procedural cohesion)
模块内的处理元素是相关的,而且必须以特定次序执行,特定次序是人为赋予的
- 评价:
比时间内聚好,至少操作之间是过程关联的
仍是弱连接,不太可能重用模块
- 通信内聚(communicational cohesion)
模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据,即在同一个数据结构上操作
- 评价
模块中各操作紧密相连,比过程内聚更好。
不能重用
- 解决方案
分成多个模块,每个模块执行一个操作
- 顺序内聚(sequential cohesion)
模块的各个成分和同一个功能密切相关,而且这些处理必须顺序执行(一个成分的输出作为另一个成分的输入)
- 功能内聚(functional cohesion)
模块内所有处理元素(每个处理都是必不可少)属于一个整体,完成一个单一的功能
- 评价
模块可重用,应尽可能重用
可隔离错误,维护更容易
扩充产品功能时更容易
顺序内聚,根据需求可有也可以拆
通信内聚,对同一个数据结构做操作
过程内聚,人为限定执行顺序
时间内聚,同一时间段完成的没什么关系的多个任务
逻辑内聚,逻辑控制语句来决定那部分操作。和控制耦合对应。一般进行拆模块
偶然内聚,完全没关系,一般不用
设计原则
- Liskov替换原则(LSP)
任何基类可以出现的地方,子类一定可以出现
另一种表达方式:子类不能添加任何基类没有的附加约束
这些约束很可能造成使用者无法通过子类正常的使用针对基类的程序
实际操作中,基类往往就是抽象类(行为没有任何实现),甚至是接口
- 开放-封闭原则(OCP)
软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。是最重要的,核心设计原则
核心思想:是对抽象编程,因为抽象相对稳定
- 单一职责原则(SRP)
一个类被改变的原因不能超过一个,也就是说一个类只有一个职责
- 接口隔离原则(ISP)
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
- 依赖倒置原则(DIP)
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
高层将服务声明写入抽象层中,底层根据所能实现的服务,就和按个抽象层发生关联。这样割断了高层和底层之间的直接依赖关系,抽象成为整个系统中最稳定的部分
组合/聚合原则:尽量多用聚合方式,少用泛化关系
架构设计
-
软件架构
一系列相关的抽象,用于指导大型软件系统各方面的设计,是一个系统的草图
- 主机结构:一台主机多个终端
- C/S(客户机/服务器)
- B/S(浏览器/服务器):演化出多层架构,都是基于MVC模式
- SOA:面向服务的结构
- 云架构
小型图书资料管理系统-架构设计
Bo,存放各种业务处理类和实体类的操作
Po,存放各种实体类,只含set和get方法,用来创建对象,不对其做任何操作,在使用时传出
Db,处理和数据库打交道的方法
DTO,从Db类获取的数据,封装成对象,交给DTO,将来返回给控制层
定义架构后,开始的概要设计包括:
-
问题域子系统的设计(PDC)(重点)
面向对象设计仅需从实现角度对问题域模型做一些补充或修改,主要是增添、合并或分解类与对象、属性及服务,调整继承关系等等。(基于使用的框架、设计原则和设计模式)
结果表现形式:实现阶段的对象模型——类图
辅助以相关文档,描述每个类的职责,类的属性,类的服务(服务的参数,服务的功能)
-
人机交互部分的设计(HIC)(重点)
是OOD模型的外围组成部分
确定人机交互的细节,其中包括指定窗口和报表的形式、设计命令层次等项内容
-
数据部分的设计(DMC)
边界类
用于描述外部参与者与系统之间的交互
-
用户界面类:用户和系统进行通信
-
系统接口类:该系统和其他软件系统进行通信
-
设备接口类:对硬件设备使用,如各类驱动程序
如何标识边界类?
每一对“用例—参与者”之间确定一个边界类
问题域模型设计
对问题与结构,从实现角度做一些补充或修改,主要是增添、合并或分解类与对象、属性和服务,调整继承关系等
“血”指的是domain object的model层内容
- 失血模型
domain object只有属性的get set方法的纯数据类,所有的业务逻辑完全由Service层来完成的,由于没有DAO,Service直接操作数据库,进行数据持久化
- 贫血模型
domain ojbect包含了不依赖于持久化的原子领域逻辑,而组合逻辑在Service层(常见)
- 充血模型
绝大多业务逻辑都应该被放在domain object里面,包括持久化逻辑,而Service层是很薄的一层,仅仅封装事务和少量逻辑,不和DAO层打交道
贫血模型和充血模型的差别在于,领域模型是否要依赖持久层,贫血模型是不依赖的,而充血模型是依赖的
- 胀血模型
取消了Service层,只剩下domain object和DAO两层,在domain object的domain logic上面封装事务
控制类
协调边界类和实体类
- 每个用例都对应有一个控制类
- 一个控制类可以对应多个用例
数据部分的设计(DMC)
将类的属性映射成表的一个字段
特殊情况:
并不是类中的所有属性均是永久的。例如,发票中的“合计”属性可由计算所得而不需保存在数据库中,此时该类属性(称为派生属性)不映射
一般地,类中的属性是单值的,但如果在类中存在多值属性,则该属性映射成多个字段
- 关系到数据库的映射方法
- 一对一关联
两个实体分别映射两张表,两张表主键一样(或一个主键作为另一个的外码)
- 一对多关联
一的主键作为多的外键
- 组合与聚合
一的主键作为多的外键
- 自身关联
自身关联映射到一张表,然后定义一个父节点属性就可以表示
泛化关系:将父子类关系映射为一张表,以父类id作为主键,通过类型字段区分子类,将父子类中其他属性值分别加入表中。实现从类到表的映射
缺点:里面的字段值可能为空。对空字段进行拆分。
- 整个类层次映射为单个数据库表
- 每个具体子类映射成单个数据库表
- 每个类均映射为数据库表
四、详细设计
- 模块接口:类接口输入输出数据
类的详细描述,内含数据、方法及方法的参数返回值
-
算法描述:对模块的实现算法设计并表述
-
流程图
-
判定表
当算法中包含多重嵌套的条件选择时,判定表能够清晰地表示复杂的条件组合与应做的动作之间的对应关系
适合:单一条件只有两种结果供选择
由4部分组成:
-
左上部列出所有条件
-
左下部是所有可能做的动作
-
右上部是表示各种条件组合的一个矩阵
-
右下部是和每种条件组合相对应的动作
-
-
-
数据描述:局部数据结构
模块在运行过程中数据的输入、输出、存储和处理情况,以及模块内部对象之间的关联和交互
eg.假设某航空公司规定,乘客可以免费托运重量不超过30kg的行李。
当行李重量超过30kg时,对头等舱的国内乘客超重部分每公斤收费4元,对其他舱的国内乘客超重部分每公斤收费6元。
对外国乘客超重部分每公斤收费比国内乘客多一倍,对残疾乘客超重部分每公斤收费比正常乘客少一半。
五、软件项目的测试
测试方案
包括
- 具体的测试目的(例如,预定要测试的具体功能)
- 应该输入的测试数据
- 预期的结果
- 通常又把测试数据和预期的输出结果称为测试用例
白盒测试
适用于对单一模块测试内部结构是否和详细设计相同
逻辑覆盖法
- 语句覆盖
选择足够的测试用例,使得程序中每一条可执行语句至少被执行一次
特点:
语句覆盖对程序的逻辑覆盖很少
语句覆盖不能走过所有支路
语句覆盖是很弱的逻辑覆盖标准
- 判定覆盖
不仅每个语句必须至少执行一次,而且每个判定的每种可能的结果都应该至少执行一次
既然语句覆盖对逻辑不作判定,则增加内容扩充为判定覆盖
- 条件覆盖
不仅每个语句至少执行一次,判定表达式中的每个条件都取到各种可能的结果
特点:
条件覆盖通常比判定覆盖强,因为它使每个条件都取到了两个不同的结果,判定覆盖却只关心整个判定表达式的值。判定覆盖不一定包含条件覆盖,条件覆盖也不一定包含判定覆盖
- 判定/条件覆盖
使得判定表达式中的每个条件都取到各种可能的值,每个判定表达式也都取到各种可能的结果
- 条件组合覆盖
使得每个判定表达式中条件的各种可能组合都至少出现一次
特点:
条件组合覆盖是前述几种覆盖标准中最强的
满足条件组合覆盖标准的测试数据,也一定满足判定覆盖、条件覆盖和判定/条件覆盖标准
但是,条件组合覆盖标准的测试数据并不一定能使程序中的每条路径都执行到
- 路径覆盖(了解即可)
使得程序中的所有可能路径都至少被执行一次
总结:
-
语句覆盖发现错误能力最弱
-
判定覆盖包含了语句覆盖,但它可能会使一些条件得不到测试
-
条件覆盖对每一条件进行单独检查,一般情况它的检错能力较判定覆盖强,但有时达不到判定覆盖的要求
-
判定/条件覆盖包含了判定覆盖和条件覆盖的要求,但实际上不一定达到条件覆盖的标准
-
条件组合覆盖发现错误能力较强,凡满足其标准的测试用例,也必然满足前 4 种覆盖标准
以上五种覆盖方法,基本上是依次增强的(除少数如:条件覆盖和判定覆盖)。随覆盖级别的提高,所需设计的测试用例数量也急剧增加,开销数量级的加大
黑盒测试
等价类法
在输入数据中选择一些有代表性的数据进行测试
等价类:功能相同或作用相同的一类数据
- 有效等价类
尽可能多地覆盖尚未被覆盖的有效等价类
- 无效等价类
使它覆盖一个而且只覆盖一个尚未被覆盖的无效等价类
边界值分析法
一个用例尽量覆盖多的有效边界
一个用例只能覆盖一个无效边界
边界值:指输入等价类和输出等价类边界上的数据
- 划分等价类
- 找等价类的边界
软件测试
- 单元测试
驱动程序:接收测试数据,传递给被测试的模块,并且印出有关的结果
存根程序:代替被测试的模块所调用的模块
-
集成测试
集成测试更多用于测试各模块之间的接口是否正确
-
功能性测试:使用黑盒测试技术针对被测模块的接口规格说明进行测试
-
非渐增式测试方法:先分别测试每个模块,再把所有模块按设计要求放在一起
看似省了步骤,但是由于其复杂性导致不可操作
-
渐增式测试方法:把下个要测试的模块同已测试好的模块结合起来测试
- 自顶向下
- 深度优先:先组装在软件结构的一条主控制通路上的所有模块(先深后宽)
- 宽度优先:沿软件结构水平地移动,把处于同一个控制层次上的所有模块组装起来(先宽后深)
- 自底向上
- 自顶向下
-
-
非功能性测试:对模块的性能或可靠性进行测试
-
-
回归测试
重新执行已经做过的测试的某个子集。以保证上述这些变化没有带来非预期的副作用
-
确认测试(系统测试)
检查软件能否按合同要求进行工作
确认测试偏向于整个大系统是否正确完成
-
验收测试(交付测试)
- 内部测试(Alpha测试)
- 第三方测试(Beta测试)
七、软件项目管理
WBS工作分解结构
工作分解任务是以列来进行的:
-
第一列系统
-
第二列分配角色
-
第三列当前角色具有的功能模块
-
第四列该功能模块具体需要完成什么样的任务
使用此方式把一个项目,按一定的原则进行分解,项目分解成任务,任务再分解成一项项工作,再把一项项工作分解到每个人的日常活动中,直到分解不下去为止
Gantt图