Cia

唯见月寒日暖,来煎人寿

软件工程概述

本章内容:

  1. 软件危机出现、爆发的原因和软件危机的表现
  2. 软件工程的概念、软件生存期、软件工程方法和工具
  3. 七种典型的软件生存期

软件危机和软件工程

20 世纪 60 年代以前,软件设计多针对特定应用和计算机,采用机器代码或汇编语言,规模小、无文档,以个人化生产方式为主。60 年代中期,大容量高速计算机出现,高级语言、操作系统和数据库管理系统相继诞生,软件规模扩大,可靠性问题凸显,私人化生产方式难以满足需求,软件发展滞后于硬件,60 年代末软件危机爆发。

软件危机爆发的原因

软件危机是计算机软件在开发和维护过程中面临的一系列严重问题,几乎所有软件都不同程度存在,如成本进度估计不准、产品质量不可靠、用户不满意、开发速度慢、不可维护、缺乏文档等。其爆发的主要原因如下:

  1. 软件作为计算机系统的逻辑部件,开发正确性和质量难以检验,维护阶段发现错误需修改原始设计,维护费用极高。
  2. 软件开发多由多人分工分阶段完成,人员沟通配合至关重要,但实践中未采用工程化方法,常使用错误技术,这是主要原因。
  3. 开发和管理人员重开发轻问题定义,导致软件产品无法满足用户需求。
  4. 软件管理技术落后,缺乏统一的质量管理规范,存在文档缺失、资金分配混乱、人员组织不合理、进度无序等问题。
  5. 对软件开发与维护关系存在错误认知,忽视维护的复杂性,未将维护观念融入开发全阶段。

软件工程的概念

为解决软件危机,1968-1969 年北约会议提出软件工程概念。Fritz Bauer 定义软件工程为:为经济地获得可靠且能在实际机器上有效运行的软件,而建立和使用的完善工程原理。

著名软件工程专家 B.W. Boehm 于 1983 年提出软件工程七条基本原理:

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

软件工程是指导计算机软件开发和维护的工程学科。它采用工程的概念、原理、技术和方法,结合成熟的管理技术与当前最优技术,实现经济地开发高质量软件并有效维护的目标。

软件生存期

软件存在孕育、诞生、成长、成熟和衰亡的完整过程,即软件生存期。软件工程的生存期方法学从时间维度分解软件开发与维护的复杂问题,将其划分为若干阶段,每个阶段有独立任务,可显著提高开发效率和成功率。

软件生命周期分为软件定义软件开发软件维护三个时期,细分共八个阶段:

  1. 软件定义时期
    1. 问题定义:明确要解决的核心问题。
    2. 可行性研究:判断已定义问题是否有可行的解决办法。
    3. 需求分析:确定目标系统必须具备的功能。
  2. 软件开发时期
    1. 总体设计:概括性地确定解决问题的方案。
    2. 详细设计:明确系统具体的实现方式。
    3. 编码和单元测试:编写易理解、易维护的正确程序模块。
    4. 综合测试:通过集成测试、验收测试等使软件达到预定要求。
  3. 软件维护时期
    1. 软件维护:通过改正性、适应性、完善性、预防性四类维护活动,使系统持续满足用户需求。

软件工程方法

软件工程方法学是软件生命周期中一整套技术方法的集合,通过划分阶段、明确任务逐步完成开发维护工作。目前广泛应用的是结构化方法和面向对象方法,方法学包含方法工具过程三要素,分别提供技术指导、支撑环境和任务框架。

结构化方法

结构化方法是传统软件开发方法,在面向对象方法出现前应用广泛。其基本思想是秉持用户至上原则,运用系统工程思想和工程化方法,结构化、模块化、自顶向下地分析设计系统。开发过程划分为系统规划、分析、设计、实施等独立阶段,前三个阶段自顶向下统筹全局,实施阶段自底向上逐步落地,先完成底层模块编程,再拼接调试构建整体系统。

结构化方法的主要特点:

  1. 自顶向下整体分析设计与自底向上逐步实施相结合。
  2. 始终坚持用户至上原则。
  3. 深入开展调查研究工作。
  4. 严格划分各工作阶段。
  5. 充分预判可能的变化。
  6. 开发过程遵循工程化标准。

面向对象方法

面向对象方法是将面向对象思想应用于软件开发的系统方法,涵盖面向对象的分析(OOA)、设计(OOD)和实现(OOI)三个环节。统一建模语言(UML)是目前最常用的面向对象建模语言,整合了多种 OOA 和 OOD 方法的优势。

面向对象方法的主要优点:

  1. 契合人类习惯的思维方式,易理解、易维护、易测试调试。
  2. 系统稳定性好,局部修改不会影响整体,且修改操作简便。
  3. 可重用性强,将大问题分解为独立小问题,降低开发难度,便于管理,节省成本,适合大型软件开发。

结构化方法在编程上思路清晰、条理严谨,便于阅读理解;面向对象方法更注重用户体验,操作简单、界面友好,各有优势。

软件工程工具

软件工程工具是以计算机为基础的软件工具,又称计算机辅助软件工程(CASE)工具,可支持软件生存周期某一阶段的工作。其目的是使软件工程系统化,自动化执行重复动作,减少工程师重复性劳动,使其专注于创造性工作。

软件工具的分类

  1. 支持软件开发过程的工具:需求分析工具、设计工具、编码工具、排错工具、测试工具等。
  2. 支持软件维护过程的工具:版本控制工具、文档分析工具等。
  3. 支持软件管理过程和支持过程的工具:项目管理工具、配置管理工具等。

软件工具的介绍

  1. Rational Rose:由 Rational 开发后被 IBM 收购,是基于 UML 的可视化建模工具。
  2. IBM Rational Software Architect(RSA):IBM 推出的完整集成开发环境,支持 UML 建模、模型驱动开发等活动。
  3. PowerDesigner:Sybase 开发的 CASE 工具集,可便捷完成管理信息系统分析设计,覆盖数据库模型设计全过程。
  4. Microsoft Office Visio:微软开发的软件,便于 IT 和商务人员可视化处理、分析和交流复杂信息、系统及流程,支持多种图形绘制和部分 UML 建模。

软件生存期模型

软件生存期模型用于描述软件开发过程中各类活动的执行方式,明确各阶段的次序、准则、规定和限制,有助于协调活动、促进沟通、实现活动重用和高效管理。常见的软件生存期模型有七种。

瀑布模型

20 世纪 80 年代前,瀑布模型是唯一广泛采用的软件生存期模型。其核心思想是按工序化简问题,分离功能实现与设计,便于分工协作,将软件生命周期划分为六个自上而下、衔接固定的基本活动,如同瀑布流水逐级下落。

  1. 特点:阶段间具有顺序性和依赖性;推迟软件物理实现,编码前聚焦逻辑模型构建;重视质量保证,各阶段需完成规定文档并评审。
  2. 实际形态:带“反馈环”的瀑布模型,可应对开发中的问题反馈。
  3. 优点:强迫采用规范化方法;严格要求阶段文档提交;所有产品均需经过验证。
  4. 缺点:依赖书面规格说明,可能导致产品不符合用户实际需求;仅适用于项目初期需求明确的情况。

增量模型

增量模型中,系统开发通过一系列版本(增量)逐步推进,每个版本在原有基础上新增部分功能。首个增量通常是核心产品,实现基本需求,后续逐步补充特征。

  1. 优点:短期内可向用户交付可用产品;用户有充足时间适应新产品;项目失败风险较低;优先交付高优先级服务。

快速原型模型

开发真实系统前,先构建可运行的原型程序,实现最终产品功能的子集,基于原型逐步完成全系统开发。该模型解决了传统瀑布模型中需求难预先准确定义且易变的问题。

image-20251203082942441
  1. 开发步骤:第一步建造原型,通过用户交互评价细化需求,明确用户真实需求;第二步基于原型开发用户满意的软件产品。
  2. 优点:贴合用户真实需求;原型验证后产生的规格说明文档准确;开发基本按线性顺序进行;后续阶段返工少;开发人员经验积累充分,设计编码错误少;开发速度快,节约成本。

喷泉模型

喷泉模型以用户需求为动力、以对象为驱动,主要用于面向对象软件开发过程,“喷泉”体现了迭代和无间隙的特性。

  1. 优点:各阶段无明显界限,开发人员可同步工作,提升效率、节省时间,适配面向对象开发。
  2. 缺点:阶段重叠导致需大量开发人员,不利于项目管理;文档管理要求高,审核难度大。

螺旋模型

螺旋模型是演化型软件开发模型,融合了快速原型的迭代特征与瀑布模型的系统化、严格监控特性,最大特色是引入风险分析。

image-20251203083139110
  1. 应用限制:仅适用于大规模软件项目;要求开发人员具备丰富的风险评估知识和经验。
  2. 优点:设计灵活性高;分段构建大型系统,成本计算简便;客户全程参与开发,可与管理层有效交互;强调可选方案和约束条件,利于软件重用和质量提升。
  3. 缺点:风险驱动特性对开发人员的风险评估能力要求极高。

统一过程模型

该模型以用例驱动、以体系结构为核心,是迭代及增量的软件过程模型,广泛应用于面向对象项目。模型按二维结构组织,横轴按时间显示动态特征,纵轴按内容显示静态特征,包含业务建模、需求等六个核心工作流。

image-20251203083245946
  1. 核心阶段:分为初始、细化、构建、转换四个阶段,每个阶段结尾进行评估,结束于主要里程碑。
    1. 初始阶段:制定项目计划、评估风险、进行可行性分析,明确基本功能和总体结构。
    2. 细化阶段:控制风险,确保功能、结构和计划稳定,编制计划、设计体系结构,实现主要功能。
    3. 构建阶段:管控成本、进度和质量,开发集成剩余构件和功能,完成详细测试。
    4. 转换阶段:确保软件对最终用户可用。

敏捷开发

敏捷开发以用户需求进化为核心,采用迭代、循序渐进的方法,将大项目拆解为多个相互关联且可独立运行的小项目逐一完成。

  1. 原则:快速迭代;测试人员和开发者参与需求讨论;编写可测试的需求文档;加强沟通、精简文档;制作产品原型;及早规划测试。
  2. 适用条件:要求团队自主管理、自我组织且具备多功能型;适用于需求多变、团队与用户易沟通、开发风险高、规模小、可测试性好的项目。

结构化分析

本章介绍结构化软件工程方法的结构化分析阶段,涵盖问题定义、可行性研究、结构化分析(功能建模、数据建模、行为建模、数据字典)以及航空公司机票预订系统的项目案例实践。

问题定义

问题定义的核心是明确“要解决的问题是什么”,其结果直接影响后续开发的方向与成本,需严谨规范地开展。

问题定义的规范化要求

  1. 重视问题定义,不能将其视为简单的前期准备工作。
  2. 客观、全面地界定问题,避免避重就轻、遗漏关键要点。
  3. 明确问题定义的核心是界定问题本身,而非直接给出解决方案。
  4. 深入分析业务场景与需求本质,抓住问题的核心矛盾。
  5. 对问题定义结果进行严格评审,确保其准确可行。

问题定义实例(高校图书借阅系统)

  1. 项目名称:高校图书借阅系统
  2. 项目目标:提升高校图书借阅服务效率,降低借阅错误发生率,减少信息交流的繁琐流程及相关开销。
  3. 项目规模:开发成本不超过 20 万元。
  4. 初步设想:利用学校现有物力和人力资源进行系统开发。
  5. 可行性研究:建议开展为期 3 周的可行性研究,研究成本不超过 2 万元。
  6. 额外要求:系统需具备安全性、可靠性,保障读者与图书信息安全准确;采用开放结构,便于扩充与维护,拥有良好的人机交互界面。

可行性研究

可行性研究旨在判断针对已定义的问题是否存在可行的解决办法,主要从技术、经济、法律三个核心维度展开分析。

可行性研究的内容

  1. 技术可行性
    1. 采集影响系统性能、可靠性、可维护性的相关信息。
    2. 论证实现系统功能和性能所需的设备、技术、方法与过程。
    3. 分析项目开发在技术层面面临的风险及对开发成本的影响。
    4. 调研现有类似系统的功能、性能、技术选型及开发经验教训,为新项目提供参考。
  2. 经济可行性
    1. 估算成本,涵盖软硬件购置安装费、系统开发费、运行维护费、人员培训费等。
    2. 估算效益,包括系统为用户新增的收入、对其他产品或利润的影响,判断开发成本是否超出预期利润。
  3. 法律可行性
    重点考量合同、责任、侵权、规范等法律相关问题,规避项目后续可能面临的法律风险。

可行性研究实例(高校图书借阅系统)

  1. 技术可行性:系统属于普通信息管理系统,高校技术人员可组建团队开发;学校现有电脑软硬件可满足基础需求,仅需购置数据库服务器及图书、校园卡扫描设备,相关实现技术成熟;图书馆管理人员和读者无需复杂培训即可上手操作,技术层面完全可行。
  2. 经济可行性:系统开发以低成本、低投入为目标,短期内可完成基础功能开发,预期投入资金较少,从经济角度判断开发可行。
  3. 法律可行性:系统为高校自行开发、自行使用,开发过程不涉及与法律抵触的合同、责任等问题,法律层面无风险。

需求分析

需求分析阶段的核心任务并非解决问题,而是明确“目标系统必须做什么”,确定系统需具备的核心功能,主要分为获取需求、分析需求、定义需求和验证需求四个步骤。

需求获取

需求获取涉及客户、用户和开发方,需由专业系统分析师主导,遵循科学的任务、原则与流程开展。

  1. 需求获取的任务
    1. 发现并分析问题,梳理问题的因果关系。
    2. 通过多种方式与用户交流,运用调查研究方法收集需求信息。
    3. 从数据、过程和接口三个维度观察问题的不同侧面。
    4. 将获取的需求通过用例、数据流图、数据字典等形式文档化。
  2. 需求获取的原则
    1. 深入浅出原则:尽可能全面细致地收集需求,目标系统实现的是需求全集的子集。
    2. 以流程为主线原则:用业务流程串联各类需求,便于与用户沟通确认。
  3. 需求获取的过程
    1. 深入了解应用领域,构建高层业务模型。
    2. 定义项目范围和高层需求。
    3. 识别用户类型并确定用户代表。
    4. 通过用户交流、竞品分析、需求文档研读、问卷调查、现场观察等途径获取具体需求。
    5. 确定目标系统的业务工作流。
    6. 对需求进行整理与总结。

结构化需求分析

结构化分析方法是面向数据流的需求分析方法,核心框架包含功能建模(数据流图)、数据建模(实体 - 关系模型)、行为建模(状态转换图)以及作为核心的数据字典。

功能建模

功能建模采用抽象模型概念,按软件内部数据传递与变换关系自顶向下逐层分解,最终确定满足功能要求的可实现软件,核心工具是数据流图,用于描述数据在系统中的流动和处理过程。

  1. 数据流图的基本符号:包含外部实体、加工、数据流、数据存储等基本元素,多个数据流指向或出自一个加工时,相互之间通常存在特定关联关系。
  2. 画数据流图的步骤
    1. 绘制系统环境图(顶层数据流图/0 层数据流图):仅包含一个代表目标系统的加工,同时明确系统的输入输出数据流及对应的外部实体,确定系统名称和使用者。
    2. 绘制分层数据流图:对于复杂系统,为降低理解难度,采用自顶向下逐层分解的方式,将复杂系统拆分为多个层次的数据流图,清晰呈现系统结构关系。
  3. 分层数据流图的注意事项
    1. 层次编号规则:顶层为 0 层,子图编号由父图编号与子加工编号组成,如 1.1、1.2 等。
    2. 子父图平衡:子图的输入、输出数据流需与父图中对应加工的数据流一致,可忽略枝节性数据流(如错误提示数据流)。
    3. 数据存储规则:子图中未在父图出现的数据存储,若读写局限于子加工内部,不影响子父图平衡。
    4. 加工分解原则:每次分解的加工数量不超过七个,分解需自然合理,上层可多分解,下层分解节奏放缓,兼顾可读性与层数控制。
  4. 数据流图的改进
    1. 正确性检查:验证数据是否守恒、数据存储使用是否合理、子父图是否平衡、加工与数据流的命名及关系是否匹配。
    2. 可读性提升:简化加工间联系,保证分解均匀,为加工、数据流、数据存储赋予恰当名称(加工名建议为“动词 + 名词”形式)。
    3. 重新分解步骤:将需重分解的子图合并,按联系最少原则划分,重建父图与子图,重新命名编号。
  5. 功能建模实例
    1. 某医院患者监护系统:识别医生、病人、护士、时钟为外部实体,输入包括生理信号、患者安全范围等,输出为病情报告和警告信息,绘制完成顶层数据流图和一层数据流图(系统功能简单,无需进一步细分)。
    2. 高校图书借阅系统:外部实体为管理员和读者,输入包含图书读者信息维护、借还书信息等,输出涵盖借还书结果、超期信息等,依次完成顶层数据流图、一层数据流图及读者信息维护、图书信息维护、借书、续借、还书等多个二层数据流图的绘制。

数据建模

