#

# 概述

# 软件工程概念

软件危机在 20 世纪
70 年代表现得尤其严重,具体表现有:超预算、超期限、质量差、用户不满意、开发过程无法有效介入和管理、代码难以维护等。
IEEE 对软件工程的定义是:将系统化、规范化、可量化的工程原则和方法应用于软件的开发、运行和维护及对其中方法的理论研究,其主要目标是高效开发高质量的软件,降低开发成本。

系统工程是为了更好地达到系统目标,对系统的构成要素、组织结构、信息流动和控制机构等进行分析与设计的技术。针对不同的领域,系统工程有着不同的实现方法,如商业过程工程 (
Business Process Engineering)、产品工程 (Product Engineering)
等。系统工程的目的是使人们确保在正确的时间使用正确的方法做正确的事情。
系统分析的常用方法也是层次分析方法,它将问题分解为不同的组成因素,并按照因素间的相互关联影响及隶属关系将因素按不同层次聚集组合,形成一个多层次的分析结构模型。统一建模语言 (
UML) 提供了一整套对系统建模的基础设施,包括模型的表示及建模的方法等,可以适用不同的系统层次。

UML 是一种语言,或者说是一种工具,而不是一种方法。UML 将软件开发中的语言表示与过程进行了分离,具有如下重要的功能:可视化 (
Visualization)、规格说明 (Specification)、构造 (Constructing) 和文档化 (Documenting)

软件工程三个要素:方法、工具和过程。

  • 方法是完成软件开发各项任务的技术,回答 “如何做”;
  • 工具是为方法的运用提供自动或半自动软件支撑环境,回答 “用什么做”;
  • 过程是为获得高质量的软件要完成的一系列任务的框架,规定完成各项任务步骤,回答 “如何控制、协调、保证质量”。

# 软件工程开发方法

传统方法:又称结构化方法

  • 软件过程划分为若干个阶段
  • 每个阶段有各自的任务
  • 阶段之间有某种顺序性

面向对象方法:

  • 对象作为融合数据及在数据之上的操作行为的统一的软件构件。把所有对象都划分成类 (Class)。每个类都定义了一组数据和一组操作。(封装)
  • 按照父类 (或称为基类) 与子类 (或称为派生类) 的关系,把若干个相关类组成一个层次结构的系统 (也称为类等级)
    。在类等级中,下层派生类自动拥有上层基类中定义的数据和操作,称为继承。
  • 对象彼此间仅能通过发送消息互相联系-封装性。
  • 重视重用

# 软件开发过程

软件开发过程 (Software Development Process) 又称软件开发生命周期 (Software Development Life Cycle,SDLC)
,是软件产品开发的任务框架和规范,又可以简单地称为软件生命周期

# 传统生命周期模型

瀑布模型

特点:文档驱动、质量保证。
缺陷:难以应对需求变化

快速原型模型
类似于图 2.2,但需将图中的 “需求分析” 改为 “原型开发”。
特点:要求对系统进行简单、快速的分析,快速构造一个软件原型,用户和开发者在试用或演示原型过程中加强沟通和反馈,通过反复评价和改进原型
缺陷:快速建立起来的模型可能由于不符合各种开发规范,再加上不断的修改,质量一般都比较差,所以,通常在实际开发过程中会完全抛弃之前建立起来的原型系统

增量模型

指一步一步将软件建造起来。在增量模型中,软件被视为一系列的增量构件来设计、实现、集成和测试,每个构件是由多种相互作用的模块所形成的提供特定功能的代码片段组成的。
特点:软件开发可以较好地适应变化,客户可以不断地看到所开发的软件,从而降低开发风险。
缺陷:需要软件具备开放式的体系结构;容易退化为 “边做边改” 模型,从而使软件过程的控制失去整体性。

螺旋模型

特点:螺旋模型由风险驱动,强调可选方案和约束条件,从而支持软件的重用,有助于将软件质量作为特殊目标融入产品开发中。
缺陷:风险分析需要成本。适用于内部的大规模软件开发。

喷泉模型

生存期的各个阶段可以相互重叠和多次反复,而且在项目的整个生存期中还可以嵌入子生存期
特点:面向对象

# 敏捷生命周期模型

增量交付与迭代开发是敏捷生命周期模型的两个基本特点
采用敏捷生命周期模型能够给企业和用户带来诸多好处:

  • 精确。产品由开发团队和用户反馈共同推动。
  • 质量。使用测试驱动开发 (Test Driven Development,TDD)
  • 速度。避免较大的前期规划,开发项目中当前最需要、最具价值的部分
  • 丰厚的投资回报率。最具价值的功能总是被优先开发
  • 高效的自我管理团队

