1. 软件工程概述

软件的概念和特点

软件(广义):计算机系统中与硬件相互依存的一部分

程序:计算机可以接受的一系列指令,运行时可以提供所要求的功能和性能。
数据:使得程序能够适当地操作信息的数据结构。
文档:描述程序的研制过程、方法和使用的图文资料。

  • 软件所具有的复杂性一致性可变性不可见性等特性,使得软件开发过程变得难以控制。
  • 软件的特点:
  • 软件是一种逻辑实体,不是具体的物理实体。
  • 软件产品的生产主要是研制。
  • 软件具有“复杂性”,其开发和运行常受到计算机系统的限制。
  • 软件成本昂贵,其开发方式目前尚未完全摆脱手工生产方式。
  • 软件不存在磨损和老化问题,但存在退化问题

软件危机

  • 软件危机:落后的软件生产方式无法满足迅速增长的计算机软件需求,从而导致软件开发与维护过程中出现一系列严重问题的现象。
  • 体现在:
  1. 软件产品无质量保证,可靠性差,甚至开发过程就夭折。
  2. 软件生产率太低
  3. 无法开发复杂程度高的软件
  4. 成本和进度估计不准
  5. 无统一科学的规范,软件不可维护
  6. 软件常不能满足用户的需求
  • 解决途径:
  1. 管理措施:项目管理、配置管理、过程管理、质量控制
  2. 技术措施:开发过程、开发技术与方法和开发工具

软件工程的发展

  • 史前时代(1965-1967)
  1. 软件开发没有方法可循
  2. 软件设计是在开发人员头脑中完成的隐藏过程
  3. 60世纪中期的软件危机
  • 瀑布过程模型(1968-1982)
  1. 1968年提出“软件工程”
  2. 结构化开发方法
  3. 瀑布式软件生命周期模型 成为典型
  • 质量标准体系
  1. 面向对象开发方法
  2. 软件过程改进运动
  3. CMM/ISO9000/SPICE等 质量标准体系
  • 20世纪90年代至今
  1. 敏捷开发方法流行
  2. 更紧密的团队协作
  3. 有效应对需求变化
  4. 快速交付高质量软件
  5. 迭代和增量开发过程

软件工程的定义

软件工程是①将系统性的、规范化的、可定量的方法应用于软件的开发、运行和维护,即工程化应用到软件上;②对①中所述方法的研究。

是一门指导计算机和维护的工程学科。

软件工程三要素

  1. 方法: 研究软件开发“如何做”的技术。涵盖了项目计划、需求分析、系统设计、程 序实现、测试与维护等一系列的开发活动如何来做。开发方法经历了从面向结构、面向对象、 面向组件到面向服务的发展工程。
  2. 过程: 为及时合理地开发出满足用户需求的计算机软件而进行一系列有组织的活动
  3. 工具: 为过程和方法提供自动的或半自动的支持。这些软件工具被集成起来,建立起一个支持软件开发的系统,称之为**计算机辅助软件工程 **(CASE ,Computer Aided Software Engineering)。

2.软件过程模型

软件过程模型定义任务之间关系和规程和方法,软件过程模型是对软件过程的抽象描述。

瀑布模型:

  • 瀑布模型:将软件生存周期各活动规定为依线性顺序联接的若干阶段的模型。一个系统的、顺序的软件开发方法。
  • 该模型说明整个软件开发过程是按图中的各个阶段进行的,每个阶段的任务完成之后,产生右边相应的文档
  • 优点:
  • 为项目提供了按阶段划分的检查点
  • 当前一阶段完成后,只需要去关注后续阶段
  • 可在迭代模型中应用瀑布模型
  • 缺点:
  • 各个阶段的划分完全固定,阶段之间产生了大量的文档,增加了工作量
  • 由于开发过程是线性的,开发过程中很难响应用户的变更要求
  • 早期错误要等到后期测试阶段才能发现
  • 实际项目开发中很少遵守瀑布模型提出的顺序
  • 客户要等到开发周期的晚期才能得到可执行的程序