数据建模的核心是建立软件系统的数据模型,结构化分析中采用实体 - 联系(E - R)建模技术,通过 E - R 图可视化呈现数据对象、属性及对象间的关系。

  1. E - R 图核心元素
    1. 数据对象(实体):表示目标系统所需的复合信息,用矩形表示,如学生、课程、图书等。
    2. 属性:定义数据对象的特征,用椭圆或圆角矩形表示,通过无向边与对应实体连接,如学生的学号、姓名等。
    3. 联系:表示不同实体实例间的关联,用菱形表示,通过无向边与实体连接并标注联系类型;若联系有属性,需将属性与菱形连接。
  2. 实体间的联系类型
    1. 一对一(1:1)联系:如部门与经理、学校与校长。
    2. 一对多(1:m)联系:如班级与学生、学院与专业。
    3. 多对多(m:n)联系:如学生与课程、读者与图书。
  3. 数据建模实例(高校图书借阅系统):结合业务流程和功能建模结果,确定系统包含读者、读者类别、图书、单本图书、存放区域、管理员 6 个实体,梳理各实体间的关联关系,构建完整的 E - R 图。

行为建模

行为建模通过绘制状态转换图(状态图)描述系统行为,展现系统的状态及引发状态转换的事件。

  1. 状态图基本符号:实心圆表示初态,牛眼图形表示终态,圆角矩形表示中间状态(可包含状态名称、状态变量、活动表,后两者可省略);带箭头的连线表示状态转换,箭线上标注触发事件,未标注则在源状态内部活动完成后自动触发。
  2. 行为建模实例(高校图书借阅系统):围绕核心业务流程,绘制借书状态图和还书状态图,清晰呈现图书借还过程中系统的状态变化及触发条件。

数据字典

数据字典是定义和解释数据模型、功能模型、行为模型中数据对象及控制信息的词条集合,是粘合三种分析模型的核心,包含数据流图所有成分的定义说明。

  1. 数据字典常用符号:包含=(被定义为)、+(与)、[…](或)、{…}(重复)等,用于精准描述数据结构,具体符号含义可参考对应说明表。
  2. 核心词条描述
    1. 数据流词条:包含数据流名、编号、简述、组成、来源、去向、流通量、峰值等内容。
    2. 数据元素词条:描述数据元素的类型、取值范围及相关数据结构。
    3. 数据存储文件词条:涵盖文件名、编号、简述、组成、输入、输出、存取方式、存取频率等信息。
    4. 加工词条:包含加工名、编号、简述、输入、输出、加工逻辑(复杂逻辑可用判定表、判定树等描述)。
    5. 数据源点及数据汇点词条:描述名称、简述、相关数据流和交互数目。
  3. 加工逻辑描述工具
    1. 判定表:由基本条件项、条件项、基本动作项、动作项四部分组成,每一列代表一条规则,可通过合并相似规则简化表格。
    2. 判定树:判定表的变种,更直观易懂,可根据条件从属关系、并列关系等构造,清晰呈现条件组合与对应动作。
  4. 数据字典实例(高校图书借阅系统):给出数据源点“读者”、数据流“读者号”、数据存储“借阅信息”、加工“检查借书量上限”的详细词条描述,明确各数据元素的类型、取值范围及相关关联。

机票预订系统结构化分析项目实践

需求获取

机票预订系统的业务流程涵盖航空公司操作员和旅客两类核心用户。操作员负责航班信息维护、旅客及订单查询统计;旅客需注册后进行订票、取票、退票操作,接收相关通知,可查询修改个人信息及订单;系统时钟在行程当日发送行程通知;操作员可在期限内为已取票旅客办理退票,流程与旅客自行退票基本一致。

功能建模

为简化分析,省略操作员退票功能,按步骤完成数据流图绘制:

  1. 识别外部实体为航空公司操作员和旅客,输入数据包括航班信息维护、旅客注册订票等事务,输出数据涵盖各类通知、机票及查询结果。
  2. 依次绘制系统顶层数据流图、一层数据流图,以及订票、取票、退票等多个二层数据流图,完整呈现系统数据流动与加工过程。

数据建模

结合系统业务流程和功能建模结果,梳理出旅客、航班、航班票务、订单、机票 5 个核心实体。其中航班信息拆分为航班基本信息和航班票务信息,订单和机票信息由旅客预订行为产生,据此构建系统 E - R 图。

行为建模

围绕系统核心业务,绘制预订机票、取票、退票三个关键状态转换图,清晰展示旅客在不同业务场景下系统的状态变化及触发事件。

数据字典

选取核心词条进行详细描述,包括数据源点“旅客”、数据流“订票信息”、数据存储“订单信息”、加工“计算退款金额”,明确各数据元素的类型、取值范围、组成及关联关系。其中“计算退款金额”的加工逻辑采用判定表描述,按距离起飞天数和票价折扣划分不同情况,明确对应的退款比例(省略 5%手续费扣除项)。

结构化设计

本章首先介绍结构化总体设计阶段的体系结构设计、接口设计和数据设计,然后介绍结构化详细设计,同时给出与每一步骤相对应的“高校图书借阅系统”的设计实例。最后用“航空公司机票预订系统”项目案例完整地实现结构化设计的全过程。

结构化设计概述

结构化设计方法以数据流为中心、以结构化需求分析的结果作为设计依据,设计出满足用户需求的软件模型。总体设计阶段的软件设计模型包括软件体系结构、软件接口和数据模型,详细设计阶段的软件设计模型就是为软件设计过程模型。

结构化设计的任务

  1. 体系结构设计:又称模块设计,定义软件模块及其之间的关系,设计依据是结构化需求分析阶段的数据流图。
  2. 接口设计:包含外部接口和内部接口设计。外部接口描述用户界面、软件与硬件设备及其他软件系统的接口,依据是顶层数据流图;内部接口是软件各模块之间的接口,依据是结构化需求分析阶段的数据流图。
  3. 数据模型设计:根据结构化需求分析阶段建立的 E - R 图和数据字典,确定软件涉及的文件系统结构及数据库表结构。
  4. 过程设计:确定软件各个模块内的算法及内部数据结构,并选定过程表达形式描述算法,设计依据为数据流图、状态转换图及数据字典。

结构化设计的原则

  1. 模块化:将大型复杂问题分解为多个容易解决的小问题。
  2. 高内聚、低耦合:耦合是模块间相互关联的度量,低耦合要求模块间联系简单;内聚是模块内部元素结合紧密程度的度量,高内聚可提升后续开发效率。
  3. 抽象:按从高到低的抽象级别设计软件,利于软件理解和开发过程管理。
  4. 信息隐藏:采用封装技术隐藏程序模块的实现过程、数据等细节,未授权模块无法访问。
  5. 一致性:软件设计中各模块使用一致的术语和符号,模块间接口保持统一。

模块独立性

模块独立是指每个模块仅完成一个相对独立的特定子功能,且与其他模块关系简单、相互作用少。这类软件易于开发、测试和维护,错误传播范围小,便于功能扩充。模块独立性由内聚和耦合两个定性标准度量,需遵循高内聚、低耦合原则。

  1. 耦合性:从低到高、独立性从高到低依次为非直接耦合、数据耦合、标记耦合、控制耦合、外部耦合、公共耦合、内容耦合。
  2. 内聚性:和独立性从高到低依次为功能内聚、信息内聚、通信内聚、过程内聚、时间内聚、逻辑内聚、巧合内聚。

结构化设计的步骤

  1. 设计软件的体系结构图。
  2. 设计软件的接口。
  3. 设计软件的数据模型。
  4. 软件的详细设计。
  5. 编写结构化设计说明书。

体系结构设计

功能划分过程从需求分析确立的目标系统模型出发,分割整体问题,用一个或多个软件模块解决每个部分问题,进而实现整体问题的解决。

体系结构的启发式设计原则

  1. 提高模块独立性:遵循高内聚、低耦合原则,减少模块间的依赖和相互影响。
  2. 模块规模适中:通常规定模块语句行数为 50 - 100 行,最多不超过 500 行。过大模块需进一步分解,过小模块(仅被一个模块调用时)可合并到上级模块,且分解与合并不能降低模块独立性。
  3. 结构图的深度和宽度适中:中等规模程序的结构图深度约为 10,深度过大需检查模块是否过于简单;同一层次模块的最大个数为结构图宽度,需合理控制。
  4. 结构图中扇入和扇出适当:一个模块直接控制的下层模块个数为扇出数,好的系统平均扇出为 3 - 4,最多 5 - 9,扇出过大可增加中间控制模块,过小可分解或合并下级模块;一个模块的上级模块个数为扇入数,扇入数大说明模块共享度高,但非公用模块扇入数超过 8 需进一步分解。
  5. 模块的作用域应在控制域之内:模块控制域是自身及其所有从属模块的集合,作用域是受其判定影响的所有模块的集合,判定作用域需包含在所在模块的控制域内,否则会增加耦合性,降低可靠性和可维护性。
  6. 模块功能的完善化:完整模块应包含执行规定功能、出错处理和返回结束标志三部分,出错时需回送标志并说明原因,返回系列数据时需给出结束标志。
  7. 消除重复功能,改善软件结构:审查结构图,结构完全相似仅数据类型不同的模块可合并;局部相似的模块,可将相似部分独立为下层模块。

面向数据流的设计方法

该方法与结构化分析方法相衔接,能将数据流图转换为程序结构设计描述,且适配编码阶段的结构化程序设计方法。其流程为精化数据流图,区分事务型和变换型数据流,分别映射成对应结构,再用启发式设计规则精化软件结构,导出接口描述和全局数据结构,复查后进入详细设计。

  1. 变换型数据流
    • 特征:数据从外部进入子系统,经加工变换后输出,变换数据为核心。
    • 映射过程:先整合数据流图,划分输入数据流、输出数据流和变换中心;再进行一级分解,设计系统上层模块;最后进行二级分解,设计中、下层模块,输入模块需包含接收和转换数据的下属模块,输出模块同理,变换模块按数据流图功能分解。
  2. 事务型数据流
    • 特征:接收事务后,由事务处理中心根据事务性质选择分派适当处理单元,各处理单元并列,最终输出结果。
    • 映射过程:整合数据流图,划分边界,明确输入及输出;建立上层模块,事务调度模块控制下层并列事务模块;分解细化得到下层模块,先设计事务模块,再设计操作模块,最后设计细节模块,相似操作模块可设为公用模块。
  3. 混合型数据流:实际系统多为事务型和变换型的混合形式。设计时以变换型设计为主,先设计顶层主模块和第一层模块,再根据各部分结构特点,用事务分析或变换分析设计下层模块,最后依据启发式设计原则精化调整结构图。
  4. 实例(某医院患者监护系统):该系统包含事务型和变换型数据流,采用混合型结构设计。先划分软件系统的输入、变换中心和输出三部分,再设计体系结构图,涵盖设置患者安全范围、更新患者日志、分析病人生理信号、打印病情报告等事务模块。

事务型体系结构设计实例(高校图书借阅系统)

结合一层和二层数据流图设计上层结构图,划分输入、输出和中间部分边界,逐步完成体系结构设计:

  1. 设计上层结构图,包含图书借阅系统、输入数据、调度、输出数据模块。
  2. 分解“输入数据”模块,涵盖图书信息维护事务、读者信息维护事务等多个事务。
  3. 分解“输出数据”模块,包含读者和管理员的查询结果、借阅信息、超期信息等。
  4. 设计“调度”模块的下层事务模块,包括借书、续借、还书等模块。
  5. 设计“事务”层的下层“操作”模块及“细节”模块,如借书模块包含生成借阅信息、超期借阅检查等子模块。
  6. 按启发式设计原则整合、精化结构图,调整输入输出信息,省略部分查询结果输出及信息维护内容,得到最终体系结构图。

接口设计

接口设计主要包括软件与人之间的交互界面、软件与硬件或其他软件系统之间的接口以及软件内部模块之间的接口设计三个方面。

软件与人之间的交互界面设计

该设计又称用户界面设计,关乎软件的操作性、美观性和实用性,好的设计需兼顾个性品位与操作舒适度、简便性。

  1. 用户界面设计原则:简洁实用、清楚一致、贴合用户习惯、最小化用户记忆负担、安全可靠、灵活人性化。
  2. 用户界面设计类型:主要包括输入/输出设计、操作设计和用户手册。界面描述形式有问题描述语言、图形、菜单等,处理方式有批处理、交互式等。选择界面描述形式时需考虑使用难度、学习难度、操作速度等多方面因素。
  3. 人机交互细节设计准则:保持术语、步骤和活动的一致性;减少操作步骤和按键次数;避免“哑播放”,操作等待时给出进度反馈;提供 Undo 功能,支持操作恢复;提供联机帮助,提高用户学习效率。

软件与硬件或其他软件系统之间的接口设计

软件与硬件的接口实例有银行卡管理系统与 POS 机、手机与 SIM 卡等的接口;软件与其他软件系统的接口实例有网站与天气预报软件、购物网站与银行系统等的接口。这类接口根据数据流图中的边界确定,需软件工程师结合软、硬件及相关软件特性规范设计。

模块之间的接口设计

模块之间的接口属于软件内部接口,根据数据流图中各加工之间的联系确定,主要实现模块间的通信、数据传递或交换。因内部模块规范一致,设计重点在于提升模块间的通信效率,相对简单。

接口设计实例(高校图书借阅系统)

  1. 用户界面设计:满足高校教职工和学生使用需求,保证操作舒适、简单、自由。
  2. 软件与硬件接口设计:包含图书条形码扫描和读者校园卡扫描两个接口。
  3. 软件与其他软件系统接口设计:需设计与学校档案系统的接口,实现读者信息导入功能。
  4. 模块之间的接口设计:依据数据流图中各加工联系确定,保障模块间高效通信和数据交换。

数据设计

数据存储方式设计直接影响软件执行效率,需重点考虑数据存取方式和存储结构设计。目前数据存储多采用关系模型数据库,文件存储在多媒体、日志等方面具有优势,因此数据设计通常包括文件设计和数据库设计。

文件设计

非结构化多媒体数据、信息松散数据等适合采用文件存储,需根据软硬件条件和处理需求设计文件类型和组织方式。

  1. 顺序文件:物理记录顺序与逻辑记录顺序一致,适用于批处理,结构简单、存储利用率高,顺序查询和处理效率高,但不能直接存取特定记录。
  2. 随机文件:记录存储在特定地址,通过 HASH 函数计算地址,随机查找速度快,更新、新增和删除操作便捷,但选取最佳 HASH 函数较难,文件增大易出现冲突和空白区域。
  3. 索引文件:由索引表和主文件构成,索引表按键顺序排列。读取记录时先载入索引表,查找目标键后按地址检索数据,兼顾顺序文件和随机文件的部分优势。

数据库设计

数据库分为网状数据库、层次数据库、关系数据库等,目前多数设计者选择关系数据库,数据以二维表形式存储。在结构化设计中,可将 E - R 图便捷映射成关系数据库模型。

  1. 数据对象实体的映射:一个数据对象可映射为一个表,也可采用横切或竖切方法分解。竖切适用于记录少而属性多的表,横切适用于记录与时间相关的表。
  2. 联系的映射
    • 一对一联系:可双向或单向引入外键,也可单独构表或合并实体。
    • 一对多联系:在“多”端加入“一”端主键作为外键,或单独构表包含双方主键。
    • 多对多联系:必须单独构表,将双方主键作为外键。
  3. 设计数据视图:视图是从一个或多个表导出的虚表,能集中、简化和定制数据显示,保证数据逻辑独立性,提高数据安全性。

数据设计实例(高校图书借阅系统)

将高校图书借阅系统的 E - R 图映射为关系数据模型:

  1. 实体的映射:E - R 图中的 6 个实体映射为 6 个表,分别是图书、单本图书、读者、读者类别、存放区域、管理员,下划线属性为主键。
  2. 联系的映射:图书表添加存放区域作为外键;单本图书表添加书号作为外键;读者表添加读者类别号作为外键;管理员表添加管理的区域名作为外键;借阅表以读者号和图书条码号为组合主键,并分别作为外键关联读者表和单本图书表,借阅表可按时间横切为历史记录表和最近信息表。
  3. 设计视图:设计图书视图和读者视图,分别集中展示图书相关信息和读者详细信息,方便用户查询。

过程设计

过程设计阶段需确定各模块的实现算法,并用过程描述工具精确描述这些算法。

过程设计的任务和原则

  1. 任务
    • 算法设计:用图形、表格、语言等工具描述模块处理过程的详细算法。
    • 数据结构细节和数据操作设计:对数据的类型、关系、安全性等进行详细设计,确定数据操作方法。
    • 输入/输出格式设计:设计模块输入输出数据格式,以及实时交互系统的交互方式和内容。
    • 编写过程设计说明书。
  2. 原则:采用结构化程序设计方法,使用有限控制结构;复杂程序可采用修正的结构化程序设计;控制结构仅有一个入口和一个出口;严格控制 GOTO 语句;采用自顶向下、逐步细化原则。

过程设计的工具

  1. 图形工具
    • 程序流程图:又称程序框图,有标准符号,包含顺序、选择、WHILE 型循环、UNTIL 型循环、多分支选择五种基本控制结构,能直观展示程序逻辑。
    • N - S 图:又称盒图,符合结构化程序设计原则,每个矩形框定义特定控制结构的作用域,不能随意转移控制,结构清晰,易确定变量作用域。
    • PAD 图:由程序流程图演化而来,不能随意转移控制,结构清晰,易读易记,可自动转换为源程序,还能描绘数据结构。
    • 举例:以判断年份是否为闰年为例,分别用程序流程图、N - S 图、PAD 图进行设计展示。
  2. 语言工具:伪代码(PDL),介于自然语言和形式化语言之间,结合结构化程序设计语言关键字和自然语言语法。优点是可作为注释插入源程序,易编辑,可自动生成代码;缺点是不如图形工具直观,复杂条件组合描述不如表格工具清晰。以判断闰年为例,给出了 PDL 语言的实现代码。
  3. 表格工具:通过表格列出操作及对应条件,描述输入、处理和输出信息,如判定表、判定树等。
  4. 设计实例(高校图书借阅系统):分别给出了借书时检查读者是否有超期未还图书、还书时检查图书是否超期未交罚款的流程设计图。