极限编程 (Extreme Programming,XP)
XP 鼓励客户代表每天都与开发团队在一起
建议所有的编程都采用结对编程 (Pair Programming) 的方式
XP 注重可用于开发活动的实践,包括一系列核心的做法,也称最佳实践 (Best Practices)。

  • (1) 小规模,频繁的版本发布,短迭代周期。
  • (2) 测试驱动开发 (Test Driven Development)。
  • (3) 结对编程 (Pair Programming)。
  • (4) 持续集成 (Continuous Integration)。
  • (5) 每日站立会议 (Daily Stand-up Meeting)。
  • (6) 共同拥有代码 (Collative Code Ownership)。
  • (7) 系统隐喻 (System Metaphor)。

Scrum

  • Scrum 将开发过程分为多个冲刺 (Sprint) 周期,每个 Sprint 代表一个 1~4 周的开发周期,有固定的时间长度。

  • 在 Sprint 计划会议 (Sprint Planning Meeting) 上,最重要或者是最具价值的产品需求积压被优先安排

  • 每天开发团队都会进行一次简短的 Scrum 会议 (Daily Scrum Meeting)

  • 每个 Sprint 周期结束后,都会有一个可以被使用的系统交付给客户,并进行 Sprint 评审会议 (Sprint Review)

  • 随后,有一个回顾会议 (Sprint Retrospective),总结上次 Sprint 周期

    4 种角色:

  • 产品拥有者 (Product Owner):平衡所有利益。开发团队和客户或最终用户之间交流的联络点。

  • 涉众 (Stakeholder):产品之间有直接或间接的利益关系。

  • 专家 (Scrum Master):开发团队与产品拥有者之间交流的联络点。

  • 团队成员 (Team Member):即项目开发者。

DevOps
基本的敏捷原则,强调了 “个体和交互胜过流程和工具” 的作用
另一个核心目标是自动化和可持续交付

# 需求分析

需求分析阶段一般通过一份需求规格说明书的文档描述项目应该实现的内容。
可行性分析活动。在可行性分析中,需要明确系统的功能性需求、边界位置,以及在技术、经济、法律或操作等方面,项目是否能顺利进行,是否存在潜在的风险
识别出所有的涉众后,接下来需要定义待开发系统的目标

下一步是确定与各涉众相关的系统核心功能
使用的工具:UML 用例图
用例规约是对每个用例的细化
用例规约模板中,有两个使用文本方式描述的事件流:基本事件流和备选事件流。
对于处理流程的建模,使用 UML 中的活动图是非常合适的

活动图中还有 “泳道 (Swimlane)” 的概念。泳道是一种角色的划分方法,每个角色的活动放置在对应的泳道中。

# 非功能性需求

关于系统可用性方面的需求例如,系统的响应时间,它使用户能够有效地使用软件工作,不允许存在较大的延迟。
软件产品某些方面的要求,如可移植性或者文档的完整性等。
质量需求(安全性、可靠性、可维护性……),技术性需求 (硬件需求),其他交付物,合同需求,规格说明

# 软件架构

软件架构为软件系统提供了一个结构、行为和属性的高级抽象,由构成系统的元素的描述、这些元素的相互作用、指导元素集成的模式及这些模式的约束组成。软件架构不仅指定了系统的组织 (
Organization) 结构和拓扑 (Topology) 结构,并且显示了系统需求和构成系统的元素之间的对应关系,提供了一些设计决策的基本原理。


管道与过滤器风格的软件架构中,每个构件都有一组输入和输出,构件读取输入的数据流,经过内部处理,然后产生输出数据流。典型的管道与过滤器风格的例子是以
UNIX Shell 编写的程序。

层次结构风格最广泛的应用是分层的网络通信协议,如 ISO 的七层协议栈结构

在仓库 / 黑板系统风格中,有两种不同的构件:中央数据结构,说明当前状态;独立构件,在中央数据存储上执行。传统应用是信号处理领域,如语音和模式识别,另外的应用包括松耦合代理数
据共享存取等。

正交软件结构由组织层和线索的构件构成。每一条线索的实现与其他线索的实现无关或关联很少,在同一层中的构件之间不存在相互调用。每个需求变化仅影响某一条线索,而不会涉及其他线索

MVC 全名是 Model View Controller,是 “模型 (Model)-视图 (View)-控制器 (Controller)” 的缩写。

# 类的分析与设计

# 类的确定