原型模型

  • 为了描述、述软件开发过程中可能的回溯,尤其是维护阶段往往要经历上描述各个阶段,采用循环模型描述。
  • 原型是一个部分开发的产品,用于加强对系统的理解,有助于明确需求和选择可行的设计策略。

迭代式开发

  • 迭代式开发:将描述、开发和验证等不同活动交织在一起,在开发过程中建立一系列版本,将系统一部分一部分地逐步交付。

增量模型:

  • 增量模型:先开发主要功能模块,再开发次要功能模块,逐步完善直到构造全部功能。
  • 优点:
  • 分步开发,降低复杂性和难度。
  • 当配备的人员不能在设定的期限内完成产品时,它提供了一种先推出核心产品的途径。
  • 可先发布部分功能给客户,对客户起到镇静剂的作用。
  • 边开发边投入,可及早发现问题,减少投资风险。
  • 适用于需求不完整的软件开发,可以灵活应对用户的需求变化。
  • 缺点:
  • 加入构件不能破坏已构造好的系统部分,这需要软件具备开放式的体系结构。
  • 需求改变过大会导致软件过程的控制失去整体性。
  • 如果增量包之间存在相交的情况且未很好处理,则必须做全盘系统分析。

螺旋模型

  • 一种风险驱动型的过程模型,一种演进式软件过程模型。它结合了原型的**迭代性质**和瀑布模型的**系统性和可控性**特点。具有快速开发越来越完善软件版本的潜力。
    
  • 螺旋模型将开发过程分为几个螺旋周期,每个螺旋周期可分为4个工作步骤:
  1. 确定目标、方案和限制条件;
  2. 评估方案、标识风险和解决风险;
  3. 开发确认产品;
  4. 计划下一周期工作。
  • 优点:
  • 开发大型系统和软件的理想方法。
  • 客户始终参与每个阶段的开发,每个阶段的成果需客户确认,避免错误的积累。
  • 增加风险分析,一旦风险成立,原方案应终止、修订,力求风险可控。
  • 缺点:很难说服客服演进的方法是可控的,依赖于低昂的风险评估专家来保证成功。

敏捷开发

  • 敏捷宣言
  • 个体和交互>过程和工具
  • 可以工作的软件>面面俱到的文档
  • 客户合作>合同谈判
  • 响应变化>遵循计划
  • 极限编程(XP):偏重编程实践
  • Scrum开发方法:偏重项目管理
  • 特点:计划性、灵活性

3. 软件需求分析

  • 目的:弄清用户对系统的细节要求,完整、准确、清晰、具体地回答目标系统 “做什么”。准确地理解用户 出的软件功能、性能及其环境的要求。
  • 必要性:用户与开发者的知识领域不同,产生歧义;软件开发失败 50%是需求 不合理,早期错误易放大。

需求工程活动

  • 需求获取

  • 需求获取技术:面谈、问卷调查、群体诱导技术、参与调查法、文档分析、原型化方法、需求讨论会..

  • 需求分析与建模

  • 需求规约:软件需求规约(文档)

  • 需求管理

  • 需求验证

  • 业务需求:反映企业/组织对软件系统的高层次目标要求,即软件系统的建设目标。

  • 用户需求:用来描述用户使用产品必须要完成的任务,使用业务领域的术语描述,采用开发者与用户都能理解的语言和图形表达。

  • 功能需求:开发人员必须实现的软件功能,结果在需求规格说明书中。

  • (也包括非功能需求):如界面的交互性、数据的安全性、 数据的事务性、用户的并发性、响应的快速性、操作的实时性、错误与异常的恢复性、软件 的容错性等等

    需求分析方法

  • 功能分析方法:将系统看作若干功能模块的集合,每个功能又可以分解为若干子功能,子功能还可继续分解,分解的结果已经是系统的雏形。

  • 结构化分析方法(SA法):是一种以数据、数据的封闭性为基础,从问题空间到某种表示的映射方法,由数据流图(DFD图)表示。

  • 信息建模法:是从数据的角度对现实世界建立模型的,基本工具是ER图。

  • 面向对象的分析方法(OOA)的关键是识别问题域内的对象,分析它们之间的关系,并建立起三类模型。

  • 面向对象的特性有抽象性、封装性、继承性、多态性和消息机制等五大特性。