数据结构细节和数据操作的设计

  1. 具体设计内容:确定数据库大小和文件组;定义数据表字段的数据类型;设计数据库完整性,包括实体完整、参照完整等;设计数据安全性,如设置口令、授予权限等;设计视图、存储过程、函数和触发器,以及索引键。
  2. 设计实例(高校图书借阅系统)
    • 数据库:根据读者、图书、借阅数量及增长情况,确定数据文件和日志文件的数量、初始大小和增长方式。
    • 表:设计 7 个表,以图书表为例,明确字段含义、数据类型、键值等信息。
    • 视图:设计图书相关信息视图,明确字段数据源和索引情况。
    • 存储过程、函数和触发器:设计查询图书存放区域的存储过程,计算借书和续借超期天数的函数,以及借阅图书时的插入存储过程和修改图书状态的触发器。
    • 数据库安全性:明确程序员对各表、视图、存储过程等的操作权限;对读者表和管理员表中的敏感信息加密保存;制定隔天差异备份和 10 天完整备份的策略,保留近三次完整备份及对应差异备份。

机票预订系统的结构化设计项目实践

体系结构设计

该系统包含信息维护、订票、取票等事务处理,具有事务型特征。结合一层和二层数据流图,划分输入、输出和中间部分边界,设计上层结构图,逐步分解输入事务、调度等模块,最终形成包含订票事务、退票事务、取票事务等的完整体系结构图。

接口设计

  1. 用户界面设计:面向全球各行各业旅客,要求布局简洁、操作舒适简单、步骤清晰,兼具一定品味。
  2. 软件与硬件接口设计:取票模块需设计旅客身份证扫描接口。
  3. 模块之间的接口设计:根据数据流图中各加工之间的联系确定,设计相对简单。

数据设计

该系统仅需进行数据库设计,将 E - R 图映射为关系数据模型并设计视图。

  1. 实体的映射:E - R 图中的 5 个实体映射为 5 个表,分别是航班、旅客、航班票务、订单、机票。
  2. 联系的映射:航班票务表以航班号、日期和舱位为组合主键,航班号为外键关联航班表;订单表添加身份证号、航班号等作为外键,关联旅客表和航班票务表;机票表以订单号为主键,同时作为外键关联订单表。部分表可按时间横切为历史记录表和近期表。
  3. 设计视图:设计航班票务视图和机票详细信息视图,分别满足旅客订票查询和机票打印需求。

过程设计

  1. 算法设计:给出了退票时计算退款金额模块的流程图,根据当前日期距航班起飞天数和购票折扣,计算不同情况下的退款金额。
  2. 数据结构细节和数据操作的设计:涵盖数据库、5 个表、视图、存储过程和触发器的设计,以及数据库安全性设计,保障系统数据处理和存储的稳定安全。

结构化编码和测试

本章首先介绍程序设计语言的类型和特点以及程序设计的风格,然后着重讨论程序效率分析方法中 McCabe 度量法,接着详细介绍各种白盒测试技术和黑盒测试方法,并以高校图书借阅系统为例进行程序效率分析以及白盒测试和黑盒测试,最后介绍软件测试的步骤。

编码

程序设计语言

  1. 程序设计语言的分类和特点
    • 程序设计语言是用于书写计算机程序的语言,是一种实现性软件语言,经历了机器语言、汇编语言到高级语言的发展,语言愈发规范、简单,代码复用性提高,程序员可更专注于软件的效率、可靠性等方面。
    • 按级别分为低级语言和高级语言:
      • 低级语言:包括机器语言和汇编语言,优点是执行速度快,实现硬件接口时易于实现且效率高;缺点是代码编写难度大、可读性差,与具体机器相关,可移植性弱。
      • 高级语言:优点是一定程度上与具体机器无关,可移植性强,更接近人的思维,易于编程、阅读和修改;缺点是运行需先翻译成机器语言,运行效率相对较低,对硬件的可控性弱于低级语言。
  2. 程序设计语言的选择
    • 选择直接影响开发难度和软件质量,不能仅顺应流行趋势,需考虑系统应用领域、硬件设备、开发人员熟练程度及用户特殊要求。
  3. 程序设计语言选择实例(高校图书借阅系统)
    • 若设计移动 APP,需兼容多数手机系统,一般采用 Android 开发环境下的 JAVA 语言。
    • 若设计 Web 网站,可根据用户需求和程序员技能选择 C++、JAVA、Python 等语言。

程序设计风格

  1. 源程序文档化
    • 标识符命名:选择精练、意义明确的名字。
    • 注释使用:好的程序编码中,注释行占源程序的 1/3 到 1/2。
    • 程序视觉组织:利用空格、空行和缩进增加程序的层次性和清晰度。
  2. 数据说明标准化
    • 数据说明次序规范化:使数据属性易查找,利于测试、排错和维护。
    • 说明语句中变量安排有序化:一个说明语句中多个变量名按字母顺序排列。
    • 注释说明复杂数据结构:说明数据结构在程序实现时的固有特点。
  3. 语句结构简单化
    • 一行内只写一条语句。
    • 程序需直截了当地体现程序员用意。
    • 除非对效率有特殊要求,否则做到清晰第一,效率第二。
    • 尽量仅采用三种基本控制结构编写程序。
    • 避免采用过于复杂的条件测试。
    • 数据结构应有利于程序简化。
    • 模块化,确保每个模块的独立性。
    • 从数据出发构造程序。
    • 不修补不好的程序,重新编写。
  4. 输入/输出方法规范化
    • 输入数据需检验,检查输入项重要组合的合理性,允许自由格式输入和缺省值。
    • 输入步骤和操作尽可能简单,保持简单格式。
    • 输入一批数据时,使用输入结束标志。
    • 用提示符提示交互输入请求,指明可选项种类和取值范围。
    • 对输入/输出格式有严格要求时,保持一致性。
    • 给所有输出加注解。
  5. 结构化程序设计重要原则
    • 使用语言中的顺序、选择、重复等有限基本控制结构表示程序逻辑。
    • 选用的控制结构只准许有一个入口和一个出口。
    • 复杂结构通过基本控制结构组合嵌套实现。

程序效率分析

  1. 模块内程序复杂性度量方法
    • 代码行度量法:统计程序模块的源代码行数目,适用于控制结构较简单的模块。
    • McCabe 度量法:基于程序控制流的复杂性度量方法,又称环路复杂性度量。McCabe 认为程序复杂性很大程度取决于程序图复杂性,顺序结构最简单,循环和选择构成的环路越多越复杂。
  2. McCabe 度量法实施步骤
    • 绘制程序控制流图(流图):流图是“退化”的程序流程图,仅描绘控制流程,不表现数据操作及分支或循环条件。将程序流程图的处理符号退化为结点,流线变为有向弧。
    • 流图画法:
      • 用圆表示结点,一个圆代表一条或多条语句。
      • 程序流程图中顺序的处理框序列和菱形判断框,映射成流图中的一个结点。
      • 流图中的箭头称为边,代表控制流。
      • 流图中一条边必须终止于一个结点。
    • 计算环路复杂度:
      • 方法一:流图中的区域数等于环路复杂度。
      • 方法二:流图 G 的环路复杂度 V(G)=E-N+2(E 是边的条数,N 是结点数)。
      • 方法三:流图 G 的环路复杂度 V(G)=P+1(P 是流图中判定结点的数目)。
    • 特点:源程序错误数及排错时间与 McCabe 环复杂度量值有明显关系,在选择方案和估计排错费用上有效,但度量数大的程序不一定结构化不好。
  3. 程序效率分析实例(高校图书借阅系统)
    • 借书时检查读者是否有超期未还图书模块:环路复杂度为 3。
    • 还书时检查是否超期未交罚款模块:环路复杂度为 4。

软件测试概述

测试的目的和原则

  1. 测试目的
    • 软件测试是为了发现错误而执行程序的过程,目的是尽可能多地发现软件中的错误、排除错误,交付高质量软件,而非证明软件正确性。
    • Grenford J.Myers 关于软件测试目的的观点:
      • 测试是程序的执行过程,目的在于发现错误。
      • 一个好的测试用例能发现至今未发现的错误。
      • 一个成功的测试是发现了至今未发现的错误的测试。
  2. 测试原则
    • 尽早且不断地进行软件测试。
    • 测试用例由测试输入数据和对应的预期输出结果组成。
    • 由第三方人员从事测试工作。
    • 设计测试用例时,包括合理和不合理的输入条件。
    • 注意测试中的错误群集现象。
    • 严格执行测试计划,排除测试随意性。
    • 妥善保存测试计划、测试用例、出错统计和最终分析报告。

测试的方法和步骤

  1. 软件测试方法
    • 黑盒测试(功能测试):将测试对象视为黑盒子,不考虑程序内部逻辑结构和特性,仅依据需求规格说明书,检查程序功能是否符合说明。
    • 白盒测试(结构测试):将测试对象视为打开的盒子,利用程序内部逻辑结构及相关信息,设计测试用例,对所有逻辑路径进行测试,检查程序状态是否与预期一致。
  2. 软件测试步骤
    • 采用“自底向上”方法,从测试单个程序模块开始,逐步过渡到集成模块组、整个系统,最后由用户进行验收测试,分为单元测试、集成测试、系统测试和验收测试四个阶段:
      • 单元测试:对软件最小可测试单元检查验证,易发现编码和详细设计阶段错误。
      • 集成测试:将软件单元按概要设计规格说明组装,测试子系统或系统工作是否达标,着重测试模块间接口。
      • 系统测试:将经集成测试的软件作为计算机系统一部分,与其他系统元素结合,在实际环境下测试,发现潜在问题。
      • 验收测试:针对用户需求、业务流程进行正式测试,确定系统是否满足验收标准,由用户或授权机构决定是否接受。
    • 软件测试 V 模型:体现测试四个阶段与前期开发各阶段的对应关系,即验收测试对应用户需求,系统测试对应需求分析,集成测试对应概要设计,单元测试对应详细设计和编码。

黑盒测试

黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和功能测试,旨在发现功能错误或遗漏、输入输出正确性、数据结构或外部信息访问错误、程序初始化和终止错误等。黑盒穷举测试需测试所有输入数据排列组合,因情况无穷多难以实现。

等价类划分法

  1. 核心思想:将程序输入分为若干等价类,每类选取少数代表性数据作为测试用例,同类数据测试作用等价,以少量测试数据获得较好测试结果。
  2. 等价类类型
    • 有效等价类:符合程序规格说明,合理、有意义的输入数据集合,用于检验程序是否实现规定功能和性能。
    • 无效等价类:不符合程序规格说明,不合理、无意义的输入数据集合,用于检查程序功能性能实现是否不符合规格说明。
  3. 划分等价类原则
    • 输入数据规定取值范围:确定 1 个有效等价类和 2 个无效等价类(如成绩 0-100,有效等价类“0≤ 成绩 ≤100”,无效等价类“成绩<0”“成绩>100”)。
    • 规格说明规定数据值集合:确定 1 个有效等价类和 1 个无效等价类(如性别“男/女”,有效等价类“男、女”,无效等价类为其他字符)。
    • 规定条件数据:确定 1 个有效等价类和 1 个无效等价类(如“x≥0”,有效等价类“x≥0”,无效等价类“x<0”)。
    • 输入数据为一组值且程序分别处理:确立 n 个有效等价类和 1 个无效等价类。
    • 规定输入数据必须遵守规则:确立 1 个有效等价类(符合规则)和若干无效等价类(从不同角度违反规则)。
    • 已划分等价类中元素处理方式不同:进一步划分为更小等价类。
  4. 设计测试用例原则
    • 为每个等价类规定唯一编号。
    • 设计新测试用例,尽可能覆盖未覆盖的有效等价类,直至全部覆盖。
    • 设计新测试用例,仅覆盖 1 个未覆盖的无效等价类,直至全部覆盖。
  5. 实例:某软件注册用户名规定“必须是未注册过的 5 到 16 位英文字母或数字的字符串”,划分有效等价类(5-16 位英文字母字符串、5-16 位数字字符串、5-16 位英文字母和数字组合)和无效等价类(小于 5 位、大于 16 位、含非法字符、不输入内容、已注册过的用户名)。

边界值分析法

  1. 核心思想:大量错误发生在输入或输出范围边界,针对边界情况设计测试用例,可查出更多错误,是等价类划分法的补充。
  2. 边界定义:输入或输出等价类稍高于和稍低于边界值的特定情况。
  3. 选择测试用例原则
    • 输入条件规定值的范围:取刚达边界及刚超越边界的值作为测试输入数据;输出同理(检查输出边界可能不现实)。
    • 输入条件规定值的个数:用最大个数、最小个数、比最小个数少一、比最大个数多一的数作为测试数据;输出同理。
    • 输入域或输出域为有序集合:选取集合第一个和最后一个元素作为测试用例。
    • 程序使用内部数据结构:选择内部数据结构边界上的值作为测试用例。
    • 分析规格说明,找出其他可能的边界条件。
  4. 实例:结合等价类划分法,为“未注册过的 5 到 16 位英文字母或数字的字符串”用户名设计测试用例,覆盖各有效和无效等价类的边界值(如 5 位英文字母、16 位数字、4 位字符、17 位字符等)。

错误推测法

  1. 核心思想:基于经验和直觉,推测程序中可能存在的缺陷和错误,列举出程序中可能的错误和易出错特殊情况,针对性设计测试用例。
  2. 常见易出错情况:输入或输出数据为 0、字符为空、输入表格为空格或仅一行等;程序员易忽略但用户可能进行的合法或非法输入;单元测试中模块常见错误、以往产品测试发现的错误等。

因果图法

  1. 核心思想:等价类划分法和边界值分析法未考虑输入条件间的联系与组合,因果图法适合描述多种条件组合产生多个动作的情况,最终生成判定表,分析输入条件组合(因)与输出结果(果)的关系。
  2. 因果图符号
    • 关系符号:
      • 等于:if(C)then Ef。
      • 非:if(!C)then Ef。
      • 或:if(C1 || C2)then Ef。
      • 与:if(C1 && C2)then Ef。
    • 约束符号(输入状态依赖关系):
      • 排他约束:C1、C2、C3 中最多一个为 1。
      • 包含约束:C1、C2、C3 中至少一个为 1。
      • 唯一约束:C1 和 C2 中必须且仅有一个为 1。
      • 要求约束:C1 为 1 时,C2 必须为 1。
      • 强制约束:Ef1 为 1 时,Ef2 强制为 0。
  3. 生成测试用例步骤
    • 分析软件规格说明,确定原因(输入条件或其等价类)和结果(输出条件),并赋予标识符。
    • 分析语义,找出原因与结果、原因与原因间的对应关系,绘制因果图。
    • 标注因果图中因语法或环境限制不可能出现的组合情况(约束或限制条件)。
    • 将因果图转换为判定表。
    • 以判定表每一列为依据,设计测试用例。
  4. 特点:生成的测试用例包含输入数据取 TRUE 与 FALSE 的情况,数量最少,且随输入数据增加线性增加。
  5. 实例:处理单价 3 元 5 角盒装饮料的自动售货机软件,分析原因(投入 3 元 5 角硬币、投入 4 元硬币、按“可乐”“雪碧”“红茶”按钮)、中间状态(已投币、已按钮)和结果(退还 5 角硬币、送出对应饮料),绘制因果图并转换为判定表,设计测试用例。

黑盒测试实例(高校图书借阅系统)

  • 读者密码规定“必须是 6 到 12 位英文字母、数字和特殊符号(空格除外)的组合,至少包含两种”。
  • 有效等价类:6-12 位英文字母和数字组合、6-12 位英文字母和特殊符号组合、6-12 位数字和特殊符号组合、6-12 位英文字母、数字和特殊符号组合。
  • 无效等价类:6-12 位英文字母字符串、6-12 位数字字符串、6-12 位特殊符号字符串、6-12 位空格字符串、小于 6 位有效字符组合字符串、大于 12 位有效字符组合字符串、不输入任何字符。
  • 基于边界值分析法设计测试用例,覆盖各等价类(如 6 位字母数字组合、12 位字母特殊符号组合、5 位字符组合、13 位字符组合等)。

白盒测试

白盒测试需了解程序内部结构及运作方式,目的是保证程序关键路径被测试、衡量测试完整性、测试真分支和假分支、检查局部数据结构有效性、程序异常处理能力及代码是否遵循编码规范。

静态白盒测试

  1. 定义:不执行程序的测试技术,检查软件表示和描述是否一致,有无冲突或歧义,也称为人工测试或结构分析,能尽早发现程序 30%-70%的缺陷,为黑盒测试提供思路。
  2. 主要形式
    • 代码检查:程序员自行设计测试用例,仔细检查源代码和详细设计,记录错误和不足。
    • 桌前走查:测试人员阅读文档和源代码,人工输入测试数据,沿程序逻辑走查运行,跟踪进程发现错误。
    • 代码审查:审查人员阅读软件资料,根据错误类型清单填写检测表,提出相关问题。

