我将会在2024年6月20日9至11时参加校方组织的《软件工程》课程期末考试。因此在这里作复习笔记。使用的课本是清华大学出版社出版的《软件工程导论(第6版)》(ISBN 978-7-302-33098-1)。

软件工程学概述(第1章)

软件危机

在计算机软件的开发和维护过程中所遇到的一系列严重问题。主要表现如下:

  1. 对软件开发的成本与进度估计经常不准确;
  2. 用户经常对已完成的软件系统不满意;
  3. 软件产品质量不可靠;
  4. 软件常常不可维护;
  5. 软件没有适当的文档材料;
  6. 软件成本在计算机系统总成本中占比逐年上升;
  7. 软件开发生产率的上升速度跟不上计算机应用普及的趋势。

产生软件危机的原因主要分为软件本身特点软件开发与维护方法有误两方面;消除软件危机的途径之一即对「软件」二字有一个正确的认识。陈腐的观念认为“软件就是程序”,这并不正确——软件应是程序、数据和相关文档的完整集合。其中程序是能够完成预期功(性)能的可执行指令序列,数据是使程序可以适当处理信息的数据结构,相关文档是开发、使用或维护程序所需的图文资料。

软件工程

指导计算机软件开发和维护的一门工程学科。

1993年IEEE给软件工程进行了一个全面而具体的定义,即“把系统、规范且可度量的途径应用于软件的开发、运行和维护过程(也就是将工程应用于软件),并研究这些途径的学科”。它的本质特性如下:

  1. 关注于大型程序构造
  2. 中心课题是控制复杂性
  3. 软件经常变化
  4. 重视开发软件效率
  5. 视和谐合作为开发软件之关键
  6. 以支持用户工作为软件开发目的
  7. 由具有一种文化背景的人替具有另一种文化背景的人创造产品

基本原理

软件工程共7条基本原理。

  1. 用分阶段的生命周期计划严格管理;
  2. 坚持进行阶段评审;
  3. 实行严格产品控制;
  4. 采用现代程序设计技术;
  5. 结果应可清楚地被审查;
  6. 开发小组人员少而精;
  7. 承认不断改进软件工程实践的必要性。

方法学

目前使用最广泛的是传统方法学面向对象方法学两种。

软件生命周期

软件定义软件开发运营维护(或软件维护)共3个时期组成。

软件过程

为了获得高质量软件所需要完成的一系列任务之框架。其描述为了开发出客户所需的软件所应满足的「Who, When, What, How」之条件。

生命周期模型一般用于简洁地描述软件过程;因此生命周期模型也成为过程模型。典型的过程模型主要包括瀑布模型快速原型模型增量模型螺旋模型喷泉模型

可行性研究(第2章)

任务:确定问题是否值得去解决。

具体步骤:进一步分析和澄清问题定义,随后导出系统的逻辑模型。最后从这一模型出发,探索各种可选的主要解法。一般从技术、经济、操作三个方面讨论各种解法的可行性;必要时还需考虑社会效益、法律等因素。

系统流程图

以用图形符号通过“黑盒子”形式描绘组成系统的各个部件为基本思想,概括地描绘物理系统的传统工具。一般用矩形表示处理部件,用非矩形平行四边形表示输入输出I/O部件,用圆形指出转到流程图的哪一部分或从流程图的哪一部分转来,用向下的五边形(上端2个角均为直角,非水平的4条边长度相等)指出转到哪一页的图上或者从哪一页的图上转来,用箭头表示数据流及其流向

数据流图

描绘信息流和数据从输入移动到输出的过程中所经受的变换的图形化技术。其无法描绘物理部件,只能描绘数据在软件中流动和被处理的逻辑过程。一般用正方形(体)表示数据起迄点,用圆(角矩)形表示变换数据的处理,用两条平行的水平线表示数据存储,用箭头表示数据流及其流向

数据流图中应描绘所有可能的数据流向,不能描绘出现某个数据流的条件。

数据字典

关于数据的信息的集合,也即对数据流图中包含的所有元素的定义的集合。一般包括数据流数据流分量数据存储处理四类元素的定义。

成本效益分析

成本估计

软件开发成本以人力消耗为主。通过代码行技术任务分解技术自动估计成本技术可以估算成本。

自动估计成本技术需要大量历史数据为基础,因此需要良好的数据库系统支持。