结构化分析方法(SA法)

  • 基本思想
  • 分解:对于一个复杂的系统,为了将复杂性降低到可以掌握的程度,可以把大问题分解成若干小问题,然后分别解决。
  • 抽象:分解可以分层进行,即先考虑问题最本质的属性,暂把细节略去,以后再逐层添加细节,直至涉及到最详细的内容,
  • DFD图
  • 箭头表示数据流(表示数据流的名称和数据的流向)
  • 圆或椭圆表示加工
  • 双杠或者单杠表示数据存储
  • 矩形框表示数据的源点或终点,即外部实体(系统外与系统交互的人或实体)。
  • 分层DFD图
  • 先确定系统范围,画出顶层的DFD图。顶层图说明了系统的边界,
    即系统的输入和输出数据流,顶层图只有一张。
  • 逐层分解顶层DFD图,获得若干中间层DFD图。中间层的数据流图描述了某个加工的分解,而它的组成部分又要进一步分解。
  • 画出底层的DFD图。底层图由一些不能再分解的加工组成。
  • **数据字典(DD)**:描述数据流图中出现的所有数据和加工。
  • 分层数据流图只是表达了系统的“分解”,为了完整地描述这个系统,还需借助“数据词典”和“小说明”对图中的每个数据和加工给出解释。
  • 数据流条目:给出了DFD中数据流的定义,通常对数据流的简单描述为列出该数据流的各组成数据项。
  • 文件条目:给出某个文件的定义,文件的定义通常是列出文件记录的组成数据流,还可指出文件的组织方式。
  • 数据项条目:给出某个数据单项的定义,通常是该数据项的值类型、允许值等。
  • 加工条目:说明DFD中基本加工的处理逻辑。由于上层的加工是由下层的基本加工分解而来,只要有了基本加工的说明,就可理解其他加工。

4.软件体系结构设计

软件设计

  • 软件设计阶段要解决“如何做”的问题
  • 软件所有的开发工作都将根据设计的方案进行
  • 软件的总体结构设计决定了系统的质量
  • 软件设计包括软件结构设计、数据设计、接口设计和过程设计。
  • 结构设计:定义软件系统各主要部件之间的关系
  • 数据设计:将分析时创建的模型转化为数据结构的定义
  • 接口设计:描述软件内部、软件和操作系统之间及软件与人之间如何通信
  • 过程设计:把系统结构部件换成软件的过程描述。

软件设计的任务

  • 将分析阶段获得的需求说明转换为计算机中可实现的系统。
  • 完成系统的结构设计,包括:数据结构和程序结构,最后得到软件设计说明书。
  • 对模块内部的过程进行设计。

软件设计阶段任务

  1. 划分模块,确定软件结构:开发方法不同,确定软件结构的方法也不同。例如SD法,是从分层的DFD图导出初始的结构图,再对初始的结构图进行改进,获得最终的结构图。一般包括确定系统的软件结构,分解模块,确定系统的模块层次关系。
  2. 确定系统的数据结构:数据结构的建立对于信息系统而言尤为重要。要确定数据的类型,组织、存取方式,相关程度及处理方式等。
  3. 设计用户界面:作为人机接口的用户界面起着越来越重要的作用,它直接影响到软件的寿命。

软件的层次结构

  • 模块:是程序对象有名字的集合(过程、函数、子程序、宏..)。
  • 深度:表示软件结构从顶层模块到最底层模块的层数。
  • 宽度:表示控制的总分布。
  • 扇出度:一个模块直接控制下属的模块个数。
  • 扇入度:一个模块的直接上属模块个数。
  • 好的软件结构的形态准则:顶部宽度小,中部宽度大,底部宽度次之;在结构顶部有较高的扇出数,在底部有较高的扇入数。