动态白盒测试

  1. 定义:也称为结构测试,在受控环境下用特定测试用例运行程序,根据程序内部逻辑设计测试用例,对程序模块所有独立执行路径至少执行一次,对所有逻辑判定“真”“假”取值至少测试一次,在循环边界执行循环体,检查内部数据结构有效性等。
  2. 局限性:白盒穷举测试需对每条独立路径在每种输入数据下执行,因路径数量可能极大(如含 20 次循环的程序路径达 5²⁰ 条),实际不现实;即使路径少,也可能因程序本身错误、数据相关错误等导致测试不全面。
  3. 逻辑覆盖技术:考察测试数据对程序逻辑的覆盖程度,从弱到强依次为语句覆盖、判定覆盖、条件覆盖、判定-条件覆盖、条件组合覆盖、路径覆盖。
    • 语句覆盖:设计测试用例,使每一可执行语句至少执行一次;优点是易从源代码获测试用例,缺点是无法测试隐藏条件和隐式逻辑分支。
    • 判定覆盖(分支覆盖):设计测试用例,使程序中每个判断的取真和取假分支至少经历一次;优点是测试路径多于语句覆盖,缺点是忽略判断中多个逻辑条件的取值情况。
    • 条件覆盖:设计测试用例,使程序中每个判断的每个条件的可能取值至少执行一次;优点是增加对条件取值的测试,缺点是不能保证判定覆盖。
    • 判定-条件覆盖:设计测试用例,使判断中每个条件的所有可能取值至少执行一次,且每个判断本身的所有可能结果至少执行一次;优点是满足判定覆盖和条件覆盖,缺点是未考虑条件组合情况。
    • 条件组合覆盖:设计测试用例,使每个判断的所有可能条件取值组合至少执行一次;优点是满足判定覆盖、条件覆盖和判定-条件覆盖,缺点是测试用例数量线性增加。
    • 路径覆盖:设计测试用例,覆盖程序中所有可能路径;优点是可彻底测试程序,缺点是测试用例数量庞大,无法实现。
  4. 基本路径测试
    • 定义:在程序控制流图基础上,通过分析控制结构的环路复杂性,导出基本可执行路径集合,设计测试用例,保证每一个可执行语句至少执行一次。
    • 步骤:
      • 画出程序流程图的控制流图,标出结点。
      • 计算程序模块的环路复杂度。
      • 确定线性无关的基本路径集(独立路径条数等于环路复杂度,每条独立路径含以前未处理的语句或条件)。
      • 生成测试用例,确保基本路径集中每条路径执行。
    • 实例 1:含输入 x、y,计算 t 值的程序,画出控制流图,计算环路复杂度为 6,确定 6 条独立路径,设计测试用例覆盖路径。
    • 实例 2:统计输入字符中字母、空格、数字和其他字符个数的 C 语言程序,画出控制流图,计算环路复杂度为 9,确定 9 条独立路径,设计测试用例覆盖路径。

白盒测试实例(高校图书借阅系统)

  1. 检查读者是否有超期未还图书模块:含 3 条路径,设计测试用例覆盖所有路径(路径 P1:输入学生卡号,Tmax=92,t=90,预期“有超期未还图书”;路径 P2:输入学生卡号,Tmax=80,t=90,Tmax=20,t=30,预期“无超期未还图书”;路径 P3:输入学生卡号,Tmax=80,t=90,Tmax=32,t=30,预期“有超期未还图书”)。
  2. 检查图书是否超期未交罚款模块:环路复杂度为 4,确定 4 条基本路径,设计测试用例覆盖路径(路径 P1:输入图书条码号,t1 为空值,Tmax=150,t=180,k=’否’,预期“未超期或超期已交罚款”等)。

单元测试

单元测试内容

单元测试(模块测试)针对软件最小可执行单位—程序模块,采用白盒测试为主、黑盒测试为辅的方法,从 5 个方面检查被测模块:

  1. 模块接口测试。
  2. 局部数据结构测试。
  3. 重要路径测试。
  4. 错误处理测试。
  5. 边界测试。

单元测试步骤

  1. 编码阶段,源程序通过复审和编译检查(无语法错误)后,开始设计测试用例。
  2. 模块非独立程序,需辅助模块模拟与被测模块的联系:
    • 驱动模块:相当于被测模块的主程序,接收测试数据并传送给被测模块,输出实测结果。
    • 桩模块:代替被测模块调用的子模块,可做少量数据操作,不可无操作。

集成和系统测试

集成测试

  1. 定义:单元测试的逻辑扩展,也称组装测试,测试软件单元组合及与其他模块集成能否正常工作,最终测试所有模块组合能否正常工作,集成测试花费时间远超过单元测试,不可直接从单元测试过渡到系统测试。
  2. 集成测试目标和过程
    • 目标:按设计要求用通过单元测试的构件构造程序结构,发现模块间非预期交互产生的隐蔽失效,包括功能性测试(黑盒测试接口规格说明)和非功能性测试(模块性能或可靠性)。
    • 过程(4 个阶段):
      • 计划阶段:确定测试对象和范围,评估测试难度,分工,标识时间、任务、约束,准备资源,定义测试完成标准。
      • 设计阶段:分析被测对象结构、模块、接口,分析策略、工具、环境,估计工作量。
      • 实现阶段:设计集成测试用例。
      • 执行阶段:执行集成测试用例。
  3. 集成方式
    • 一次性集成方式(整体拼装):先分别测试每个模块,再将所有模块组装在一起测试,得到软件系统。
    • 增量式集成方式:先测试单个模块,再逐步组装成较大系统,边连接边测试,发现连接问题,最终组装成软件系统,分为:
      • 自顶向下的增量方式:从主控模块开始沿控制层次自顶向下组装,用桩模块代替下属模块,逐步替换桩模块,进行回归测试,直至构造完整软件结构。
      • 自底向上的增量方式:从最底层模块开始组装测试,无需桩模块,用驱动模块控制测试,逐步替换驱动模块,组装成子系统并测试,直至到达主控模块。
      • 混合增量方式:结合自顶向下和自底向上的优点,如衍变的自顶向下增量测试、自底向上—自顶向下增量测试、回归测试。
  4. 集成测试的实施
    • 制定测试计划,考虑系统组装方法、模块连接顺序、代码编制和测试进度一致性、是否需专门硬件设备。
    • 由专门测试小组(有经验的系统设计人员和程序员)进行,评审人员出席,测试后整理分析结果,形成测试报告。

系统测试

  1. 定义:将集成好的软件系统与计算机硬件、外设、支持软件、数据、人员等其他系统元素结合,在实际运行环境下进行组装测试和确认测试,验证软件功能和性能满足规约要求。
  2. 测试用例设计依据:系统需求分析说明书。
  3. 常见系统测试类型:功能测试、回归测试、可靠性测试、压力测试、性能测试、恢复测试、启动/停止测试、配置测试、安全性测试、可使用性测试、可支持性测试、安装测试、互连测试、兼容性测试、容量测试、文档测试。

验收测试

验收测试概述

验收测试是部署软件前的最后测试操作,确保软件准备就绪,让最终用户用于执行既定功能和任务,向用户表明系统符合预定要求,由相关用户和独立测试人员根据测试计划和结果进行测试和接收,常用策略为正式验收测试、Alpha 测试、Beta 测试,策略选择基于合同需求、组织标准和应用领域。

正式验收测试

  1. 定义:管理严格的过程,通常是系统测试的延续,测试用例为系统测试用例的子集,可由开发小组与用户代表共同执行、完全由用户执行或由用户选客观小组执行。
  2. 优点:测试功能特性已知,细节可评测,可自动执行支持回归测试,过程可评测监测,可接受性标准已知。
  3. 缺点:需大量资源和计划,可能重复系统测试,可能无法发现主观原因造成的缺陷。

Alpha 测试

  1. 定义:由用户在开发环境下或公司内部用户在模拟实际操作环境下进行,开发者在旁记录错误和使用问题,评价软件功能、可使用性、可靠性、性能等,尤其注重界面和特色,无特定测试用例,测试内容由测试员决定,较为主观。
  2. 优点:测试功能特性已知,过程可评测监测,可接受性标准已知,比正式验收测试发现更多主观原因缺陷。
  3. 缺点:需资源和计划,无法控制测试用例,用户可能沿用系统工作方式无法发现缺陷,测试资源可能受压缩。

Beta 测试

(文档中未详细展开 Beta 测试内容,可理解为 Beta 测试是由最终用户在实际使用环境下进行的测试,开发者不在场,用户记录使用中发现的问题并反馈,用于进一步改进软件。)

机票预订系统编码和测试项目实践

机票预订系统编码

  1. 语言选择
    • 移动 APP:需兼容多数手机系统,一般采用 Android 开发环境下的 JAVA 语言;条件允许时,也为苹果手机系统开发相应 APP。
    • Web 网站:根据用户需求和程序员技能选择 C++、JAVA、Python 等语言。
  2. 程序效率分析(计算退款金额模块):标出模块流程图结点,绘制流图,计算环路复杂度为 9。

机票预订系统测试

  1. 测试过程
    • 单元测试:对计算退款金额模块进行基本路径测试,同时测试模块接口、局部数据结构、错误处理等。
    • 集成测试:采用混合增量集成方式。
    • 系统测试:可靠性测试、压力测试、性能测试、安全性测试、可使用性测试等对该系统至关重要。
    • 验收测试:潜在用户广泛,用户参与方便,易测试多种使用场景,发现软件缺陷。
  2. 计算退款金额模块的基本路径测试:确定 9 条基本路径,设计测试用例覆盖每条路径(如路径 P1:输入 D=6,R=1,预期输出“票价-手续费”;路径 P2:输入 D=6,R=0.9,预期输出“票价*0.8-手续费”等)。

面向对象分析

本章首先简单介绍面向对象的基本概念和 UML 面向对象的统一建模语言,然后介绍面向对象的分析方法和步骤,最后,重点介绍每一种分析模型的建立方法、过程以及以航空公司机票预订系统为例进行各种模型的建模。

面向对象方法介绍

面向对象的分析是采用面向对象方法进行软件开发的第一步。

面向对象的基本概念

  1. 对象:对象是要研究的任何事物。有形的实体,如图书、学生、职工、商品、仓库等等;无形的规则、计划或事件,如选课、购买、比赛、法律条文等等。对象由数据(描述事物的属性)和作用于数据的操作(体现事物的行为)构成一个独立整体。
  2. :类是对一组有相同数据和相同操作的对象的定义。类是在对象之上的抽象,对象则是类的具体化(实例)。
  3. 继承性:继承性是子类自动共享父类中数据和方法的机制。
  4. 封装性:数据和加工该数据的方法封装为一个整体,实现独立性很强的模块。封装的目的是把对象的设计者和对象的使用者分开,使用者不必知晓行为实现的细节,只须用设计者提供的消息来访问该对象。
  5. 多态性:多态性是指相同的操作可作用于多种类型的对象上并获得不同的结果。不同的对象收到同一消息可以产生不同的结果,这种现象称为多态性。
  6. 面向对象:面向对象(Object–Oriented,OO),以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界和设计、构建相应的软件系统。
  7. OO 方法:建立在“对象”概念基础上的方法学。对象和传递消息分别表现事物及事物间相互联系;类和继承是适应人们一般思维方式的描述范式。方法是允许作用于该类对象上的各种操作。这种对象、类、消息和方法的程序设计范式的基本点在于对象的封装性和类的继承性。
  8. 面向对象的分析:面向对象的分析(Object Oriented Analysis,OOA)的任务是了解问题域所涉及的对象、对象间的关系和操作,构造问题的对象模型,力争该模型能真实地反映出所要解决的“实质问题”。
  9. 面向对象的设计:面向对象的设计(Object Oriented Design,OOD)是设计软件的对象模型,就是在软件系统内设计各个对象、对象间的关系(如层次关系、继承关系等)、对象间的通信方式(如消息模式)等,总之是设计各个对象“应做些什么”。
  10. 面向对象的实现:面向对象的实现(Object Oriented Implementation,OOI)指软件功能的编码实现,包括每个对象的内部功能的实现,确立对象哪一些处理能力应在哪些类中进行描述,确定并实现系统的界面、输出的形式及其它控制机理等。

统一建模语言

统一建模语言(Unified Modeling Language,UML)是一个支持模型化和软件系统开发的图形化语言,简单、统一,而且能表达软件设计中的动态和静态信息,目前已成为可视化建模语言的工业标准。

UML 的特点

  1. UML 支持面向对象技术的主要概念,简洁明了,容易掌握和使用。
  2. UML 是一种通用的建模语言,有效的消除了各种建模语言之间不必要的差异。
  3. UML 建模能力强,能清晰地表示系统的逻辑模型和实现模型。
  4. UML 是一种建模语言,而不是一个开发过程。
  5. 用 UML 建立模型可以用 Java、VC++、delphi 等任何面向对象的程序设计语言实现。

UML 的基本组成

组成要素包括 UML 的基本构造块、支配这些构造块如何放置在一起的规则和运用于整个语言的公用机制。

  1. UML 的基本构造块:共 3 种,分别是事物、关系和图。
    • 事物:对模型中最具有代表性的成分的抽象,包括结构事物(如类、接口、协作、用例等)、行为事物(如交互、态机等)、分组事物、注释事物。
    • 关系:用来把事物结合在一起,包括依赖、关联、泛化和实现关系。
    • 图:包括用例图、类图、包图、对象图等 10 种图,从不同侧面对系统进行描述。
  2. UML 规则:描述了一个结构良好的模型看起来应该像什么。
    1. 命名:为事物、关系和图起名。
    2. 范围:给一个名称以特定含义的语境。
    3. 可见性:怎样让其他人使用或看见名称。
    4. 完整性:事物如何正确、一致地相互联系。
    5. 执行:运行或模拟动态模型的含义是什么。
  3. UML 公共机制:有 4 种贯穿整个语言且一致应用的公共机制。
    1. 规格说明:对构造块的语法和语义的文字描述。
    2. 修饰:每个元素都有一个基本符号,如+代表 public、-代表 private、#代表 protect 等。
    3. 通用划分:包括类/对象二分法(如用例和用例实例)、接口/实现二分法(如用例和实现它们的协作)。
    4. 扩展机制:对 UML 图示符号的扩展,包括构造型、标注值和约束。

面向对象分析概述

面向对象需求分析阶段的任务与结构化需求分析方法完全相同,首先是需求获取,然后分析获取的需求、建立分析模型及规格说明文档。

面向对象的分析模型一般有 3 大类:

  1. 用例模型:通过用例和场景表示的功能模型。
  2. 对象模型:用类和对象表示的静态模型。
  3. 交互模型:由状态图、顺序图和活动图表示的动态模型。

任何一个软件的开发,都需要建立用例模型(功能)、对象模型(软件要处理的数据)和动态模型(交互作用和时序)。

建立用例模型

用例图从用户的观点描述系统的功能,由一组用例、参与者以及它们之间关系所组成。参与者是与系统交互的外部实体(使用系统的用户、系统交互的其他外部系统、硬件设备或组织机构),用例是从用户角度描述系统的行为,一个功能描述成一系列事件。

建立用例模型的过程

  1. 确定业务参与者:即标识出目标系统将支持的不同类型的用户。系统的业务参与者可以是人、组织机构、外部系统或硬件设备等。首先画出系统的环境图,环境图包含目标系统、输入/输出数据流和系统参与者三部分。确定参与者可参考旧系统文档、需求会议记录等资料,参与者一般用名词或名词性词组命名。
  2. 确定业务需求用例
    1. 通过环境图提交输入和接收输出的各方确定潜在用例。
    2. 从每个参与者与系统交互的过程确定用例,包括参与者的特定任务、读写的信息、更新的内容等。
    3. 每个用例应是系统的一个独立功能,若功能过大需进行分解;若用例包含不同时间段或不同用户执行的内容,需分离形成单独用例。
    4. 若用例描述不清晰,可进行实地调查分析。用例命名一般采用动词加名词的形式,且每个用例需给出规格说明,包括用例名、参与者、前置条件、后置条件、主事件流和备选事件流。
  3. 创建用例模型:围绕参与者创建,可先为每个参与者创建分用例图,再组合成整体用例图。
    • 基本图形符号:参与者用小木头人表示,用例用椭圆形表示,二者通过无方向直线以《communicate》关系通信。
    • 用例间关系:包含关系(箭头指向被包含用例,标《include》)、扩展关系(箭头指向基本用例,标《extend》)。
    • 泛化关系:用带空心箭头的直线表示,箭头指向被继承方。参与者后代继承祖先所有用例,用例子用例继承父用例的结构、行为和关系,可重载父用例行为。

建立用例模型的实例

以高校图书借阅系统为例,进行功能建模。

  1. 确定业务参与者:包括图书管理员和读者两类用户。
  2. 确定业务需求用例
    • 图书管理员的用例:读者信息维护、图书信息维护、图书管理员的查询事务等,其中图书信息维护包含添加图书、修改图书信息等子用例。
    • 读者的用例:修改个人密码、读者的查询事务、借书、续借、还书等。
  3. 借书用例的规格说明
