十、软件工程
软件工程是指合理利用工程方法和原则写出能在真实机器上工作的可靠软件的过程。
10.1 软件生命周期
软件生命周期是软件工程中的基础概念,软件和其他产品一样,周期性地重复着一些阶段。
软件开发完成之后,通常需要使用一段时间才会修改。修改可能是由于软件中的bug、设计规则或公司本身发生变化导致的,软件在过时之前会一直重复使用和修改这一回路。当软件的效率低下或是用户需求发生了重大变化后,软件就会由于过时而失去它的有效性,软件地生命周期就到此停止。
10.2 开发过程模型
在软件的生命周期中,开发过程分为四个阶段:分析、设计、实现和测试,开发过程有两个常见模型:瀑布模型和增量模型。
10.2.1 瀑布模型
瀑布模型是指开发过程只朝一个方向进行,也就是说前一个阶段没有完成,后一个阶段就不能开始。
瀑布模型的优点之一是每一个阶段开始前,它前面的所有阶段都已经完成。例如,在实现阶段开始前,分析和设计阶段就已经完成,所以程序员在实现阶段清楚的知道他们要实现一些什么样的功能等。瀑布模型的缺点就是难以定位问题,当软件出现错误时,需要对整个过程进行检查。
10.2.2 增量模型
在增量模型中,开发者首先完成一个软件的简化版本,这个版本中不包括具体的细节。然后在第二个版本中向软件中加入更多细节,完成测试后继续开发第三个版本,知道软件的所有功能被实现。在这种模型中,如果在某一个版本的测试中出现错误,可以清楚的知道是这个版本中新加入的功能出现问题。
10.3 分析阶段
在分析阶段生成规格说明文档,这个文档说明软件是用来做什么的,并没有定义如何去做。根据实现语言的不同,分析阶段有两种分析方法。
10.3.1 面向过程分析
如果实现阶段使用过程式语言,分析阶段就使用面向过程分析(结构化分析或经典分析),这种方法下规格说明文档可以使用多种建模工具。
1. 数据流图
数据流图显示了系统中数据的流动。矩形表示数据源或数据目的、带圆角的矩形表示过程(数据上的动作)、末端开口的矩形表示数据存储的地方、箭头表示数据流。
下图显示了一个小旅馆的预订系统的简单版本。
2. 实体关系图
在数据库部分会说到。
3. 状态图
状态图显示系统中的实体的状态在响应事件下的变化。以电梯为例,请求的楼层如果与当前楼层相同,则忽略;如果请求楼层较高,则电梯上升;请求楼层较低,则电梯下降。电梯在停止状态时,它接受请求。
10.3.2 面向对象分析
当实现使用面向对象语言时,就使用面向对象分析,规格说明文档也可以使用多种建模工具。
1. 用例图
用例图给出了系统的用户视图,它显示了用户与系统间的交互。用例图中包括四种组件:系统(矩形表示,执行功能)、用例(动作)、动作者(用户)和关系。
2. 类图
分析的下一步就是创建类图,为了创建类图,我们需要考虑系统涉及的实体。在老式电梯中,有按钮类和电梯类两个实体。
3. 状态图
类图完成后就可以给每个类准备状态图,比如电梯有停止、上升和下降三种状态,按钮有开关两种状态。
10.4 设计阶段
设计阶段定义了系统如何完成分析阶段定义的需求,在设计阶段,系统的所有组成部分都被定义。
10.4.1 面向过程设计
在面向过程设计中,我们既要设计过程,还要设计数据,整个系统被分解成一组过程或模块。
1. 结构图
在面向过程设计中,说明模块间关系的常用工具是结构图。
2. 模块化
模块化可以将大项目分解成较小的部分,以便能够容易理解和处理。也就是说,模块化可以将大程序分解为互相连接的小程序。当系统被分解为模块时,主要关心两点:耦合和内聚。
耦合:指两个模块之间联系的紧密程度,越紧耦合的模块,独立性越差。在模块化设计中,我们希望耦合越松散越好,松散耦合的模块有几个优点:
松散耦合的模块更可能被重用(这个模块可以在其他程序或项目中使用);
松散耦合的模块不容易在相关模块中产生错误(自己的错误不会影响到其他模块);
当系统需要修改时,松散模块可以使我们只修改需要修改的模块。
内聚:模块中各元素之间联系的紧密程度。内聚程度越高,说明这个模块越高效。
在软件工程中,我们希望耦合最小化、内聚最大化(高内聚、低耦合)。
10.4.2 面向对象设计
在面向对象设计中,设计通过详细描述类的细节来继续。面向对象设计阶段列出类的属性(变量)和方法的细节。
10.5 实现阶段
在这个阶段,程序员为面向过程设计中的模块编写程序或程序单元,实现面向对象设计中的类。
10.5.1 语言的选择
面向过程开发中,通常使用C语言;面向对象开发中,通常使用C++或Java。
10.5.2 软件质量
软件质量是实现阶段的一个非常重要的问题。高质量软件是能满足用户需求、符合组织操作标准和能高效运行在硬件上的软件。如果我们要评价一个软件的质量,就需要定义一些关于质量的指标。
软件质量因素:可操作性、可维护性、可迁移性。
可操作性:可操作性涉及系统的基本操作。度量有:准确性(能否准确完成功能)、高效性、可靠性(故障率)、安全性、及时性、适用性。
可维护性:与保持系统正常工作并及时更新有关,一个系统需要修改,不一定是它本身出了问题,有可能是外部因素的影响。度量:可变性(复杂性和结构,越复杂越不容易改变)、可修正性(恢复正常的平均时间)、适应性、可测试性。
可迁移性:是指把系统和数据从一个平台移动到另一个平台并重用代码的能力,如果编写通用性软件,这个因素就很重要了。度量:重用性(代码在不同程序或项目能否使用)、互用性(发送数据给其他系统的能力)、可移植性(把软件从一个硬件平台移动到另一个硬件平台的能力)。
10.6 测试阶段
测试阶段的目标就是发现软件中的错误并改正,好的测试策略能发现更多的错误。有两种测试:白盒测试与黑盒测试。
10.6.1 白盒测试(玻璃盒测试)
白盒测试是基于知道软件内部结构的。测试的目标是检查软件所有的部分是否全部被实现。白盒测试假定测试者知道软件的一切。使用白盒测试需要至少满足4个标准:
每个模块中的所有独立的路径至少被测试过一次;
所有的判断(选择)结构每个分支都被测试;
每个循环被测试;
所有的数据结构被测试。
下面介绍两种测试方法:基本路径测试和控制结构测试。
1. 基本路径测试
基本路径测试是由Tom McCabe提出的,这种方法创建一组测试用例,这些用例执行软件中的每条语句至少一次。基本路径测试是一种软件中每条语句至少被执行一次的方法。基本路径测试使用图论和圈复杂性找到没有走过的独立路径,从而保证每条语句至少被执行一次。
2. 控制结构测试
控制结构测试比基本路径测试更容易理解并且包含基本路径测试。
条件测试:应用于模块中的条件表达式,简单条件是关系表达式,而符合条件是简单条件和逻辑运算符的组合。条件测试用来检查是否所有的条件都被正确设置。
数据流测试:是基于通过模块的数据流的,这种测试选择测试用例,这些用例检查变量的值。
循环测试:使用测试用例检查循环的正确性。
10.6.2 黑盒测试
黑盒测试在不知道程序内部也不知道程序是怎样工作的情况下测试程序。黑盒测试按照软件应该完成的功能测试软件。
1. 穷举测试
用所有可能的输入值测试软件,但如果软件的输入域非常大,这种做法就不现实。
2. 随机测试
通过随机数生成器等方法,在输入域中随机选取子集进行测试。
3. 边界值测试
当输入是边界值时常常发生错误,如果模块在边界值出错,那代码就有问题。
10.7 文档
软件的正确使用和有效维护离不开文档。通用软件有三种独立文档:用户文档、系统文档和技术文档。文档是一个持续的过程,不论是软件发布之后出现什么问题等,都需要记录在文档,只有当软件过时后,编写文档才停止。
10.7.1 用户文档
告诉用户如何使用软件包。一个好的用户手册能够成为一个强大的营销工具,有利于软件销量。
10.7.2 系统文档
系统文档定义软件本身,有利于软件的开发和维护。
分析阶段,收集的信息应该仔细用文档记录,需求等内容也要记录清楚;
设计阶段,对系统建立的模型(UML图等)要记录下来,如果模型迭代了很多,那么最终版本要有完整的注释将它们记录在案;
实现阶段,代码的每个模块都要记录下来,另外,代码要使用注释等尽可能详细的形成自文档;
最后,开发人员必须仔细地形成测试阶段的文档。对最终产品使用的每种测试,包括测试的结果(好的或坏的)都要记录在文档。
10.7.3 技术文档
描述了软件的安装、服务以及一些环境的搭建等内容。服务文档描述了如果需要,系统应该如何维护和更新。