工具:UML 类图
在类的分析与设计阶段,类通常可以分为实体类 (Entity Class)、控制类 (Control Class) 和边界类 (Boundary Class) 三种。

  • 实体类对应系统需求中的每个实体,它们通常需要在存储介质上持久保存,如使用数据
    库表或文件来记录。
  • 控制类用于体现应用程序的执行逻辑,提供相应的业务操作。控制类一般是由动宾结构的短语 (动词 + 名词)
    转化来。如增加商品可以对应一个 “商品增加” 类,用户注册可以对应一个 “用户注册” 类等
  • 边界类用于对外部用户与系统之间的交互对象进行抽象,主要包括界面类 (如对话框、窗口、菜单等) 及与外部系统的数据交换类 (
    如同步、缓存等)。

细化的类:管理类和控制类

  • 管理类理解为对同类对象的协调和管理,主要负责同类对象的创建、
    代理访问其他对象的信息等。
  • 控制类用于对一个或几个用例所特有的控制行为进行建模。通常控制和协调不同
    对象的行为,用来封装用例的特有行为。

# 验证

工具:UML 顺序图
顺序图可以对复杂业务场景进行辅助分析,补充类图中的功能说明,确认是否实现了活动
图中描述的功能性需求

# 类的详细设计

# 类方法的设计

引入结构化程序设计的目的是简化软件设计的过程,仅使用有限的可预测的操作完成相应的算法流程。通过复杂程度的度量,可以看到结构化程序设计能提高程序的可读性、可测试性及可维护性。
图形工具

  • 程序流程图
  • 盒图
  • PAD 图

表格工具
判定表是一种进行详细设计的表格工具,又称决策表

语言工具
程序设计语言 (Programming Design Language, PDL) 是一种用来进行详细设计的语言工具,又称结构化语言或伪代码。

# 类行为的设计

两种类:有状态、无状态。
对象的状态及状态变化可以借助状态图 (State Diagram) 或有穷状态机 (Finite State Machine) 进行描述
工具:状态图

对象约束语言:UML 提供了一种对每个 UML 对象进行条件约束的机制,并通过将约束条件放置于花
括号中进行表达。

# 设计优化

# 设计原则

  • 接口隔离原则:应尽量使用 “接口继承” ,而非 “实现继承”
  • 依赖倒置原则:应依赖于抽象,而不要依赖于具体
  • 开放封闭原则:对扩展开放,对修改封闭。模块的行为可以被扩展,以满足新的需求;模块的源代码不需要进行修改。
  • Liskov 替换原则:任何出现父类的地方都应该能使
    用子类对其进行无条件的替换
  • 单一职责原则:类功能应该只有一个,而不应该为两个或多个
  • 合成 / 聚合复用原则:应尽量使用合成 / 聚合形式的委托重用,尽量不使用继承重用

# 设计模式

观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;
当这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
MVC 结构在实现上就使用了观察者模式,其中的主题对象相当于 MVC 中的模型,观察者对
象相当于 MVC 中的视图。
每个观察者 (Observer) 对象为了得到主题 (Subject) 对象
的及时通知,需要事先在 Subject 对象中进行订阅,并且在不需要时进行退订。每个具体的
Observer 对象需要实现自己的更新方法 update ()。

桥接模式
桥模式的主要思想是将抽象部分与实现部分 (行为)
分离,使它们都可以独立地变化。用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系


# 软件测试

依据阶段划分:

依据细节程度划分:

# 软件度量

方法的复杂程度可以通过 McCabe 指标进行度量,也称为 McCabe 环形复杂度,它主要以
方法的控制流图结构为基础进行计算。

内聚性:

# 等价类测试

是一种黑盒测试的方法
等价类的重要的特性,即完备性和不相交性,也是测试设计的数学基础

# 基于控制流的测试

属于白盒测试。基于程序流图。

语句覆盖

分支覆盖

条件覆盖

这里的原子谓词是指在程序中不能继续再拆分的布尔表达式,如上例中条件 if
(input<0||input%2==1) 中含有两个原子谓词,再加上 i>0 的原子谓词,共有三个原子谓词

路径覆盖
路径是指从方法的入口开始到出口结束,由控制流图中若干条边构成的一条唯一的执行路线,也可以看成由若干可能的逻辑条件的组合。

  • (1) 绘制程序的控制流图。
  • (2) 计算 McCabe 环形复杂度。从程序的环形复杂性可导出程序基本路径集合中的独立路
    径条数,独立路径要求在路径中至少含有一条未曾使用过的边。
  • (3) 导出测试用例。为每一条基本路径设计测试用例的数据输入和预期结果,并确保覆盖
    到基本路径集中的每一条路径,参照环形复杂度规定的上限路径条数。

# 其他

能力成熟度模型 (Capability Maturity Model,CMM)

持续集成的目的就是让产品
可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到基线之前,必须通过自
动化测试。

更新于 阅读次数