用例名:借书 参与者:读者
1.前置条件:图书和读者信息已存在系统的数据库中
2.后置条件:如果此用例执行成功,则借阅列表中增加了此读者的一条借阅信息。如果执行不成功,则系统不发生任何变化。
3.主事件流
(1)当读者单击借书按钮时,此用例开始。(2)读者扫描校园卡。
(3)系统检查此读者是否有超期未还图书。
(4)如果没有超期未还图书,则系统检查此读者的借书量上限是否已满。
(5)如果借书量上限未满,则系统提示读者扫描图书条形码。
(6)读者扫描图书条形码。
(7)系统生成借阅信息写入数据库,包括借书日期(默认为当前日期时间),还书日期(空值),是否归还(默认为否),续借日期(空值),罚款金额(空值),是否交罚款(默认为否)。
(8)系统修改此图书的状态为已借出。
(9)系统显示借阅信息。(10)读者选择继续借阅或退出。
4.备选实事件流
4.1 如果读者扫描校园卡后,系统检查有超期未还图书,则显示超期信息,此用例结束。
4.2 如果系统检查此读者借书上限已满,提示“借书量已满无法再借阅!”,此用例结束。
4.3 如果系统不能成功更新数据库,提示“系统操作失败,请稍候再试!”,此用例结束。

建立对象模型

对象模型(类图)是模型的静态结构,表示软件要处理的数据。

建立对象模型的过程

共 5 个步骤,具体如下:

  1. 划分主题:对于稍大型系统,将大问题划分为若干主题,每个主题规模适中(含 6 个左右的类为宜),功能具有独立性和完整性,与其他主题联系最少,再分别建模后合并。
  2. 确定类和对象
    1. 找出候选的类和对象:从问题域中抽象有意义的事物,包括物理实体、人或组织的角色、事件、相互作用、概念等。可通过自然语言需求陈述中的名词筛选候选类,形容词作为属性线索,动词作为操作候选。
    2. 筛选正确的类和对象:遵循冗余、无关、笼统、属性、操作、实现等筛选原则,去掉不必要的类和对象。
  3. 确定类与类之间的关系:包括泛化关系、关联关系和聚合关系。
    1. 泛化关系:从一般类划分特殊类,或从特殊类抽出一般类,子类继承父类属性和行为,可拥有独有属性和行为。
    2. 聚合关系:表示整体与部分的关系,如大学和学院、设备和零件等。
    3. 关联关系:最常见的类间关系,需识别静态关系、分析多重性(一对一、一对多、多对多),以及关联的属性和操作,特殊关联可视为关联类。
  4. 确定类和关联的属性:每个类的对象至少包含一个“id”属性;属性需适合类中所有对象;系统需存储的数据必须定义为属性;导出属性应略去;删除错误或不确定的属性,并可确定属性的数据类型。
  5. 确定类和关联的操作
    1. 简单的操作:包括对象的建立、初始化、关联的建立与切断、属性存取、对象删除等,分析时隐含,编码时需定义。
    2. 复杂的操作:分为计算操作(利用属性值计算实现功能)和监控操作(处理输入输出、设备控制和数据存取)。

建立对象模型的实例

以高校图书借阅系统为例创建类图,该系统无需划分主题。

  1. 类和对象及关系:类和对象包括图书、单本图书、读者、读者类别、图书存放区域、图书管理员。关系为图书包含多个单本图书;图书对应一个存放区域;图书管理员管理一个区域图书,每个区域多名管理员;读者可借阅单本图书;读者属于一个读者类别。
  2. 类与对象的属性
类与对象 属性
图书 书号,书名,作者,单价,类别,出版社,出版日期
单本图书 条码号,图书状态
读者 读者号,密码,姓名,性别,电话,身份证号
读者类别 类别号,类别名,最大借书天数,最大续借天数,最大借书数量
存放区域 区域名,最大存放数量
图书管理员 编号,姓名,密码,电话
借阅 读者号,图书条码号,借书日期,还书日期,是否归还,续借日期,罚款金额,是否交罚款
  1. 类与对象的操作
类与对象 操作
读者 修改密码,查询读者信息,查询个人借阅信息
图书 查询图书信息,统计各类别图书借阅情况
单本图书 修改图书状态
借阅 增加读者借阅信息,修改还书日期、是否归还等信息,查询借阅信息,查询超期未还信息
读者类别 查询各类别读者借书量上限、最大借书天数等
图书管理员 修改密码

建立交互模型

交互模型一般是由顺序图、状态图和活动图表示的动态模型,是从需求分析向设计过渡的重要环节。

顺序图

顺序图以图形形式描绘用例的交互场景,包含交互时间顺序、参与交互的对象及对象间交互信息,还可包含操作界面表单对象和系统服务器对象,直观易懂,便于交流和需求向设计过渡。

  1. 顺序图结构:纵轴从上到下为交互时间顺序,横轴从左到右为参与交互的参与者对象、类对象、表单对象和服务器对象。
  2. 基本符号:竖直虚线(生命线)、细长矩形(生存活跃期)、有向实线、有向虚线。
  3. 消息类型
    1. 普通消息:连接两个对象生命线,接收对象执行请求动作,可含参数。
    2. 调用消息:属于过程调用的一部分。
    3. 返回消息:执行动作后返回信息,不执行新动作。
    4. 异步消息:发送对象不等待接收对象响应。
  4. 实例:借书用例顺序图涉及读者、借书 Form、借阅 object、单本图书 object、系统 server;还书用例顺序图涉及读者、还书 Form、系统 Server 等对象的交互过程。

状态图

面向对象分析中,状态图描述每一类对象的动态行为,画法与结构化分析方法基本相同,由对象的状态和状态转换组成。仅针对具有明显状态特征和复杂状态转换的类绘制状态图。

  1. 高校图书借阅系统实例-借阅类对象状态:未归还、未续借、未超期;未归还、已续借、未超期;未归还、未续借、已超期、未交罚款;未归还、已续借、已超期、未交罚款;已归还、未超期;已归还、已超期、已交罚款。
  2. 单本图书对象状态:未借出、已借出、不可借和已下架四种状态,状态间通过借书、还书、维护等事件转换。

活动图

活动图用来捕捉用例的活动,以框图形式显示动作及其结果,是描述活动流的流图,包含起点、终点、动作、转移、决策、同步棒和泳道等元素。

  1. 借书用例活动图:涉及用户接口、业务逻辑接口、数据库接口,流程包括扫描校园卡、检查超期图书、检查借书量上限、扫描图书条形码等步骤。
  2. 还书用例活动图:涉及用户接口、业务逻辑接口、第三方支付接口、数据库接口,流程包括扫描图书条形码、检查超期罚款、计算罚款金额、支付罚款等步骤。

机票预订系统面向对象分析项目实践

建立用例模型

  1. 确定业务参与者:包括航空公司操作员、旅客两类用户和时钟。
  2. 确定业务需求用例
    • 航空公司操作员的用例:航班信息维护、注销旅客、操作员的查询事务。
    • 旅客的用例:旅客信息维护、订票、退票、取票、旅客的查询事务。
    • 补充:航空公司操作员可在已取票情况下,期限内为旅客退票,流程与旅客退票相同。
  3. 核心用例规格说明
    • 订票用例:前置条件为航班及票务信息存在于数据库;主事件流包括生成订单、付款、生成机票等步骤;备选事件流涵盖未付款、取消订单、付款失败等情况。
    • 退票用例:前置条件为旅客订单和机票信息存在于数据库;主事件流包括退票审核、计算退款金额、退款等步骤;备选事件流有审核失败、取消退票、退款失败等情况。

建立对象模型

  1. 类和对象及关系:类和对象包括旅客、航班、航班票务、订单和机票。关系为航班包含多个航班票务对象;旅客可对航班票务下订单;每个订单产生一张机票。
  2. 类与对象的属性
类与对象 属性
旅客 身份证号,密码,姓名,电话
航班 航班号,起飞站,起飞时间,到达站,到达时间,登机时间,登机口
航班票务 航班号,舱位,日期,票价,余票
订单 订单号,折扣,订票时间,订单状态
机票 订单号,座位,机票状态
  1. 类与对象的操作
类与对象 操作
旅客 注册,个人信息维护,注销
航班 查询航班信息
航班票务 按航班日期查询票务信息,统计航班预订情况
订单 生成订单,改变订单状态,统计订单情况,计算退款金额
机票 生成机票,改变机票状态,统计机票信息

建立交互模型

  1. 顺序图:重点绘制了订票、取票、退票的顺序图,涵盖用户登录、验证、订单处理、支付、信息修改等完整交互流程。
  2. 状态图
    • 订单类对象状态:未付款、已付款、已取消、已退款和已完成,通过付款、取票、退票等事件转换。
    • 机票对象状态:未取票、已取票、已退票,通过取票、退票事件转换。
  3. 活动图:分别绘制了订票、取票、退票用例的活动图,清晰展现了从用户操作到系统处理、数据库更新的完整流程,包含决策判断和分支处理。

面向对象的设计

本章内容:

  1. 面向对象的设计准则和设计步骤
  2. 系统环境模型的设计和体系结构的设计
  3. 重点:问题域子系统、人机交互子系统、任务管理子系统和数据管理子系统的设计以及对象模型的设计
  4. 系统的构件图和部署图设计
  5. 航空公司机票预订系统的面向对象设计实例

面向对象的设计方法

在面向对象的方法中,面向对象的分析和设计界限模糊,二者是逐步扩充、细化且反复迭代的过程。

面向对象的设计准则

  1. 模块化:结构化设计中模块多为过程或函数,面向对象设计中模块通常是类、对象或接口。后者将数据和对数据的处理封装在一起,而非分离控制逻辑。
  2. 抽象:不仅支持过程抽象,还支持数据抽象,类就是对一组具有相似特征对象的抽象概括。
  3. 信息隐藏:借助类对象的封装性实现,类对象的属性表示方法和操作实现算法对用户隐藏,用户仅能通过接口访问。
  4. 低耦合:降低不同类对象间的关联程度,减少一个类的变更对其他类造成的影响。
  5. 强内聚:类内部各元素结合紧密,一个类应专注于单一用途,若功能繁杂则需分解为多个专用类。
  6. 可重用性:软件开发效率和质量提升的关键。优先使用系统类库和历史开发的类;创建新类时,需考虑未来的复用可能性。

面向对象的设计步骤

面向对象设计分为概要设计和详细设计,概要设计聚焦系统整体框架,详细设计侧重具体对象,具体步骤如下:

  1. 设计系统的环境模型:明确与软件交互的外部实体,包括人、其他软件系统和硬件设备,并定义它们的交互方式。
  2. 设计系统的体系结构:将系统划分为若干子系统,可采用自底向上(对象组合为子系统,逐步整合)或自顶向下(先划分子系统)的方法。
  3. 设计各子系统:核心涵盖问题域子系统、人机交互子系统、任务管理子系统和数据管理子系统。
  4. 对象的设计:细化和优化需求分析阶段的类图,以问题域对象设计为核心,最终形成详细的对象模型。

设计系统的环境模型

环境模型设计方法和内容

首先通过分析阶段的功能建模,确定与人、其他软件系统、硬件设备等和软件交互的外部实体;再对交互方式进行建模并给出设计规格说明。接口作为外部实体与目标系统通信的软硬件标准,需针对性详细设计,例如人机交互界面、硬件设备接口、第三方系统调用接口等。

环境模型设计实例(高校图书借阅系统)

该系统的外部实体及对应设计需求如下:

  1. 图书管理员:负责图书信息维护和数据查询,需设计合理的人机交互界面。
  2. 读者:涵盖高校学生和教职工,登录、借还书及查询界面需简洁实用、操作便捷。
  3. 刷卡、扫码机:用于扫描校园卡和图书二维码的硬件设备,需设计配套的软硬件接口。
  4. 支付宝、微信支付系统:用于超期罚款缴纳,采用成熟的二维码扫码付款方式,设计对应接口即可。

设计系统的体系结构

体系结构的概念

体系结构包含操作系统之上的基础设施软件、实现计算逻辑的主体应用程序以及便捷的用户界面程序。结构化程序设计时代,程序规模较小,凭借自顶向下、逐步求精的方法和低耦合模块设计即可构建良好结构;而面向对象时代,软件规模大幅扩大,需关注构件搭配、结构稳定性及适配特定领域需求等问题。

Bass、Ctements 和 Kazman 对其定义为:一个程序或计算机系统的软件体系结构,包括一个或一组软件构件、软件构件的外部可见特性及其相互关系。其中“软件外部的可见特性”涵盖构件提供的服务、性能、特性、错误处理、共享资源使用等。

几种典型的体系结构

软件体系结构风格定义了系统家族,包含特定的构件、连接件词汇表及约束,反映领域内系统共有的结构和语义特性,为大粒度软件重用提供可能,常见类型如下:

  1. 管道与过滤器风格
    • 结构:每个构件有输入和输出数据流,构件读取输入数据后经处理生成输出数据流,通过管道连接各构件。
    • 优点:构件隐蔽性好,高内聚、低耦合;系统输入输出行为可通过过滤器行为合成;支持软件重用和并行执行;便于维护及性能分析。
    • 缺点:多为批处理结构,不适合交互类应用;数据传输无通用标准,需额外解析和合成数据,降低系统性能并增加开发复杂度。
  2. 数据抽象与面向对象风格
    • 结构:基于数据抽象和面向对象思想,数据表示及操作封装在抽象数据类型或对象中,构件为对象实例,通过过程调用交互。
    • 优点:实现信息隐藏,修改对象表示不影响其他对象;可将数据存取问题分解为交互代理程序集合。
    • 缺点:对象交互依赖标识,标识变更需修改所有调用对象,易产生不可预知的副作用。
  3. 仓库风格
    • 结构:包含中央数据结构和独立构件,中央数据结构描述当前状态,独立构件在其上执行操作。根据控制原则分为传统型数据库(事件触发进程)和黑板系统(中央数据状态触发进程)。
    • 黑板系统组成:由知识源(独立的应用相关知识,通过黑板交互)、黑板数据结构(按层次组织的问题解决数据)和控制(由黑板状态驱动)三部分构成。
    • 优点:控制算法与中心存储库分离,易更改维护;知识源可重用,具有容错性。
    • 缺点:算法不确定,测试困难;成本高、效率低,难以制定控制策略,不支持并行机制,常用于语音和模式识别等信号处理领域。
  4. 客户/服务器(C/S)体系结构
    • 核心思想:基于资源不对等,为实现资源共享提出,由数据库服务器、客户机应用程序和网络三部分组成,应用系统分为数据管理层、应用逻辑层和表示层。
    • 分类:传统两层结构中,服务器实现数据管理层,客户机分为仅实现表示层的瘦客户机和实现表示层与应用逻辑层的胖客户机;三层结构则分离三层功能,逻辑独立。
    • 优点:提高系统可维护性和可扩展性;灵活选用平台和硬件;支持分层并行开发,可选择适配的开发语言;隔离表示层与数据层。
  5. 浏览器/服务器(B/S)体系结构
    • 核心思想:通过通用浏览器实现复杂功能,无需在客户机安装专用应用程序。
    • 优点:零客户端方式,充分利用网络资源,大幅减少应用程序维护工作量。
    • 缺点:系统扩展能力差,安全性难控制;数据查询响应慢,动态交互性弱,不利于在线事务处理。

体系结构设计实例(高校图书借阅系统)

考虑到当前 PC 机普遍联网,用户习惯通过浏览器获取信息,该系统采用 B/S 体系结构,方便用户通过网络进行图书借阅和信息查询。系统分为 WWW 页面层(含个人信息管理、借书管理等功能)、应用逻辑层和数据管理层。

设计问题域子系统

问题域子系统对应软件功能部分,在 C/S 或 B/S 结构中属于应用逻辑层。设计核心是对分析阶段的用例图、类图、顺序图等分析模型进行补充调整,具体操作如下:

  1. 调整需求:应对用户需求或外部环境变化,修正系统分析员对问题的理解偏差。
  2. 复用已有的类:优先使用系统类库或历史项目中的成熟类,减少重复开发。
  3. 组合问题域类:引入根类,将分散的问题域类整合起来,优化类结构。
  4. 增添泛化类:建立类间协议,规范类的行为和交互方式。
  5. 调整继承层次:优化类的继承关系,提升类的复用性和扩展性。

设计人机交互子系统

人机交互主要通过用户界面实现,界面设计质量直接影响用户满意度。设计需融入交互细节,包括菜单、按钮等对象及操作,还有界面布局和输入输出显示等内容。

面向对象的用户界面设计步骤

用户界面设计是反复迭代的过程,可借鉴动漫设计思路,先绘制界面草图,经商讨修改并结合用户需求优化后,再定义对象、操作等信息,具体步骤如下:

  1. 从系统输入输出及用户交互中提取信息,定义界面中的对象和操作。
  2. 明确导致界面状态变化的各类事件。
  3. 描述展示给用户的每个界面状态。
  4. 说明用户如何通过界面信息理解系统状态,助力设计沟通。
  5. 完成界面详细布局,考虑成分分布合理性、外观美观性和舒适性,确定窗口规格、图形样式、文字内容及菜单设计等。