软件结构图(SC图)

  •  结构图(Structure Chart,简称SC图)是精确表达软件结构的图形表示方法,它以特定的符号表示模块、模块间的调用关系和模块间信息的传递。
  • 模块(Module):用矩形框表示,框中写有模块的名字,说明模块的功能。
  • 调用:从一个模块指向另一个模块的箭头表示前一模块对后一模块的调用,一般是上层调用下层。
  • 数据:调用箭头边上的小箭头表示调用时从一个模块传送给另一模块的数据。通常在短箭头附近应注有信息的名字。

模块独立性

  • 模块独立性的度量标准:
  • 耦合性——用于描述模块之间联系的紧密程度。
  • 内聚性——用于描述模块内部联系的紧密程度。
  • 模块独立性比较强的模块,具有:高内聚性、低耦合度
  • 内聚性低 → :偶然型 → 逻辑型 → 瞬时型 → 通信型 → 顺序型 → 功能性
  • 耦合性高 → :内容耦合 → 公共耦合 → 控制耦合 → 复合耦合 → 数据耦合

软件设计(SD)

  • 软件设计定义:软件设计是把软件需求(定义阶段)转换为软件的具体设计方案,即划分模块结构的过程,是软件开发阶段最重要的步骤。
  • 软件设计划分:按工程管理角度划分为:概要(总体)设计和详细设计。
    概要(总体)设计完成高层次结构设计;详细设计进行低层次过程设计,并穿插数据设计和接口设计。
  1. 概要/总体设计:分解模块,确定系统模块的层次结构。
  • 任务:① 划分模块 ② 确定模块功能 ③ 确定模块间调用关系 ④ 确定模块间界面
  • 步骤:从DFD图导出初始的模块结构图,按照SD法设计总则,改进模块结构图。
  • 得出:软件/模块结构图及其模块功能说明。
  1. 详细设计:对模块图中每个模块的过程进行描述,把功能描述转变为精确的、结构化的过程描述。
  • 常用描述方式:伪代码,流程图,N-S图,PAD图…

软件设计过程,概要设计是关键,根据需求确定软件和数据的总体框架,详细设计是进一步精化成软件的算法和数据结构。

软件结构化方法的优化准则

  • 抽象:抽取事物最基本的特性和行为,忽略非基本的细节。采用分层次抽象的办法可以控制软件开发过程的复杂性,有利于软件的可理解性和开发过程的管理。
  • 信息隐藏:采用封装技术,将程序模块的实现细节(过程或数据)隐藏起来,不被不需要这些信息的其它模块访问。
  • 模块化:模块是程序中逻辑上相对独立的单元;模块的大小要适中;高内聚、低耦合。
  • 使开发工作更易于规划
  • 可以定义和交付软件增量
  • 容易实施变更
  • 更有效地开展测试和调试
  • 一致性:整个软件系统(包括文档和程序)的各个模块均应使用一致的概念、符号和术语;程序内部接口应保持一致;