分析方法

货币时间价值

类似于利息,不与赘述。

投资回收期

是累积的经济效益等于最初投资所需要的时间。根据定义不难看出这是衡量一项开发工程的价值的重要指标。

纯收入

(在折合成当前货币值之后)整个生命周期内系统的累积经济效益与投资的差。显然如果通过这种算法得到的纯收入如果不超过0,那还不如拿这些钱去银行存着,所以对应的开发显然是不值得投资的。

投资回收率

解方程

P=i=1nFi(1+j)iP=\sum_{i=1}^n\frac{F_i}{(1+j)^i}

得到的jj的值。其中,PP是现在的投资额,FiF_i是第ii年年底的效益,nn是系统的使用寿命。

需求分析(第3章)

任务

确定对系统的综合要求

通常对软件系统有功能、性能、可靠(用)性、出错处理、接口、约束(设计或实现时应遵守的限制条件)、逆向(软件系统不应该做什么)这几方面有要求。除此之外,将来可能提出的要求也需要被分析;这可以在设计过程中预先对这些系统将来可能的扩改进行准备,以备不时之需。

分析系统的数据要求

通常采用建立数据模型的方法来分析系统数据要求。

导出系统逻辑模型

综合对系统的综合要求和数据要求后就可以导出详细的逻辑模型,一般用数据流图、实体联系图、状态转换图、数据字典和主要处理算法的形式来描述这个逻辑模型。

修正系统开发计划

通过分析可以对系统有更深入具体的了解,从而准确地估计系统成本与进度,进而实现对先前制定的开发计划进行修正。

获取需求的方法

主要包括访谈、面向数据流自顶向下求精、应用规格说明技术、快速建立软件原型这四种方法。其中,快速建立软件原型最为准确,也最有效、最强大。

分析建模与需求规格说明

需求分析过程应该建立数据、功能和行为三种模型。

软件需求规格说明书是需求分析阶段得出的最主要的文档。主要具有容易书写、容易理解的特点。

实体—联系图(ER图)

包含了数据对象(实体)、关系和属性3种基本成分,通常用矩形框表示实体,菱形框(与实体相连)表示关系,椭圆形或圆角矩形表示属性。

ER模型也可以作为用户和分析员之间有效的交流工具。

数据规范化

通常用“范式”表示消除数据冗余的程度。其中,定义第一范式NF1为数据冗余程度最大,第五范式NF5为最小。范式级别越高,存储同样数据就需要分解为越多的表,数据存储结构与基于问题域的结构之间的匹配程度也就越低,因此需求变化时数据稳定性也就越低;除此之外,需要访问的表增多也会导致性能下降。因此,一般都选用第三范式。

第一范式中每个属性值都是原子值——其仅仅是一个简单值,不含内部结构。

第二范式满足第一范式的所有条件,且每个非关键字属性都由整个关键字决定。

第三范式满足第二范式的所有条件,且每个关键字属性都由且仅由整个关键字决定。第三范式的一个非关键字属性不能仅仅是对另一个非关键字属性的进一步描述(也就是非关键字属性的值不依赖于另一个)。

状态(转换)图

初态用实心圆表示,终态用一对同心圆表示(其中内圈为实心)。中间状态用圆角矩形表示,其可以被两条水平线划为上、中、下3个部分。

验证软件需求

一致性、完整性、现实性、有效性这4个方面验证。

  • 一致性:任何一条需求不应与其他需求矛盾。
  • 完整性:规格说明书应包括用户需要的每一个功(性)能。
  • 现实性:指定需求应该是现有硬软件技术可以实现的。
  • 有效性:需求正确有效,确实可以解决用户面对的问题。

方法

可以通过人工技术审查验证一致性。分析员可以参照以往开发类似系统的经验验证现实性。

完整性和有效性需要在用户的密切合作下完成。

面向对象方法学(第9章)

主要包括4个要点。

  1. (Objects)认为客观世界是由各种对象组成的,任何事物都是对象,复杂的对象可以由比较简单的对象以某种方式组合而成;
  2. (Classes)把所有对象都划分成各种对象类class,每个对象类都定义了一组数据和一组方法;
  3. (Inheritance)按父子类的关系把若干个对象类组成一个层次结构系统;
  4. (Communication with messages)对象彼此之间仅能通过传递消息互相联系。

概念

对象Object