WWW 的界面设计

  1. 设计目标
    • 简单清晰:界面简洁,直击核心功能。
    • 风格一致:各页面导航方式、排版布局、色彩搭配保持统一。
    • 适应领域:界面和导航设计契合应用系统所属领域,如财务网站、校园网、游戏网站风格各具特色。
    • 功能健壮:验证输入、输出、选择等操作的合理性,并给出提示信息。
    • 导航直观:满足用户直观操作需求,降低使用难度。
    • 视觉吸引:在内容外观、文本布局、图片设计和色彩协调等方面打造良好视觉效果。
    • 环境兼容:适配不同硬件、操作系统和浏览器。
  2. 设计流程
    1. 根据用户需求绘制界面布局草图。
    2. 将功能目标映射为具体界面行为。
    3. 为每个界面行为定义一组用户任务。
    4. 设计每个界面行为的情节串联图板,明确导航链接、内容信息等。
    5. 借助美学专家优化界面布局和情节串联图板。
    6. 明确界面对象,优先复用已有对象。
    7. 基于状态图、顺序图和活动图,开发界面行为表示和用户交互过程表示。
    8. 详细描述界面布局的每种状态。
    9. 以可用性为核心,评审并优化界面设计模型。

设计任务管理子系统

任务可理解为进程,是执行一系列动作的程序段。当系统存在多个并发行为时,需先明确行为间的依赖或排斥关系,再进行任务管理子系统设计。

  1. 分析并发性:以面向对象分析阶段的动态模型为依据,若两个对象无交互或可同时接受事件,则二者本质上是并发的。通过分析对象状态图及事件转换,可将非并发对象归并到一条控制线中。并发行为可在多处理器上实现,也可在单处理器上通过多任务操作系统仿真实现。
  2. 设计任务管理子系统
    1. 确定事件驱动型任务:多负责通信工作,由特定事件触发,例如淘宝购物的下单流程。
    2. 确定时钟驱动型任务:每隔固定时间间隔触发执行,如定时消息推送、支付宝定时打款给卖家等。
    3. 确定优先任务:分为需在严格时限内完成的高优先级任务和后台处理的低优先级任务。
    4. 确定关键任务:关乎系统成败,对可靠性要求极高的核心处理任务。
    5. 确定协调任务:当系统任务数量达到三个及以上时,可增设协调任务统筹管理。
    6. 尽量减少任务数量:简化系统结构,降低任务调度复杂度。
    7. 确定资源需求:综合考量性能需求,决定软硬件实现方案,合理选用多处理器或固件。

设计数据管理子系统

数据管理子系统是系统存储或检索对象的基础设施与组织形式,设计核心在于选择合适的存储管理模式并完成数据格式和服务设计。

选择数据存储管理模式

根据应用系统特点,常见的数据存储管理模式有以下三种:

  1. 文件管理系统
    • 优点:作为操作系统组成部分,长期保存数据成本低、管理简单。
    • 缺点:文件操作级别低,需编写额外代码提升抽象级别;不同操作系统的文件管理系统差异明显,兼容性差。
  2. 关系数据库管理系统
    • 优点:提供中断恢复、共享性、完整性、安全性、并发控制等基础数据管理功能;接口统一,支持标准化 SQL 语言。
    • 缺点:运行开销大;难以满足数据类型丰富或操作不标准的高级应用需求;SQL 语言的集合操作与多数程序设计语言的单记录处理方式衔接不自然。
  3. 面向对象数据库管理系统
    • 设计途径:一是扩展关系数据库管理系统,增加抽象数据类型、继承机制及类和对象管理服务;二是扩展面向对象程序设计语言,增加对象存储和管理机制。

设计数据管理子系统

  1. 设计数据格式
    • 文件系统:先列出类属性表并规范为第一范式表,再为每个表定义文件,结合性能和存储需求调整设计。
    • 关系数据库管理系统:需将类图映射为第三范式表,具体规则如下:
      1. 普通类可映射为一张或多张表,多表映射采用横切或竖切方法。
      2. 关联关系映射:一对一关联可采用双向/单向导航、合并表或独立表方式;一对多关联在“多”端表添加“一”端主键作为外键,或设独立关联表;多对多关联需引入关联表,转化为两个一对多关联。
      3. 继承关系映射:可将基类与子类映射到一张表,适用于子类少、属性少的情况;也可只为每个子类建表(含基类属性),适用于子类不多、基类属性少的情况;还可分别为基类和子类建表,子类表通过外键关联基类表,适用于属性较多的场景。
  2. 设计相应的服务
    • 文件系统:定义对象服务类,提供通知对象保存自身、检索已存储对象并提供给其他子系统等服务。
    • 关系数据库管理系统:同样定义对象服务类,核心服务与文件系统一致,需适配数据库表的访问、数据行定位及数据更新等操作。

数据管理子系统设计实例(高校图书借阅系统)

系统中的图书、读者、借阅明细等数据需长期存储,且对完整性、安全性和并发一致性要求较高,因此选用关系数据库管理系统。按照类图映射规则,将 6 个普通类映射为 6 张表,4 个一对多关联通过在“多”端表添加外键实现,1 个多对多关联(单本图书与读者的借阅关联)映射为独立的借阅表,最终形成 7 张表,其中 3 张表可根据需求进行横切处理。

机票预订系统面向对象设计项目实践

机票预订系统环境模型设计

系统的外部实体包括航空公司操作员、旅客、支付宝/微信支付系统、银行支付系统和取票机,需针对各实体设计适配的交互接口和方式。

机票预订系统体系结构设计

该系统采用 B/S 体系结构,方便用户通过互联网便捷预订机票。系统分为 WWW 页面层(含旅客信息管理、订票管理、取票管理等功能)、应用逻辑层和数据管理层。

机票预订系统任务管理子系统设计

先分析订票、退票等模块操作对象的并发性,再划分设计各类任务:

  1. 事件驱动型任务:系统核心任务类型,如旅客订票流程,从查询机票、选择订单到付款,均由用户点击等事件触发。设计时需保证事件具备重放能力,应对付款不及时等问题。
  2. 时钟驱动型任务:包括定时取消未付款订单、航班起飞前四小时短信提醒等。
  3. 高优先级及关键任务:涵盖付款、打印机票、计算退款金额等核心操作。
  4. 低优先级任务:例如旅客退票后的退款任务。

机票预订系统数据管理子系统设计

系统的航班、旅客、订单等数据需长期存储,且对完整性、安全性和并发一致性要求高,选用关系数据库管理系统。将 5 个普通类映射为 5 张表,4 个一对多关联通过在“多”端表添加外键实现,其中航班票务表和订单表可根据需求进行横切处理。

面向对象的实现和测试

本章内容

面向对象的实现:面向对象语言的技术特点、如何选择程序设计语言、如何确定程序设计风格、类和应用系统的实现。

面向对象的测试:面向对象分析的测试、面向对象设计的测试、面向对象编程的测试、面向对象单元测试、面向对象集成测试和面向对象系统测试。

面向对象的实现

面向对象的实现主要任务是把设计阶段产生的结果用代码、脚本和可执行文件等构件来实现,本阶段重点是选择合适的程序设计语言和确定程序设计风格。

面向对象语言的技术特点

  1. 具有支持类和对象概念的定义与实现机制
  2. 具有实现继承的语言机制
  3. 具有实现属性和服务的机制
  4. 具有参数化类
  5. 提供类型检查
  6. 提供类库
  7. 提供持久对象的保存
  8. 提供封装
  9. 提供可视化开发环境

选择程序设计语言

从面向对象观点看来,能够更完整、更准确地表达问题域语义的面向对象语言的语法是非常重要的。选择编程语言的关键因素是语言的一致表达能力、可重用性和可维护性

  1. 一致的表示方法:始终保持表示方法稳定不变,既有利于在软件开发过程中使用统一的概念,又有利于维护人员理解软件的各种配置成分。
  2. 可重用性:始终显示地表示问题域语义,这样既可能重用面向对象分析结果,也可能重用相应的面向对象设计结果和程序设计结果。
  3. 可维护性:维护人员面对的往往主要是源程序,如果程序能显示地表达问题域语义,对维护人员理解待维护的软件有很大帮助。

在选择编程语言时,应该考虑的其他因素还有:对用户学习面向对象分析、设计和编码技术所能提供的培训操作,在使用这个面向对象语言期间能提供的技术支持,能提供给开发人员可使用的开发工具、开发平台,对机器性能和内存的需求,集成已有软件的容易程度等等。

总之,应尽量选用面向对象语言来实现面向对象分析和设计的结果。

程序设计风格

传统的程序具有源程序文档化、数据说明标准化、语句结构简单化、输入/输出方法规范化等良好的设计准则,可以方便人的理解、便于维护。

良好的面向对象的程序设计风格既包括传统的程序设计风格准则,也包括为适应面向对象方法所特有的概念而必须遵循的一些准则。

  1. 提高重用性
    一般来说,代码重用有两种:本项目内的代码重用和新项目重用旧项目的代码。内部重用一般使用继承机制,外部重用即一个项目重用另一个项目的代码,要考虑的面比较广,需要有长远的眼光、反复考虑、精心设计。 1. 提高方法的内聚 2. 减小方法的规模 3. 保持方法的一致 4. 把策略与实现分开 5. 全面覆盖 6. 尽量不使用全局信息 7. 利用继承机制 8. 使用委托机制

  2. 提高可扩充性
    可扩充性是指系统可被灵活的扩展,从而适应需求的变化和新情况的出现。提高可重用性的准则也适用于可扩充性。
    另外,提高可扩充性的准则: 1. 封装类的实现策略 2. 减少一个方法访问对象的数量 3. 不使用多分支语句 4. 精心选择和定义公有方法

  3. 提高健壮性
    所谓健壮性是指当输入数据非法时,算法也能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。
    提高健壮性应遵循以下准则: 1. 能预期用户的错误操作 2. 能检查参数是否合法 3. 动态分配数据容量 4. 根据测试结果进行优化

类和应用系统的实现

类的实现是核心问题,所有的数据都被封装在类的实例中,而整个程序则被封装在一个更高级的类中。

在使用既存构件的面向对象系统中,可以只花费少量时间和工作量来实现软件。只要增加类的实例,开发少量的新类和实现各个对象之间互相通信的操作,就能建立需要的软件。

方案:先开发一个比较小、比较简单的类,作为开发比较大、比较复杂的类的基础。实现方法:

  1. “原封不动”重用。
  2. 一个能够完全符合要求特性的类可能并不存在,那么可以进化性重用。
  3. 不用任何重用来开发一个新类,即“废弃性”开发。
  4. 一个类应是自主的,有责任定位和报告错误。

应用系统的实现是在所有的类都被实现之后的工作,实现一个面向对象的系统是一个比用过程性方法更简单、更简短的过程。

面向对象测试

传统测试的策略是从小到大的测试,即从单元测试开始,逐步进入集成测试,最后是有效性和系统测试。

面向对象程序的结构不再是传统的功能模块结构,而且,面向对象软件抛弃了传统的开发模式,对每个开发阶段都有不同以往的要求和结果,已经不可能用功能细化的观点来检测面向对象分析和设计的结果。因此,传统的测试模型对面向对象软件已经不再适用。

面向对象的开发模型突破了传统的瀑布模型,将开发分为面向对象分析(OOA)、面向对象设计(OOD)和面向对象编程(OOP)三个阶段。

针对这种开发模型,结合传统的测试步骤的划分,我们把面向对象的软件测试分为:面向对象分析的测试、面向对象设计的测试、面向对象编程的测试、面向对象单元测试、面向对象集成测试和面向对象系统测试。

面向对象分析的测试

面向对象分析(OOA)直接映射问题空间,将问题空间中的实例抽象为对象,用对象的结构反映问题空间的复杂实例和复杂关系,用属性和操作表示实例的特性和行为。

OOA 的结果是为后面阶段类的选定和实现,类层次结构的组织和实现提供平台。

对 OOA 的测试,应从以下几个方面进行:对认定的对象的测试、对认定的结构的测试、对认定的主题的测试、对定义的属性和实例关联的测试、对定义的服务和消息关联的测试。

  1. 对认定的对象的测试

    1. 认定的对象是否全面,是否反映问题空间中所有涉及到的所有实例。
    2. 认定的对象是否具有多个属性。只有一个属性的对象应作为其他对象的属性。
    3. 对认定为同一对象的实例是否有区别于其他实例的共同属性。
    4. 对认定为同一对象的实例是否提供或需要相同的服务,不是的对象需要分解或利用继承性来分类表示。
    5. 如果系统没有必要始终保持对象代表的实例的信息、提供或者得到关于它的服务,认定的对象也无必要。
    6. 认定的对象的名称应该尽量准确、适用。
  2. 对认定的结构的测试
    认定的结构指的是多种对象的组织方式,用来反映问题空间中的复杂实例和复杂关系。分为两种:分类结构和组装结构。

    1. 分类结构:一般与特殊的关系,测试的方面(1)对于结构中的一种对象,尤其是处于高层的对象,是否在问题空间中含有不同于下一层对象的特殊可能性,即是否能派生出下一层对象。(2)对于结构中的一种对象,尤其是处于同一低层的对象,是否能抽象出在现实中有意义的更一般的上层对象。(3)对所有认定的对象,是否能在问题空间内向上层抽象出在现实中有意义的对象。(4)高层的对象的特性是否完全体现下层的共性。(5)低层的对象是否有高层特性基础上的特殊性。 2. 组装结构:整体与局部的关系,测试的方面 (1)整体(对象)和部件(对象)的组装关系是否符合现实的关系。(2)整体(对象)的部件(对象)是否在考虑的问题空间中有实际应用。(3)整体(对象)中是否遗漏了反映在问题空间中有用的部件(对象)。(4)部件(对象)是否能够在问题空间中组装新的有现实意义的整体(对象)。
  3. 对认定的主题的测试
    主题是在对象和结构的基础上更高一层的抽象,如同文章对各部分内容的概要。
    对主题层的测试的几个方面:(1)贯彻 George Miller 的”7±2”原则,如果主题超过 9 个,对有较密切属性和服务的主题进行归并。(2)主题所反映的一组对象和结构是否具有相同和相近的属性和服务。(3)认定的主题是否是对象和结构更高层的抽象,是否便于理解 OOA 结果的概貌(尤其是非技术人员的 OOA 结果的读者)。(4)主题间的消息联系(抽象)是否代表了主题所反映的对象和结构之间的所有关联。

  4. 对定义的属性和实例关联的测试
    属性是用来描述对象或结构所反映的实例的特性,实例关联是反映实例集合间的映射关系。对属性和实例关联的测试从如下几个方面考虑:(1)定义的属性是否对相应的对象和分类结构的每个现实实例都适用(2)定义的属性在现实世界是否与这种实例关系密切。(3)定义的属性在问题空间是否与这种实例关系密切。(4)定义的属性是否能够不依赖于其他属性被独立理解。 (5)定义的属性在分类结构中的位置是否恰当,低层对象的共有属性是否在上层对象属性体现。(6)在问题空间中每个对象的属性是否定义完整。(7)定义的实例关联是否符合现实。(8)在问题空间中实例关联是否定义完整,特别需要注意 1 对多和多对多的实例关联。

  5. 对定义的服务和消息关联的测试
    定义的服务,就是定义的每一种对象和结构在问题空间所要求的行为。由于问题空间中实例间必要的通信,在 OOA 中相应需要定义消息关联。
    对定义的服务和消息关联的测试从如下几个方面进行: 1. 对象和结构在问题空间的不同状态是否定义了相应的服务。 2. 对象或结构所需要的服务是否都定义了相应的消息关联。 3. 定义的消息关联所指引的服务提供是否正确。 4. 沿着消息关联执行的线程是否合理,是否符合现实过程。 5. 定义的服务是否有重复,是否定义了能够得到的服务。

面向对象设计的测试

面向对象设计(OOD)以 OOA 为基础归纳出类,并建立类结构或进一步构造成类库,实现分析结果对问题空间的抽象。是对 OOA 进一步细化和更高层的抽象。

OOD 确定类和类结构不仅是满足当前需求分析的要求,更重要的是通过重新组合或加以适当的补充,能方便实现功能的重用和扩增,以不断适应用户的要求。

对 OOD 的测试,应从如下三方面进行:对认定的类的测试、对构造的类层次结构的测试、对类库的支持的测试。

  1. 对认定的类的测试
    OOD 认定的类可以是 OOA 中认定的对象,也可以是对象所需要的服务的抽象,对象所具有的属性的抽象。认定的类原则上应该尽量具有基础性,这样才便于维护和重用。测试认定的类从如下几个方面考虑: 1. 是否含盖了 OOA 中所有认定的对象。 2. 是否能体现 OOA 中定义的属性。 3. 是否能实现 OOA 中定义的服务。 4. 是否对应着一个含义明确的数据抽象。 5. 是否尽可能少的依赖其他类。 6. 类中的方法是否单用途。

  2. 对构造的类层次结构的测试
    为能充分发挥面向对象的继承共享特性,OOD 的类层次结构着重体现父类和子类间一般性和特殊性。
    测试如下几个方面: 1. 类层次结构是否含盖了所有定义的类。 2. 是否能体现 OOA 中所定义的实例关联。 3. 是否能实现 OOA 中所定义的消息关联。 4. 子类是否具有父类没有的新特性。 5. 子类间的共同特性是否完全在父类中得以体现。

  3. 对类库支持的测试
    对类库的支持虽然也属于类层次结构的组织问题,但其强调的重点是再次软件开发的重用。作为对高质量类层次结构的评估,拟订测试点如下: 1. 一组子类中关于某种含义相同或基本相同的操作,是否有相同的接口(包括名字和参数表)。 2. 类中方法功能是否较单纯,相应的代码行是否较少。 3. 类的层次结构是否是深度大、宽度小。

面向对象编程的测试