5. 软件测试

  • 软件测试是为了发现错误而运行程序的过程
  • 软件测试的目的是发现程序中的错误,是为了证明程序有错, 而不是证明程序无错
  • 测试对象不仅是程序,还应该包括开发过程中产生的所有产品,包括文档,其目的是为了尽早地、尽可能多的发现并排除软件中潜在的错误。

    软件测试分类

  • 程序执行角度
  • 静态测试:通过人工分析或程序正确性证明的方式来确认程序正确性。
  • 动态测试:通过动态分析程序测试等方式检查程序执行状态,以确认是否有问题。
  • 测试技术角度:
  • 黑盒测试:将测试对象看做一个黑盒子,完全不考虑程序内部的逻辑结构和内部特性,只依据程序的需求规格说明书,检查程序的功能是否符合它的功能说明。
  • 白盒测试:把测试对象看做一个透明的盒子,允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序所有逻辑路径进行测试。
  • 测试对象角度:
  • 单元测试:对软件基本组成单元进行的测试,其测试对象是软件设计的最小单位(模块或者类)。
  • 集成测试:在单元测试的基础上,将所有模块按照总体设计的要求组装成为子系统系统进行的测试。测试对象是模块间的接口,其主要目的是找出在模块接口(包括系统体系结构)设计上的问题。
  • 功能测试:在已知产品所应具有的功能基础上,从用户角度来进行功能验证,以确认每个功能是否都能正常使用。
  • 性能测试:在实际或模拟实际的运行环境下,针对非功能特性所进行的测试。
  • 验收测试:软件产品完成了系统测试之后、产品发布之前进行的软件测试活动,其目的是验证软件的功能和性能是否能够满足用户所期望的要求。
  • 人工干预角度:
  • 手动测试:手工地输入测试数据并记录测试结果
  • 自动化测试:利用开发的软件测试工具或者脚本

静态测试

  • 互审:程序员相互检查对方的代码
  • 走查:一个小组集体来走查程序或文档(分析方法:调用图、数据流分析图)
  • 会议:召开一个正式的会议,并有相应的记录、纪要、相应结果的文档。

动态测试

白盒测试
  • 逻辑覆盖法:
  • 语句覆盖:只覆盖可执行语句至少执行一次。
  • 判定覆盖:又叫分支覆盖,对每个判定式取真、假各一次,使每个判定的每个分支都至少执行一次,同时满足语句覆盖。
  • 条件覆盖:把程序中每个判断的每个条件为真和假各取值一次。 条件覆盖深入到判定中的每个条件,但不一定满足判定覆盖的要求。
  • 判定/条件覆盖:同时满足判定、条件两种覆盖标准的取值。就是使得判定中每个条件的所有可能取值至少执行一次,同时每个判定本身所有取值至少执行一次。
  • 条件组合覆盖:按每个判断的所有条件取值进行组合。这是 5 种覆盖中最强的覆盖。它不但可覆盖所有条件,还可覆盖所有判断的可取分支
  • 基本路径覆盖法
  • 导出程序流程图的拓扑结构-流图(控制流程图)
  • 计算流图 G 的环路复杂性 V(G)
  • 确定只包含独立路径的基本路径集,设计测试用例
黑盒测试
  • 等价类划分法:
  • 对测试数据进行区间划分,从这些区间中选取典型值作为用例代表,认为测试等价类中的一个代表值的结果就等于对该类其它值的测试。
  • 如果某个等价类的一个输入数据(代表值)测试中查出了错误,表示该等价类的其它值也有错误。
  • 选择测试用例:① 为每个等价类编号 ② 使一个测试用例尽可能覆盖多个有效等价类
    注意:一个测试用例只能覆盖一个无效等价类。
  • 有效等价类:对于程序的规格说明,是合理的、有意义的输入数据构成的集合。
  • 无效等价类:对于程序的规格说明,是不合理的、没有意义的输入数据构成的集合。
  • 边界值分析法:对输入或输出的边界值进行测试的一种方法。因为在等价类的边界处,是最可能出现错误的。
  • 步骤:划分等价类,选择测试用例,测试等价类边界。
  • 边界选择原则:① 输入值范围的边界 ② 输入/输出值个数的边界 ③ 输出值域的边界 ④ 输入/输出有序集(如顺序文件、线性表)的边界
  • 错误推测法
  • 因果图法

单元测试

  • 驱动模:模拟主程序或者调用模块的功能,用于向被测模块传递数据,接收、打印从被测模块返回的数据。一般只设计一个驱动模块
  • 桩模块:又称为假模块,用于模拟那些由被测模块所调用的下属模块的功能。可以设计一个或者多个桩模块,才能更好地对下属模块进行模拟。
  • 测试方法以白盒法为主,驱动模块和桩模块都是额外开销。