在应用领域中有意义的、与所要解决的问题有关系的任何事物。其可以理解为具有相同状态的一组操作的集合,也可以理解成对属性值和操作的封装

Class

具有相同数据或操作的一组相似对象。

实例Instance

由某个特定的类所描述的一个具体的对象。

消息Message

要求某个对象执行在定义它的那个类中所定义的某个操作的规格说明。一般包括接收消息的对象、消息选择符(消息名)和变元三个部分。

方法Method

对象所能执行的操作,也就是类中所定义的服务。

属性Attribute

类中所定义的数据。C++中也把它叫做“数据成员”。

现在假设一个名为Circle的类。

1
2
3
4
5
6
7
8
#include <cstdio>
using namespace std;
class Circle{
public:
double radius;
double coordinate_x;
double coordinate_y;
}

在这其中的radius(半径)、coordinate_x(圆心横坐标)和coordinate_y(圆心纵坐标)就是这个类的数据成员,也就是Circle(圆)的属性。

封装Encapsulation

把数据和实现操作的代码集中起来放在对象内部,使外界不知道对象的具体内容。

一个对象要想拥有封装性,其必须有一个清晰边界,有确定的接口协议,并且内部实现受保护

通过封装,对象的实现细节得以对外界隐藏。因此本质上封装也是一种信息隐藏。

继承Inheritance

子类自动共享父类中定义的数据与方法的机制。

多态性Polymorphism

子类对象可以像父类那样使用,同样的消息即可以发送给父类也可以发送给子类。

重载Overloading

函数重载

在同一作用域内的若干参数特征不同的函数可以使用相同的函数名。

运算符重载

同一个运算符可以施加于不同类型的操作数。

重载进一步提高了面向对象系统的灵活性和可读性。

面向对象建模

通过对象模型、动态模型或功能模型建模。

面向对象实现程序开发(第12章)

根据未来是否占主导地位、可重用性、类库和开发环境来选择面向对象语言。

测试用例

对于已经明确了对输入有要求的系统,可以利用一些分析方法设计测试用例。

现在先考虑一个实例。

某企业公开招聘规定报名者年龄应在20~38周岁之间(出生年月在1987年1月1日至2004年12月31日之间)。报名程序具有自动检验输入数据的功能,如出生年月不在上述范围内将拒绝接受,并将显示「年龄不合格」等出错信息。

等价分类法

根据情景要求,显然只要输入出生年份和出生月份两个数。当出生年月在1987年1月至2004年12月这个区间内,才应该是有效数据。

因此可以得到等价分类表如下。

输入条件 有效等价类 编号 无效等价类 编号
出生年份 1987至2004之间的整数 1 小于1987的整数 3
大于2004的整数 4
小数 5
特殊字符 6
汉字 7
8
字母 9
出生月份 1至12之间的整数 2 小于1的整数 10
大于12的整数 11
小数 12
特殊字符 13
汉字 14
15
字母 16

在等价分类表中,所有等价类的编号不能存在相等的两个。

然后根据上表可以设计一种测试用例如下:

测试用例编号 出生年份 出生月份 预期输出 覆盖等价类
1 1990 3 不显示出错信息 1、2
2 1990 0 显示出错信息 1、10
3 1990 13 显示出错信息 1、11
4 1990 1.2 显示出错信息 1、12
5 1990 ½ 显示出错信息 1、13
6 1990 显示出错信息 1、14
7 1990 显示出错信息 1、15
8 1990 March 显示出错信息 1、16
9 281 3 显示出错信息 2、3
10 2050 3 显示出错信息 2、4
11 1987.1 3 显示出错信息 2、5
12 3 显示出错信息 2、6
13 3 显示出错信息 2、7
14 3 显示出错信息 2、8
15 Eighty-seven 3 显示出错信息 2、9

边界值分析法

本质上边界值分析法应该是等价类法的补充。

在上方的情景中注意到年份的边界值是1987和2004,因此只要选取1986、1987、1988、2003、2004、2005作为测试用例即可。类似地,出生月份的测试用例可以是0、1、2、11、12、13。

程序的环形复杂度

一般用来度量程序的逻辑复杂度。这里使用McCabe方法。

可以对程序画出流程图。若流程图中判定节点个数为PP,整个图中节点数为NN,边数为EE,则环形复杂度

V=P+1=EN+2.V=P+1=E-N+2.