典型的面向对象程序具有继承、封装和多态的新特性,这使得传统的测试策略必须有所改变。

封装是对数据的隐藏,外界只能通过被提供的操作来访问或修改数据,降低了数据被任意修改和读写的可能性,降低了传统程序中对数据非法操作的测试。

继承使得代码的重用率提高,同时也使错误传播的概率提高。

多态使得程序呈现出强大的处理能力,但同时却使得程序内“同一”函数的行为复杂化,测试时不得不考虑不同类型具体执行的代码和产生的行为。

在面向对象编程(OOP)阶段,忽略类功能实现的细则,将测试的目光集中在类功能的实现和相应的面向对象程序风格,主要体现为以下两个方面:数据成员是否满足数据封装的要求、类是否实现了要求的功能。

  1. 数据成员是否满足数据封装的要求
    数据封装是数据和数据有关的操作的集合。

检查数据成员是否满足数据封装的要求,基本原则:当改变数据成员的结构时,是否影响了类的对外接口,是否会导致相应外界必须改动。

注意:有时强制的类型转换会破坏数据的封装特性。例如程序代码:

class Hiden{private:int a=1; char *p= "hiden";}
class Visible{public:int b=2; char *q= "visible";}
…..
Hiden h;
Visible *v=(Visible *) &h;

其中,h 的数据成员可以通过 v 被随意访问。

  1. 类是否实现了要求的功能
    类所实现的功能,都是通过类的成员函数执行。测试时,应该首先保证类成员函数的正确性。

单独的看待类的成员函数,与面向过程程序中的函数或过程没有本质的区别,几乎所有传统的单元测试中所使用的方法,都可在面向对象的单元测试中使用。类成员函数间的作用和类之间的服务调用需要进行面向对象的集成测试。

着重声明:测试类的功能,不能仅满足于代码无错,而是应该以 OOD 的结果为依据,检测类提供的功能是否满足设计的要求。如果通过 OOD 的结果测试仍存在不清楚、不明确的地方,还应该参照 OOA 的结果,以 OOA 的结果为最终标准。

面向对象的单元测试

传统单元测试的对象是软件设计的最小单位-模块,依据是详细设计的描述,多采用白盒测试技术,多个模块可以并行地进行测试。

面向对象的单元测试沿用传统单元测试的概念,实际测试类的成员函数,强调对语句应该有 100%的执行代码覆盖率。

设计测试用例时,可以基于以下两个假设:

  1. 如果类成员函数对某一类输入中的一个数据正确执行,对同类中的其他输入也能正确执行。(等价类划分)
  2. 如果类成员函数对某一复杂度的输入正确执行,对更高复杂度的输入也能正确执行。例如需要选择字符串作为输入时,无须计较字符串的长度,除非字符串的长度是要求固定的,比如 IP 地址字符串。

面向对象的单元测试可以分为几个层次:

  1. 方法层次的测试
    对于一个方法,可看作一个独立函数,如果该函数的内聚性很高,功能也比较复杂,可以对其单独进行测试。一般只有少数方法需要进行单独测试,因为有很多方法与成员变量具有很强的关联性。
    常用的测试技术: 1. 等价类划分测试:根据输入参数把取值域分成若干个等价类。 2. 组合功能测试:针对那些依据输入参数和成员变量的不同取值组合而选择不同动作的方法。

  2. 类层次的测试
    很多成员方法通过成员变量产生相互依赖关系,很难对单个成员方法进行充分测试。合理的测试:将相互依赖的成员方法放在一起测试,即类层次测试。
    常用的测试技术: 1. 不变式边界测试。类层次测试的一个主要困难是成员变量的某些状态可能不会出现,即类的不变式。首先准确定义类的不变式,其次寻找成员方法的调用序列(作为测试用例)以违反类的不变式。 2. 模态类测试:模态类是指对该类所接受的成员方法的调用序列设置一定的限制。需要确定类的不同状态、每个状态下可以接受的成员方法调用。根据状态图,可以生成调用序列(作为测试用例)来覆盖状态图上的边和路径。 3. 非模态类测试:该类所接受的成员方法的调用序列没有任何限制。可以避免很多因状态引起的麻烦,但整个测试不能以状态图为指导。

  3. 类树层次的测试
    面向对象中有继承和多态现象,所以对子类的测试通常不能限定在子类中定义的成员变量和成员方法上,还要考虑父类对子类的影响。
    常用的测试技术: 1. 多态服务测试:多态服务测试是为了测试子类中的多态方法的实现是否保持了父类对该方法的规格说明。 2. 展平测试:将子类自身定义的成员方法和成员变量以及从父类和祖先类继承来的成员方法和成员变量全部放在一起组成一个新类,并对其进行测试。

面向对象的集成测试

传统的集成测试,有两种方式。

  1. 自顶向下集成:从主控模块开始,按照软件的控制层次结构,以深度优先或广度优先的策略,逐步把各个模块集成在一起。
  2. 自底向上集成:自底向上测试是从“原子”模块(即软件结构最低层的模块)开始组装测试。

面向对象软件,因为构成类的成分的直接和间接的交互,一次集成一个操作到类中经常是不可能的。主要策略:

  1. 协作集成:将可以相互协作完成特定功能的类集成在一起进行测试。
    优点:编写测试驱动和测试桩开销小。
    缺点:协作关系复杂时测试难以充分进行。

  2. 基干集成:一般系统可划分成两部分:内核部分(基干)和外围应用部分。特点:内核部分提供了系统最基本的功能和服务;外围部分以内核为基础,不能脱离内核而独自使用;内核具有很高的耦合性,并且相当复杂,试图设置桩会是相当困难且成本很高的事情;外围部分可以分为应用子系统和控制子系统,应用子系统内耦合性不大,而控制子系统内具有较高的耦合性。
    测试步骤:识别外围的应用子系统部分、控制子系统部分,基干部分;对基干中所有的组件进行一次性集成,形成基干子系统,并使用一个驱动检查经过集成的基干;对控制子系统进行自顶向下的集成;对各应用子系统进行自底向上的集成;对基干子系统、控制子系统和各应用子系统进行一次性集成形成整个系统。
    优点:集中了传统集成的优点,适合大型复杂项目。
    缺点:必须对系统的结构和相互依存性进行分析;必须开发桩模块和驱动模块;由于局部采用一次性集成策略导致有些接口可能测试不完整。

  3. 高频集成:不预测每个测试用例的预期结果,如果测试中未出现反常情况,就认为通过测试。
    主要步骤:首先,开发人员完成要提供代码的增量构件,测试人员完成相关的测试包。然后,集成测试人员将开发人员新增或修改的构件集中起来形成一个新的集成体。最后,评价测试结果。
    优点:高效性、可预测性、并行性、尽早查出错误、易进行错误定位、对桩模块需要不是必需的。
    缺点:若测试包过于简单,可能难以发现问题;开始不能平稳集成;若没有增加适当的标准可能会增加风险。

  4. 基于事件(消息)的集成:从验证消息路径的正确性出发,渐增式把系统集成在一起,从而验证系统的稳定性。

  5. 基于使用的集成:从分析类之间的依赖关系出发,首先测试那些几乎不使用服务器类的类(独立类);然后测试下一层使用独立类的类(依赖类),从对其他类依赖最少的类开始集成,逐步扩大到有依赖关系的类;一直持续到集成完整个系统。

  6. 客户机/服务器的集成:不存在独立控制轨迹,每个系统构件都有自己的控制策略。
    优点:避免了一次集成的风险;次序没有大的约束;有利于复用和扩充;支持可控制和可重复的测试。
    缺点:测试驱动和桩的开发成本高。

  7. 分布式集成:用于测试松散耦合的同级构件之间交互的稳定性。
    一般有如下几种:

    1. 类关联的多重性测试:针对类间连接测试,关注的重点是与连接关系有关的增删改操作。
    2. 受控异常测试:异常的抛出和异常的接收可以被放在不同的类中,这实际上是类间隐含的控制依赖关系。测试时需要尽可能地覆盖这些隐式的依赖关系。
    3. 往返场景测试:面向对象中,许多功能是通过多个类相互协作完成的。往返场景测试就是把与实现特定场景相联系的代码收取出来,针对这些代码设计百分之百(分支)覆盖率的测试用例集。
    4. 模态机测试:类似于类层次的模态类测试,模态机是针对多个类进行的。

面向对象的系统测试

系统测试:在实际运行时,软件是否满足用户的需要。

系统测试应该尽量搭建与用户实际使用环境相同的测试平台,保证被测系统的完整性,对临时没有的系统设备部件,也应有相应的模拟手段。

测试时,应该参考 OOA 的结果,对应描述的对象、属性和各种服务,检测软件是否能完全“再现”问题空间。

系统测试不仅是检测软件的整体行为表现,从另一个侧面看,也是对软件开发设计的再确认。

面向对象的测试总结

面向对象测试的整体目标是以最小的工作量发现最多的错误,和传统软件测试的目标是一致的。

OO 测试的策略和战术有很大不同,第一,测试的焦点从模块移向了类;第二,测试的视角扩大到了分析和设计模型。

传统和面向对象的测试方法都应该遵循的原则:

  1. 应当把“尽早和不断地测试”作为开发者的座右铭。
  2. 测试工作应该由独立的专业的软件测试机构来完成。
  3. 设计测试用例时,应该考虑到合法的输入和不合法的输入,以及各种边界条件,特殊情况下要制造极端状态和意外状态。
  4. 注意测试中的错误集中发生现象,这和程序员的编程水平和习惯有很大的关系。
  5. 对测试错误结果一定要有一个确认的过程。一般由 A 测试出来的错误,一定要有一个 B 来确认,严重的错误可以召开评审会进行讨论和分析。
  6. 制定严格的测试计划,测试时间安排尽量宽松,不要希望在极短的时间内完成一个高水平的测试。
  7. 回归测试的关联性一定要引起充分的注意,修改一个错误而引起更多错误出现的现象并不少见。
  8. 妥善保存一切测试过程文档,意义是不言而喻的,测试的重现性往往要靠测试文档。

软件运行维护

本章内容

软件正式投入运行期间的维护工作
软件维护的定义;维护策略;非结构化和结构化维护的区别;软件维护存在的问题;软件维护的过程;如何提高软件的可维护性;软件逆向工程和再工程技术。

软件维护简介

软件的运行维护阶段是软件生命周期的最后一个阶段,其基本任务是保证软件在一个相当长的时期能够正常运行。

软件维护工作量很大,大型软件维护成本高达开发成本的 4 倍左右。

软件工程的主要目的之一就是要提高软件的可维护性,减少软件维护所需要的工作量,降低软件系统的总成本。

软件维护的定义

软件维护就是在软件已经交付使用之后,为了改正错误、提高性能或满足新的需要而修改软件的过程。

主要 4 种维护活动:改正性维护、适应性维护、完善性维护和预防性维护。国外统计数据:完善性维护:5066%,改正性维护:1721%,适应性维护:18~25%,其他维护活动:4%左右。

4 种维护活动不仅要维护软件的可执行代码,还包括软件文档的维护。

  1. 改正性维护
    改正性维护是为了识别和纠正软件错误、改正软件性能上的缺陷、排除实施中的误使用,应进行的诊断和改正错误的过程。
  2. 适应性维护
    为了适应环境的变化而修改软件的活动,是既必要又经常的维护活动。例如,需要对已运行的软件进行改造,以适应网络环境或已升级改版的操作系统要求。
  3. 完善性维护
    为了满足用户新的功能与性能要求,需要修改或再开发软件,以扩充软件功能、增强软件性能、改进加工效率、提高软件的可维护性。例如,修改职工工资程序增加扣除工资功能;修改某程序增加联机在线帮助功能;调整某软件的用户操作界面;缩短某软件的应答时间等。
  4. 预防性维护
    为了改进未来的可维护性或可靠性,为了适应未来的软硬件环境的变化,或为了给未来的改进奠定更好的基础而修改软件。例如将专用报表功能改成通用报表生成功能,以适应将来报表格式的变化。预防性维护占比很小,但不应忽视,条件具备时应主动进行预防性维护,为未来的修改与调整奠定更好的基础。

软件维护的策略

软件维护活动花费的工作量占整个生存期工作量的 70%以上(工作量的比例直接反映了成本的比例)。

影响维护工作量的因素主要有 6 种:系统规模的大小;采用的程序设计语言;系统年龄的大小;数据库技术的应用水平;所采用的软件开发技术及软件开发工程化的程度;其他因素:比如应用的类型、数学模型、任务的难度、IF 嵌套层数、索引或下标数等等。

James Martin 等提出了一些策略,以控制维护成本:对于改正性维护,应用一些诸如数据库管理系统、软件开发环境、程序自动生成系统和高级(第四代)语言等新技术,可以大大提高软件的可靠性,并减少进行改正性维护的需要。

适应性维护不可避免,可以采用以下策略加以控制:

  1. 配置管理时,把硬件、操作系统和其他相关环境因素的可能变化考虑在内。
  2. 把与硬件、操作系统以及其他外围设备有关的程序归到特定的程序模块中。
  3. 使用内部程序列表、外部文件以及处理的例行程序包,可为维护时修改程序提供方便。
  4. 使用面向对象技术,增强软件系统的稳定性,易于修改和移植。

利用以上列举两类的方法,也可以减少完善性维护的工作量。此外,建立软件系统的原型,在实际系统开发之前提供给用户。用户通过研究原型,进一步完善他们的功能要求,可以减少以后完善性维护的需要。

非结构化和结构化维护的区别

  1. 非结构化维护(软件配置的唯一成分是程序代码)
    维护活动从艰苦地评价程序代码开始,常常由于程序内部文档不足而使评价更困难,对于软件结构、全程数据结构、系统接口、性能和(或)设计约束等经常会产生误解,而且对程序代码所做的改动的后果也是难于估量的。非结构化维护需要付出很大代价,这种维护方式是没有使用良好定义的方法学开发出来的软件的必然结果。

  2. 结构化维护(存在完整的软件配置)

    1. 评价设计文档,确定软件重要的结构、性能以及接口等特点
    2. 估量要求的改动将带来的影响,并且计划实施途径
    3. 修改设计并且对所做的修改进行仔细复查
    4. 编写相应的源程序代码
    5. 使用测试说明书中包含的信息进行回归测试
    6. 修改后的软件再次交付使用

软件维护存在的问题

定义和开发阶段如果无严格、科学地管理和规划,软件维护阶段必会出现问题:

  1. 理解别人写的程序通常非常困难。(仅有程序代码没有说明文档)
  2. 需要维护的软件往往没有合格的文档,或者文档资料显著不足。容易理解的并且和程序代码完全一致的文档非常有价值。
  3. 不能指望由开发人员给人们仔细说明软件。当需要解释软件时,往往原来写程序的人已经不在附近了。
  4. 绝大多数软件在设计时没有考虑将来的修改。除非使用强调模块独立原理的设计方法学,否则修改软件既困难又容易发生差错。
  5. 软件维护不是一项吸引人的工作。这种观念很大程度上是因为维护工作经常遭受挫折。

软件工程思想的设计和开发方法虽然不能保证彻底解决这些问题,但至少每个问题都可以部分地得到解决。

软件维护的时间长和成本大,是不可避免的问题。无形的代价问题:因为可用的资源必须供维护任务使用,可能会导致开发工作的延误、甚至丧失开发的良机。当看来合理的有关改错或修改的要求不能及时满足时将引起用户不满;由于维护时的改动,在软件中引入了潜伏的错误,从而降低了软件的质量;当必须把软件工程师调去从事维护工作时,将在开发过程中造成混乱。

软件维护的最后一个代价问题是生产率的大幅度下降,这种情况在维护旧程序时常常遇到。比如,如果当时没有使用软件工程方法学,原来的开发人员没有参加维护工作等等,维护代价可能会呈指数级增加。

软件维护的过程

  1. 建立维护组织
    虽然通常并不需要建立正式的维护组织,但是,即使对于一个小的软件开发团体而言,非正式地委托责任也是绝对必要的。每个维护要求都通过维护管理员转交给熟悉该产品的系统管理员去评价,系统管理员对维护任务做出评价之后,由软件变化的授权人决定应该进行的活动,由系统管理员指定一些程序技术人员作为维护人员进行维护工作。

  2. 确定维护报告
    用标准化的格式表达所有软件维护要求。(维护组织提供维护报告申请表,由申请维护的用户填写)错误:完整地说明产生错误的情况,包括输入的数据、错误清单等等。适应性或完善性的维护:简短的维护需求说明书,列出所有希望的修改。

    软件组织应根据维护申请报告制定软件修改报告:

    1. 满足维护要求表中提出的要求所需要的工作量。
    2. 维护要求的性质。
    3. 这项要求的优先次序。
    4. 与修改有关的事后数据。
  3. 规定维护事件序列

    1. 确定要求进行的维护类型
    2. 技术工作:修改软件设计、复查、必要的代码修改、单元测试和集成测试(包括使用以前的测试方案的回归测试)、验收测试和复审
    image-20251203084937470

    不同类型的维护强调的重点有所不同,但是基本途径是相同的。

  4. 保存维护记录
    做好维护记录,并长期保存,以便于评估维护技术的有效程度、维护的代价(实际工作量、费用等)以及软件产品的质量。

    首先确定哪些数据是值得记录的,Swanson 提出:程序名称、源程序语句数、机器指令条数、所使用的程序设计语言、程序安装的日期、自从安装以来程序运行的次数、自从安装以来与程序运行次数有关的故障次数、程序变动的层次和名称、因修改程序而增加的源程序语句数、因修改程序而删除的源程序语句数、每次修改耗费的人时数、修改程序的日期、软件工程师的姓名、维护申请报告表的名称、维护类型、维护开始和完成的日期、累计用于维护的人时数、维护工作带来的纯效益。

    利用这些数据构成一个维护数据库的基础内容,并利用其进行维护活动的度量和维护工作的评价。

  5. 评价维护工作
    根据维护记录中保存的数据,可以从 7 个方面度量维护活动:

    1. 每次程序运行平均失效次数。
    2. 用于每一类维护活动的总人时数。
    3. 平均每个程序、每种编程语言、每种维护类型所做的程序变动数。
    4. 维护过程中增加或删除一个源语句平均花费的人时数。
    5. 维护每种编程语言平均花费的人时数。
    6. 一张维护要求表的平均周转时间。
    7. 不同维护类型所占的百分比。

    根据度量结果,可以做出软件开发技术、编程语言选择、维护工作的时间分配、维护工作的人员分配以及其它资源分配等方面的评估,进而评价维护工作。