集成/组装测试

  • 一次性集成方式:分别测试每个单元,再一次性将所有单元组装在一起进行测试。
  • 渐增式集成方式:先对某几个单元进行测试,然后将这些单元逐步组装成较大的系统,在组装过程中边连接边测试。(自顶而下增值,自底而上增值,混合增值)
    自顶而下增值,自底而上增值可以选择深度优先或者宽度优先增值
  • 两种方式都需要设计驱动模块或桩模块,对每一个新组装的子系统进行测试主要采用黑盒法,对发现问题较多的子系统或模块应该用白盒法作回归测试。
  • 集成过程原则:尽早测试关键模块 → 提高测试效率,尽早测试包含I/O的模块 → 为以后测试提供方便

系统/性能测试

  • 恢复测试:让软件强制地发生故障,然后来验证是否能恢复到正常工作。
  • 安全测试:验证保护机制是否能够正常工作。
  • 压力(强度)测试:在一种需要反常数量、频率或资源的方式下执行系统。
  • 性能测试:测试软件在集成系统中的运行性能。

调试方法

  • 测试的目的:显示存在错误
  • 调试的目标:发现错误或导致程序失效的错误原因,并修改程序以修正错误。调试是测试之后的活动。
  • 三种调试方法
  • 蛮力法(强行排错法)
  • 返回法、回溯法
  • 原因排除法

    软件维护

  • 软件维护:在软件运行/维护阶段对软件产品进行
    的修改是所谓的维护。
  • 软件维护类型
  • 改正性维护:修改软件缺陷或者不足
  • 适应性维护:修改软件使其适应不同操作环境,主要包括硬件变化、操作系统变化或者其他支持软件变化。
  • 完善性维护:增加或修改系统功能,使其适应业务变化。
  • 预防性维护:采用先进的软件工程方法对需要维护的软件或软件中的某一部分(重新)进行设计、编制和测试。是为了提高软件的可维护性、可靠性等,为以后进一步改进软件打下良好基础。
  • 维护的副作用
  • 代码副作用
  • 数据副作用
  • 文档副作用。
  • 维护工作面临的困难:周期长、难度大、费用高。维护费用高

Point

  • 三层C/S结构包含表示层、功能层和数据层。
  • 软件由程序、数据和文档构成的。
  • 从工程管理方面来分类,软件设计一般分为概要设计和详细设计,它们之间的关系是全局和局部。
  • 面向对象程序设计原则包括开闭原则、里氏替换原则、单一职责原则和依赖倒转原则。
  • 开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。抽象化是开闭原则的关键
  • 单一职责原则:一个类只负责一个功能领域中的相应职责。或者说,一个类,应该只有一个引起它变化的原因。
  • 里氏替换原则:所有引用基类(父类)的地方必须能透明的使用其子类的对象。(实现开闭原则的重要方式之一)
  • 依赖倒转原则:抽象不应该依赖于细节,细节应当依赖于抽象。
  • 合成复用原则:尽量使用对象组合,而不是继承来达到复用的目的。
  • 在软件结构化设计方法中,指导模块划分的最重要原则是高内聚、低耦合。
  • 需求分析最终结果是产生需求规格说明书。
  • 模块独立性中,使得每个模块只完成一个相对独立的特定子功能,并且与其它模块的关系很简单。
  • 产品需求又可以细分为功能性需求和非功能性需求。
  • 需求内容来源于干系人、组织规章制度、业务过程和现有系统。
  • 需求工程过程中,最重要的工程活动包括需求获取、需求分析与建模、需求验证和需求管理。
  • 软件设计原则是系统分解和模块设计的基本标准,应用这些原则可以使代码更加灵活、易于维护和扩展。一些通用的原则包括抽象、封装、模块化、层次化和复用。
  • 在测试过程中,需要考察模块间的接口和*各模块之间联系***的测试属于集成测试。
  • 软件测试用例主要由输入用例和预期输出结果两部分组成