软件的可维护性

由于在漫长的软件运行过程中需要不断的对软件进行维修,以使其进一步完善,改正新发现的错误,适应新的环境和用户新的需求,这些修改需要花费很多精力和时间,而且有时修改不正确,还会引入新的错误。

另外,本应该严格按照软件工程的要求进行软件开发,但由于种种原因并不能完全做到,致使软件的文档和源程序难以理解、难以修改,从而造成软件难以维护,维护的工作量增大,引入新错误的可能性增加。

再者,软件维护技术并不像开发技术那样成熟和规范,自然会消耗较多的工作量,这直接影响了软件维护的成本。

因此,为了使软件能够易于维护,应该尽可能地提高软件的可维护性。

软件的可维护性定义:维护人员理解、改正、改动或改进软件的难易程度。

维护是在软件交付使用后进行的修改,修改之前必须理解待修改的对象,修改之后应该进行必要的测试,以保证所做的修改是正确的。如果是改正性维护,还必须预先进行调试以确定错误的具体位置。

决定软件可维护性的因素主要有 6 个:可理解性、可测试性、可靠性、可修改性、可移植性和可重用性

软件可维护性的主要决定因素

提高可维护性是支配软件工程方法学所有步骤的关键目标。由于软件维护的成本在软件整个生命周期中占第一位,所以如果想提高软件维护效率、降低成本,提高所开发软件的可维护性将起到至关重要的作用。

  1. 可理解性
    可理解性指读者理解软件的结构、功能、接口和内部处理过程的难易程度。
    1. 模块化:高内聚、低耦合,即每个模块完成一个相对独立的特定子功能,并且与其他模块之间的联系最简单,可明显提高可理解性。
    2. 详细的设计文档:设计文档越详细,软件的可理解性越高。
    3. 结构化设计方法:采用自顶向下逐步细化的方法进行软件设计,符合人的思维习惯,易于维护人员理解软件。
    4. 良好的程序内部文档:恰当的标识符命名、适当的注释和合理的程序视觉组织等良好的程序设计风格,提高软件的可理解性。
    5. 良好的高级程序设计语言:高级语言是面向用户的,接近于自然语言,接近于人的思维。所以选用良好的高级语言编程,可以提高软件的可理解性。
  2. 可测试性
    可测试性即软件的测试、诊断、调试的容易程度。
    1. 软件的可测试性取决于软件容易理解的程度,所以要提高软件的可理解性,这是提高软件可测试性的决定因素。
    2. 良好的文档和软件结构、可用的测试工具和调试工具、以前设计的测试过程,是提高软件可测试性的重要因素。
    3. 开发阶段用过的测试方案:软件维护人员需要得到开发阶段用过的测试方案,以便维护人员进行回归测试。
    4. 在设计阶段考虑软件的可测试性:在设计阶段应尽力把软件设计成容易测试和容易诊断的,这是提高软件可测试性的必要因素。
    5. 用程序复杂度来度量程序模块的可测试性:模块的环路复杂度越大,可执行的路径就越多,全面测试它的难度就越高。
  3. 可靠性
    可靠性是指软件按照用户的要求和设计目标,在给定的时间内正确执行的概率。
    1. 根据程序错误统计数预测软件可靠性:在进行程序测试时,统计发现的错误数量,可以估算软件的平均失效时间间隔,从而进行软件可靠性的预测。
    2. 根据程序复杂性预测软件可靠性:程序的复杂性与可靠性有直接的关系,在进行程序的复杂性度量时,可以预测哪些模块最可能发生错误,并预测可能出现的错误类型。这样,可以精准定位错误的位置并按错误类型进行快速纠正,从而提高软件的可靠性。
  4. 可修改性
    可修改性是指程序容易修改的程度。一个可修改的程序应该是可理解的、通用的、简单的。另外,软件的可修改性与软件的设计阶段采用的设计原则直接相关,比如模块的耦合和内聚程度、控制域与作用域的关系等,都影响软件的可修改性。
  5. 可移植性
    可移植性是指把程序从一种计算环境(硬件配置和操作系统)转移到另一种计算环境的难易程度。把与硬件、操作系统以及其他外部设备有关的程序代码集中放到特定的程序模块中,可以把因环境变化而必须修改的程序局限在少数程序模块中,从而降低修改的难度。
  6. 可重用性
    重用是指同一事物不做修改或稍加改动就在不同环境中多次重复使用。
    1. 可重用的软件构件在开发时都经过很严格的测试,可靠性比较高,且在每次重用过程中都会发现并清除一些错误,随着时间推移,这样的构件将变成实质上无错误的构件。因此,软件中使用的可重用构件越多,软件的可靠性越高,改正性维护需求就越少。
    2. 很容易修改可重用的软件构件使之再次应用在新环境中,因此,软件中使用的可重用构件越多,适应性和完善性维护也就越容易。

软件可维护性的影响因素

文档是软件可维护性的主要影响因素,对于维护人员理解软件起着至关重要的作用。即使是十分简单的软件系统,为了高效地维护,编制文档来描述其设计目的和内容也是非常必要的;而对于长期使用的大型软件系统而言,在使用过程中必然会经受多次修改,所以文档比程序代码更重要。

软件文档内容要求:

  1. 必须描述如何使用这个系统,没有这种描述即使最简单的系统也无法使用。
  2. 必须描述怎样安装和管理这个系统。
  3. 必须描述系统需求和设计。
  4. 必须描述系统的实现和测试,以便使系统成为可维护的。

文档可以分为用户文档和系统文档两类。

  1. 用户文档:用户文档是用户了解系统的第一步,它应该能使用户获得对系统的准确的初步印象。一般包括:软件功能描述、安装文档、用户使用手册、完整的参考手册,如果需要有系统操作员的话,还应有操作员指南。
  2. 系统文档:系统文档是指从问题定义、需求说明、系统设计、实现直到验收测试计划一系列和系统实现有关的文档。对于理解程序和维护程序来说是极其重要的。系统文档的结构和内容应该能把读者从对系统概貌的了解,引导到对系统每个方面每个特点的更形式化更具体的认识,所以每个阶段都有相应的模型和工具软件来完成形式化的描述。

软件可维护性复审

复审的重点:文档,不能准确反映软件当前状态的设计文档可能比完全没有文档更坏。如果对软件的可执行部分的修改没有及时反映在用户文档中,必然会使用户产生不满。对软件配置进行严格的复审,则可大大减少文档的问题。

需求分析复审:应该对将来要改进的部分和可能会修改的部分加以注意并指明;应该讨论软件的可移植性问题,并且考虑可能影响软件维护的系统界面。

设计复审:应该从容易修改、模块化和功能独立的目标出发,评价软件的结构和过程;设计中应该对将来可能修改的部分预作准备。

软件逆向工程和再工程

软件逆向工程

软件逆向工程又称软件反向工程,是指从可运行的程序系统出发,运用解密、反汇编、系统分析、程序理解等多种计算机技术,对软件的结构、流程、算法、代码等进行逆向拆解和分析,推导出软件产品的源代码、设计原理、结构、算法、处理过程、运行方法及相关文档等。

软件逆向工程最早是作为软件维护的一部分出现的。后来,美国研制了针对特定软件的专门用途的逆编译工具来进行软件移植,并成功转换了许多优秀软件。目前,软件逆向工程技术逐步被各国所认识,并广泛研究和应用到多个软件技术领域中。

现实中,人们并不总是完全需要逆向出目标软件的所有功能,如果那样的话将会是一个艰苦而漫长的过程。大多数情况下是意图通过对软件进行逆向,从中获取软件的算法,或破解软件及进行功能扩展等。

  1. 软件逆向工程的步骤

    1. 研究保护方法,去除保护功能。软件保护技术:如序列号保护、加密锁、反调试技术、加壳等。
    2. 反汇编目标软件,跟踪、分析代码功能。运用反汇编工具对可执行程序进行反汇编,通过动态调试与静态分析相结合,跟踪、分析软件的核心代码,理解软件的设计思路等,获取关键信息。
    3. 生成目标软件的设计思想、架构、算法等相关文档,并在此基础上设计出对目标软件进行功能扩展等的文档。
    4. 向目标软件的可执行程序中注入代码,开发出更完善的应用软件。

    软件逆向工程的优势:了解程序的结构及程序的逻辑,深入洞察程序的运行过程,分析出软件使用的协议及通信方式,并能够更加清晰地揭露软件机密的商业算法等。

  2. 软件逆向工程的应用

    1. 利用和改造现有软件形成新的软件:为避免重复劳动,提高软件生产的效率和质量,缓解软件危机,必须充分利用和改造现有软件,对现有软件进行再设计、再工程,使软件功能得到大幅提高以满足用户的需要。
    2. 将软件系统转化为易演化系统:由于系统运行环境改变,或者根据业务需要要调整其功能等,导致必须进行演化才能继续使用。系统经历多年运行,包含了众多的知识,包括系统需求、设计决策和业务规则等,逆向工程将这些系统转化为易演化系统,可以充分有效地利用这些有用资产。
    3. 研究和学习优秀软件:许多优秀软件生产厂家出于技术保护等原因没有向用户开放源代码或者不提供源代码,需要用户自己去恢复,此时对软件进行逆向工程研究是最好的方法。
    4. 作为开放源代码的前期工程:为追求利润,一些软件业霸主试图进行知识垄断,鼓励普通用户和大多数程序员把软件看成“黑箱”,不去关心软件的运行机制,把软件生产变成类似车间加工的一道道流程,隔断了人们深入研究软件科学的通路。
  3. 软件逆向工程存在的问题

    1. 缺乏统一的逆向工程概念和术语,缺乏统一的逆向工程机制的分类框架,导致研究人员之间交流的困难,限制了逆向工程工具和技术研究的进展。
    2. 逆向工程工具还不能与其他开发工具有效集成,而且缺乏对现有逆向工程工具统一的评估标准和验证工具,限制了逆向工程的应用和逆向工程技术的发展。
    3. 由于受软件知识产权保护及相关法律法规的限制,软件逆向工程并不能像其它软件技术那样公开、透明地为大家所熟知、了解和广泛交流与应用。
    4. 软件逆向工程所涉及到的技术很多,它不仅要求逆向工程人员必须熟悉如操作系统、汇编语言、加解密等相关知识,同时还要具有丰富的多种高级语言的编程经验,熟悉多种编译器的编译原理,较强的程序理解和逆向分析能力等,这些也都限制了软件逆向工程的发展。

软件再工程

软件再工程是指通过对目标系统的检查和改造,其中包括设计恢复(库存目录分析)、再文档、逆向工程、程序代码和数据重构以及正向工程等一系列活动,旨在将逆向工程、重构和正向工程组合起来,将现存系统重新构造为新的形式,以开发出质量更高、维护性更好的软件。

目的:理解已存在的软件(包括规范、设计、实现),然后对该软件重新实现以期增强它的功能,提高它的性能,或降低它的实现难度,客观上达到维持软件的现有功能并为今后新功能的加入做好准备的目标。

软件再工程的对象是某些使用中的系统(遗留系统),通常缺乏良好的设计结构和编码风格,因此,对该类软件的修改费时费力。软件再工程就是对这些系统进行分析研究,利用好的软件开发方法,重新构造一个新的目标系统,这样的系统将保持原系统所需要的功能,并使得新系统易于维护。

典型的软件再工程过程模型包含正向工程、库存目录分析、数据重构、文档重构、代码重构、逆向工程等活动。在某些情况下这些活动以线性顺序发生,但也并非总是这样。例如,为了理解某个程序的内部工作原理,可能在文档重构开始之前必须先进行逆向工程。

  1. 库存目录分析
    每个软件组织都应该保存其拥有的所有应用系统的库存目录。该目录包含关于每个应用系统的基本信息(例如应用系统的名字,最初构建它的日期,已做过的实质性修改次数,过去 18 个月报告的错误,用户数量,安装它的机器数量,它的复杂程度,文档质量,整体可维护性等级,预期寿命,在未来 36 个月内的预期修改次数,业务重要程度等)。

    仔细分析库存目录,按照业务重要程度、寿命、当前可维护性、预期的修改次数等标准,把库中的应用系统排序,从中选出再工程的候选者,然后明智地分配再工程所需要的资源。

  2. 文档重构
    老程序固有的特点是缺乏文档。以下三种情况的老程序可以分别作处理:

    1. 因为建立文档非常耗费时间,不可能为数百个程序都重新建立文档。如果程序相对稳定,正在走向其有用生命的终点,可能不会再经历什么变化,保持现状是一个明智的选择。
    2. 为了便于今后的维护,必须建立文档,但是由于资源有限,应采用“使用时建文档”的方法。并不是一下子就建立起整个软件的全部文档,而是只建立当前正在修改的部分的文档,随着再工程的不断进行,文档会不断丰富和完整。
    3. 如果某软件是完成业务工作的关键,而且必须重构全部文档,则仍然应该设法把文档工作减少到必需的最小量。
  3. 逆向工程
    逆向工程是一个恢复设计结果的过程,利用逆向工程工具从现存的程序代码中抽取有关数据、体系结构和处理过程的设计信息。

  4. 代码重构
    某些老程序具有比较完整、合理的体系结构,但是,个体模块的编码方式却是难以理解、测试和维护的。在这种情况下,可以重构可疑模块的代码。

    首先,用重构工具分析源代码,标注出和结构化程序设计概念相违背的部分;然后,重构有问题的代码;最后,复审和测试生成的重构代码,以保证没有引入异常,并更新代码文档。

  5. 数据重构
    数据重构是一种全范围的再工程活动,即对数据的修改必然会导致体系结构或代码层的改变。在大多数情况下,数据重构始于逆向工程活动,分解当前使用的数据体系结构,必要时定义数据模型,标识数据对象和属性,并从软件质量的角度复审现存的数据结构。

    当数据结构较差时应该对数据进行再工程,例如,如果采用关系数据模型可大大简化处理的情况下却使用了一般的文件系统来实现。

  6. 正向工程
    正向工程也称为革新或改造,不仅从现有程序中恢复设计信息,而且使用该信息去改变或重构现有系统,以提高其整体质量;还可能加入新功能和提高软件的整体性能。

编写格式

type 属性

在题头部分,type 属性有特殊作用,一般会被识别成特定的页面,比如使用 hexo new page tags 的时候,就会在 source 下生成一个 tags 目录和一个 index.md 文件,此时只需要在 index.md 页面的题头部分编写 type: tags 的话,那么该页面就会对所有文章的标签进行汇总和整理,对应 menu 的 /tags 页面。

---
title: tags
date: 2025-11-28 15:30:11
type: tags
---

categoreis 也是如此

categories 和 tags

一般在文章的题头部分,会编写文章的相关信息,比如编写日期、作者、分类、标签之类,那么应该如何写呢?

tags 的写法比较简单,有两种:

# 方式一,使用 yaml 的列表形式
tags:
- t1
- t2

# 方式二,使用 js 的数组形式(比较直观,更推荐这种)
tags: [t1, t2]

categories 有三种:

# 1. 单分类,文章会被归类到 c1 这个类别
categories: c1

# 2. 嵌套分类(两种写法),文章会被归类到 c1/c2 这个类别中,相当于一个大类别中的小类别
categories: [c1, c2]
categories:
- c1
- c2

# 3. 多标签分类,文章会被同时归类到 c1、c2 和 c/c3 的类别中
categories:
- [c1]
- [c2]
- [c, c3]

Hexo 插件

存在图片 404 的问题?可以按照如下步骤解决:

  1. _config.yaml 中,将 post_asset_folder 设置为 true
  2. 安装 npm install hexo-image-link --save 依赖
  3. 确保图片所在的位置在与文档同级的同名目录下:./${filename}/xxx.png

全局配置

代理

有时候我们即使开了 VPN ,git 相关操作好像还是会很慢,这大概率就是因为本地的 git 还没有配置代理,可以通过下面的命令来设置:

# 使用 socks5 代理
git config --global http.proxy 'socks5://127.0.0.1:7890'
git config --global https.proxy 'socks5://127.0.0.1:7890'

# 使用 http 代理
git config --global http.proxy 'http://127.0.0.1:7890'
git config --global https.proxy 'http://127.0.0.1:7890'

# 验证配置
git config --global --get http.proxy
git config --global --get https.proxy

# 取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy
0%