概述
第1章 软件开发概述
1.1 程序与软件
1.1.1 从程序到软件
1、软件
软件是有计算机程序和程序设计的概念发展演化而来,是程序和程序设计发展到一定规模后并且再逐步商品化的过程中形成的。
2、计算机程序的工程性和使用价值
- 计算机程序具有复制价值
- 计算机程序的生成是一种有多人合作、经历不同阶段的开发,且具有可复制和重复使用的器或件
3、计算机程序
(1)计算机程序简称程序,是为了解决某个特定问题而用程序设计语言描述的适合计算机处理的语句序列。
(2)软件是能够完成预定功能和性能的可执行的程序和使程序正常执行所需要的数据,加上描述软件开发过程及其管理、程序的操作和使用的有关文档,即“软件=程序+数据+文档”。
4、举例
教学管理系统。
1.1.2 软件类型
1、按功能划分
- 系统软件:如计算机操作系统、设备驱动程序、通信处理程序、网络管理程序。
- 支撑软件:如IDE、编译程序、文件格式化程序、DBMS、应用框架与程序库。
- 应用软件:包括商业数据处理软件、工程与科学计算机软件、管理信息系统、办公自动化软件、计算机辅助教学软件、游戏娱乐类软件、社交通信类软件等。
2、按各种方式划分
- 实时处理软件
- 分时软件
- 交互式软件
- 批处理软件
3、按服务对象范围划分
- 项目软件
- 产品软件
4、其他分类
- 商业软件
- 开源软件
- 共享软件
5、软件开发
随着软件变得越来越大、越来越复杂,软件开发的关注点也发生了变化,相对于小规模的程序设计,提出了大规模的程序设计,即软件开发。
1.1.3 程序设计与软件开发
1、计算机程序的两种形式
- 可执行程序
- 可读源代码
2、程序设计
以某种程序设计语言为工具,编写源程序,然后由编译系统完成可执行代码的转换。
3、程序设计活动
程序设计活动包括分析、设计、编码、测试、排错等。
4、程序=算法+数据结构
数据结构指程序处理或应用的数据之间的逻辑关系。算法是指解决特定问题的步骤和方法。程序设计的核心是选择和设计适合特定问题的数据结构和算法,用编程语言编制为程序。
5、软件工程
随着计算机技术的发展,软件规模变得越来越大,软件已经不可能仅仅依靠个人才能去编写与开发了,而是需要团队。程序设计活动走向软件工程,软件工程把经时间考验而证明正确的管理技术和当前能够得到的最好技术方法结合起来,以系统性的、规范化的、可定量的过程化方法去开发和维护。
1.2 软件生存周期
1.2.1 使用角度的软件生存周期
从用户角度可分为三个阶段:
1、提出需求
根据用户需求,提出要解决的问题和需要的软件。
2、获取软件
对获取软件的最佳途径做出决策并选择最佳的供应商。软件的获取有三种主要途径:
- 购买软件
- 定制或开发软件
- 租凭软件或租凭服务
3、使用软件
一旦获得软件之后,用户操作软件使之为其服务。
1.2.2 开发角度的软件生存周期
一般分为:定义软件、开发软件和维护软件。
1、需求定义
- 功能性需求:定义软件再抽象级别应该提供的基本功能。
- 非功能性需求(特性需求):软件应该具备的特性,如可用性、性能、安全性、可靠性等。
- 需求定义阶段一个重要部分是建立一组系统应该满足的总体目标。
2、软件设计
如何实现需求的决策和方案,是将系统功能分配到不同组成元素的过程,包括一组活动:
- 划分需求:对需求进行分析并合并成相关的组。
- 确定子系统:识别出独立或集体满足需求的子系统。
- 给子系统分配需求:原则上只要子系统的识别是需求划分驱动的,这个过程可省略。但在实践过程中,需求划分和子系统匹配从来都是不完全匹配的,使用外部系统或库函数可能会要求更改需求。
- 定义子系统:确定每个子系统或模块的特殊功能。
- 定义子系统的接口:定义每个子系统提供的或需要的接口。
经典的软件工程将软件设计分为:
- 概要设计
- 详细设计
3、软件实现
完成可运行程序及数据的软件开发过程。
4、软件维护
对已完成开发并发布、交付使用的软件产品进行完善、纠正错误、改进性能和其他属性,或使软件使用改变了的环境。软件维护分为四种类型:
- 改正性维护
- 适应性维护
- 完善性维护
- 预防性维护
1.3 软件开发过程
1.3.1 瀑布式开发过程
1、瀑布式开发过程
也叫软件生存期模型。它按照软件生命周期,把开发分为制定计划、需求分析、软件设计、程序编写、软件测试和运行维护这6个基本活动,并且规定了它们自上而下、相互衔接的固定次序,如同瀑布流水,逐级下落。
2、核心思想
按工序将问题化简,将功能的实现与设计分开,便于分工协作,即采用结构化的分析与设计方法将逻辑实现与物理实现分开。
3、特性
强调文档的作用,并要求每个阶段都要仔细验证。
4、存在的问题
- 阶段划分僵硬,每个阶段不能缺省,而且产生了大量文档,增加了工作量。
- 开发是线性的,只有等到整个过程的末期才能见到开发成果——可运行软件,不利于快速响应变化的需求。
- 早期的错误要等到开发后期的测试阶段才能发现,可能带来严重的后果,增加了开发的风险。
1.3.2 增量开发模型
1、增量开发
增量开发特指待开发的软件不是一次就能完成,而是把软件分成一系列增量,完成一部分就交付一部分。对每个增量的使用和评估都作为下一个增量发布的新特性和功能。这个过程在每个增量发布后不断重复,直到产生了最终的完善产品。
2、基本思想
让开发者能够从早期的开发、系统的增量、交付的版本中学到经验。从系统的开发和使用中学习一切可能学到的东西。过程中的关键是从系统需求的简单子集实现开始,通过迭代增强和进化后续版本,直到系统被实现。每次迭代中,对设计进行修改,并增加新的功能要求。
3、特性
引进了包的概念,无需等到所有需求明确,只要某个需求明确了,就可以进行开发。
4、优点
- 短时间内交付一个可运行软件,解决一些急用功能。
- 每次只交付一部分,用户有较充分的时间学习和适应新的产品。
- 灵活使用需求的变化。
- 有利于系统维护。
5、存在的风险
- 由于各部件是逐步并入已有软件,必须确定每次增加的部件不破坏已构建好的系统,这需要软件具备开放式的系统结构,否则系统将失去稳定的结构。
- 逐步增加不见的方式,很容易退化成边做边改模型,从而使软件过程的控制失去整体性。
- 如何一致的定义“增量”?如何界定它的工作量、需求范围、功能或特性?
1.3.3 个体软件过程
1、PSP
- PSP是一种可用于控制、管理和改进个人工作方式的自我持续改进过程。
- 它是一个包括软件开发表格、指南和规程的结构化框架。
- PSP 与具体的技术(程序设计语言、工具或设计方法)相对独立,其原则能够应用到任何软件工程任务之中。
- PSP能够说明个体软件过程的原则。
- 帮助软件工程师做出准确的计划。
- 确定软件工程师为改善产品质量要采取的步骤。
- 建立度量个体软件过程改善的基准。
- 确定过程的改变对软件工程师能力的影响。
2、个体软件过程
- PSP0的目的是建立个体过程基线,学会使用PSP的各种表格采集过程的有关数据,执行的软件过程包括计划、设计、编码、编译和测试。按照选定的缺陷类型标准、度量引入的缺陷个数和排除的缺陷个数等,用在测量PSP过程的改进。PSP0.1增加了编码标准、程序规模度量和过程改善建议三个关键过程域。
- PSP1的重点是个体计划,用自己的历史数据来预测新程序的大小和需要的开发时间,并使用线性回归方法计算估计参数,确定置信区间以评价预测的可信程度。PSP1.1增加了对任务和进度的规划。在PSP1阶段应该学会编制项目开发计划,这不仅对承担大型软件的开发十分重要,即使是开发小型软件,也必不可少。
- PSP2的重点是个体质量管理,根据程序的缺陷建立检测表,按照检测表进行设计复查和代码复查(也称“代码走查”),以便及早发现缺陷,使修复缺陷的代价最小。PSP2.1则论述设计过程和设计模板,但并不强调选用什么设计方法,而强调设计完备性准则和设计验证技术。
- PSP3的目标是把个体开发小程序所能达到的生产效率和生产质量延伸到大型程序;其方法是采用迭代增量式开发方法,首先把大型程序分解成小的模块,然后对每个模块按照PSP2.1所描述的过程进行开发,最后把这些模块逐步集成为完整的软件产品。在新一轮开发循环中,可以采用回归测试。
1.4 敏捷开发
1.4.1 概述
1、价值观和基本原则
(1)4个核心价值观
- 个体和互动胜过流程和工具。
- 工作的软件胜过详尽的文档。
- 客户合作胜过合同谈判。
- 响应变化胜过遵循计划。
(2)12条原则
- 最优先要做的是通过尽早地、持续地交付有价值的软件满足客户需要。
- 即使在开发后期也欢迎需求的变化,敏捷过程利用变化为客户创造竞争优势。
- 经常交付可以工作的软件,从几星期到几个月,时间越短越好。
- 业务人员和开发人员应该在整个项目过程中始终朝夕在一起工作
- 要善于激励项目人员,给他们以所需要的环境和支持,并相信他们能够完成任务。
- 在开发小组中最有效率、也最有效果的信息传达方式是面对面的交谈。
- 工作的软件是进度的主要度量标准。
- 责任人、开发者和用户应该维持长期、恒等的开发节奏。
- 对卓越技术与良好设计的不断追求将有助于提高敏捷性。
- 简单——尽可能减少工作量的艺术——至关重要。
- 最好的架构、需求和设计都源于自组织的团队。
- 每隔一定时间,团队都要总结、反省工作效率,然后相应地调整自己的行为。
2、基本技术
- 敏捷方法可以视为一些最佳实践的集合,包括经典的软件开发技术和管理,也包括敏捷开发首创的技术和方法。
- 敏捷开发遵循软件开发的基本原则,同时也总结出了11条面向对象设计的原则,如单一职责原则(模块内聚的体现)、(采纳的)Liskov替换原则等。
- 敏捷开发主要采用了面向对象的开发技术,使用CRC卡( Class-Responsibility-Collaborator,类-责任-协作)、用户用例、设计模式及UML ( Unified ModellingLanguage,统一建模语言)。
- 使用UML 的符号主要是类图和时序图,这两种符号有助于直接编写出代码。
- 敏捷开发增强和推广了一些经典的实践,如意图导向编程,指的是先假设当前这个对象中已经有了一个理想方法,它可以准确无误地完成想做的事情,而不是直接盯着每一点要求来编写代码。
3、敏捷技术
- 结对编程:两个程序员在一个计算机上共同工作。一个人输入代码,另一个人审查他输入的每一行代码。两个程序员经常互换角色。
- 代码重构:指的是改变程序结构而不改变其行为,以便提高代码的可读性、易修改性等。例如,给变量重新命名,把一段代码提升为函数,把公共的属性和行为抽象成基类。
- 测试驱动开发:在一个微循环开发中,首先确认并自动化进行一个失败的测试,然后编写足够的代码通过测试,在下一轮前重构代码。
- 持续集成:微软等公司的软件开发方法包括每日构造产品,持续集成比它更进一步,只要可能,就把新代码或变更的代码合并到应用程序,然后测试,确保每一步的工作都正确。
1.4.2 Scrum方法
1、3个角色
- 产品负责人
- 产品经理
- 团队
2、3个工件
- 产品冲压工作
- 冲刺清单
- 燃尽图
3、5个活动
- 冲刺计划会议
- 每日站会
- 冲刺评审会议
- 冲刺回顾会议
- 产品积压工作梳理会议
4、5个价值
- 承诺
- 专注
- 开放
- 尊重
- 勇气
1.5 软件构造
1.5.1 有关概念
通过程序设计(Programming)、编码(Coding)得到程序、语句(段),书写(Write)文档或程序,设计(Design)包括软件及其构件、类、函数、算法、数据结构等的软件工件,也通过开发(develope)得到软件或程序。伴随着程序成为软件、作为产品或系统,程序员或软件开发者使用可复用技术、集成技术等把一个软件的不同组成部分按照一定的结构、通过一系列步骤组装(Assemble)成可运行的软件。建造(Build)与程序的编译有关,它可以把一个或一组源程序文件翻译成可执行的指令序列;也可以把构成一个软件的所有源程序文件、配置文件、数据文件及它们需要的库文件等,按照一定的顺序编译并连接成一个可运行文件。Meyer:面向对象软件构造是一种软件开发方法,是运用面向对象技术开发具有结构的软件系统,其结构组成是类;类可以立即实现,也可以延迟实现(从而具有动态性)。因而,软件具有房屋建造的特点,是使用了预制的、可复用的建造部件,按照(设计的)结构和流程而完成的产品。
1.5.2 构造与开发过程
- 构造在不同软件开发流程或模型中的地位也不一样。有些流程更加重视构造。
- 从构造角度看,有些模型侧重于线性化过程——比如瀑布模型、阶段交付的生命周期模型。线性化开发方式更加重视构造之前的活动(需求和设计),并且在这些活动之间建立明确的任务划分。在这些模型中,构造工作主要就是编码。
- 有些模型是迭代的——如Scrum、极限编程、进化式原型法。这些方式倾向于把构造视为与其他软件开发(包括需求、设计和计划)同时发生或重叠的活动。这些方式混合设计、编码和测试活动,把构造当成这些活动的集合体。
- 如何考虑构造,在某种程度上依赖于采用的生存周期模型。一般地说,软件构造最主要是编码和调试,但也可以包含工作计划、详细设计、单元测试、集成测试,以及其他活动。
1.5.3 主要内容
- 软件构造基础
- 管理构造
- 实际考虑
- 构造技术
- 软件构造工具
1.5.4 软件构造的重要性
- 占据了软件开发的大部分工作(30%~80%的工作时间)
- 是软件开发的中心活动
- 重心放在构造上,能显著提升个体程序员的生产率
- 构造的产品——程序源代码,常常是唯一标准的软件描述
- 构造是确保唯一要完成的活动
1.6 为什么不直接编写软件
1.6.1 软件开发语言
1、按计算模型划分
- 命令式语言
- 冯·诺依曼式语言
- 脚本语言
- 面向对象语言
- 声明式语言
- 函数式语言
- 逻辑式语言
- 高级程序语言
2、按执行模式划分
高级语言的程序不能直接上计算机运行,需要转换成低级语言的指令后才能运行。
- 编译型语言:C、C++、Ada
- 解释型语言:Python、Basic及脚本语言等
1.6.2 编程工具与集成化开发环境
- 编程的编程工具——软件开发工具包(Software DevelopmentKit,SDK),它通常包括编译程序或解释程序、调试程序、连接程序等,如Java的JDK、Android 的ADK。编写程序的工具是普通的文本编辑器,它可以是传统的正文行编辑器,也可以是面向全屏的图形编辑器。编辑器可以是通用的、与程序语言无关,也可以是具备源程序语言知识的语法制导编辑器或结构化编辑器(如emacs、notepad++)。
- 软件开发还需要管理各种代码文件、检查程序质量、测试工具、管理bugs、软件打包工具等。这些基础开发工具通常是行式命令,直接在操作系统中输入相应的命令,如编译Java程序的编译命令javac,解释执行Java程序的命令java。也有一些语言直接提供可视化开发工具。
- 可视化集成开发环境IDE,用图形用户界面(Graphical UserInterface , GUI)集成了代码编写、静态分析、编译、调试、连接、打包等功能的一体化软件开发套件。支持多种语言进行编程,还提供代码管理、代码分析、软件维护、软件测试及软件部署和交付等工具,同时支持多种形态应用软件(通用程序、Web应用、数据库应用、移动应用)的开发。如Delphi、Visual Studio、Eclipse和Netbeans。
1.6.3 软件运行环境
1、软件运行环境
软件运行环境,广义上说,是一个软件运行所要求的各种条件,包括软件环境和硬件环境。许多应用软件不仅仅要求特定的硬件条件,还对软件提出明确的支撑条件。操作系统将计算机的硬件细节屏蔽,将计算机抽象成虚拟资源。通常把计算机硬件和操作系统称为平台。
2、虚拟机
- 为了能够使同一种编程语言的程序独立于操作系统,实现程序运行的独立性,即“一次编写程序、到处运行”,在操作系统层面提出并出现了语言虚拟机或运行容器。它为程序的运行提供所需的运行时资源,包括把程序翻译成计算机指令、分配内存、通过操作系统调用计算资源等。
- 例如,Java 虚拟机 (Java Virtual Machine ,JVM)可以理解成一台运行Java程序的抽象的计算机。
3、支撑环境
- 使用的数据库管理系统,如Oracle、MySQL、SQL Server。
- Web 服务器,如Apache服务器、Tomcat服务器
- 应用框架,如.NET Framework、Java程序的SSH 框架、Web 应用框架Ruby on Rails。
- 第三方程序库、APIs,如大数据分析与处理的Panda,人工智能的TensorFlow, Caffe、Torch,等。
1.6.4 软件开发的最佳实践
1、最佳实践
- 用户满意的、可以反复使用的软件开发的一切手段
- 最佳实践认为存在某种技术、方法、过程、活动或机制可以使生产或管理实践的结果达到最优,并减少出错的可能性。
2、原则
一个已经接受或专业化的指导行动的最高准则或标准。人们总结、使用了软件开发的基本原则、面向对象原则等。原则必须通过某种途径体现出来,才具有指导作用。
3、机制
指的是有机体的构造、功能及其相互关系、工作原理,如可视化编程的事件响应机制、类型的多态机制。
4、技术
是科学原理的应用,是具有技能特点的特殊的步骤或途径。软件开发技术是运用了计算机科学、数学、系统科学、管理科学的基本原理,进行软件开发的方式方法。
5、方法
是获得一个客体(对象)的步骤或过程。作为一个系统的步骤、技术活动被特定的专业或艺术采纳,是技能或技术的全部。研究方法及其知识的活动称为方法学,如面向对象方法学
6、工具
工具指的是执行操作的器具,引申为为达到、完成或促进某一事物的手段。我们使用更加广泛的含义,软件工具指的是从编辑器、编译器、自动化测试框架到IDE的实用程序。
1.6.5 开发过程与管理
软件开发不像计算机、手机的制造等可以使用机器设备进行大规模的自动化生产。软件开发主要是人的智力活动,而且很多时候是一群人的开发活动。
- 首先,软件开发是做出决策、权衡和选择的过程。
- 对于人员管理,首先要识别出参与软件开发、与软件开发相关、受软件影响的人员,并分析每个相关人员对软件的诉求、期望、影响和作用。
- 软件开发作为项目要评估成本、质量、人员等因素,预先做出项目计划。
第2章 模块化软件构造
2.1 分解与模块化
2.1.1 分解的含义
- 把问题分解成两个或多个更小的问题
- 分别解决每个小问题
- 然后把各个小问题的解答结合起来,即可得到原问题的解答。
2.1.2 模块化与结构化
1、模块化
(1)模块化
- 模块化是把问题分解成容易理解、便于控制、便于实现的子问题的一个重要手段,是实现控制复杂性的方式。
- 模块化把一个程序分解成简单独立、互相作用的模块,对不同的模块设定不同的功能,来实现大型、复杂的程序。
- 在程序系统的结构中,模块是可组合、可更换的程序单元。
- 良好的模块设计只完成一个特定的或一组相关的子功能。
- 所有模块按某种方式组装起来,成为一个整体,完成整个系统所要求的功能。
(2)软件模块
- 具有相对独立性、由数据说明、执行语句等程序对象构成的代码集合。
- 程序中的每个模块都需要单独命名,通过名字可实现对指定模块的访问。
- 一个模块具有输入/输出(接口)、功能、内部数据、程序代码4个特征。
(3)模块的三大特征
- 独立性:可以对模块单独进行设计、编码、调式、修改和存储。
- 互换性:模块具有标准化的接口,容易实现模块间的互换。
- 通用性:有利于实现系列产品间的模块的通用,实现跨系列产品间的模块的通用。
(4)模块化的好处
- 有助于了解软件设计,使结构清晰,容易阅读和理解。
- 使软件容易测试和调试。
- 提高软件的可修改性。
- 有助于软件开发工程的组织管理,一个复杂的大型程序可以有许多程序员分工编写不同的模块,并且可以进一步分配技术熟练的程序员编写困难的模块。
2、结构化
- 图灵奖获得者Wirth提出的“结构化程序设计”(Structured Programming)的方法,可以简化为“算法+数据结构=程序”。
- 该方法的重点是:不要求一步就编写成可执行的程序,而是分若干步进行,逐步求精。
- 第一步编写出的程序抽象程度最高,第二步编出的程序抽象程度有所降低……最后编出的程序即为可执行的程序。
- 优点:使程序易读、易写、易调试、易维护,也易于保证程序的正确性及验证其正确性。
- 这种结构化设计方法又称“自顶向下”或“逐步求精”法,在程序设计领域引发了一场革命,成为程序开发的一个标准方法。
2.2 数据结构与算法
2.2.1 数据结构与算法的关系
1、数据结构
- 数据结构是计算机存储、组织数据的方式,是指相互之间存在的一种或多种特定关系的数据元素的集合。
- 数据结构为数据集提供独立于计算机内存的数据组织,并提供被视为一种抽象工具来访问。
- 常见的数据结构有数组、集合、栈、队列、堆、树、图、散列表等。
2、计算机算法
- 计算机算法以一步一步的方式来详细描述计算机如何将输入转化为所要求的输出的过程。
- 描述算法的方式可以采用自然语言、程序设计语言,也可以两种语言混合使用。
- 用计算机程序语言实现并在计算机上运行的算法就是程序,它是一个解决实际问题方法的程序语言的指令序列。
- 基本的算法类型包括查找(顺序查找、二分查找)、排序(冒泡排序、快速排序、插入排序、归并排序等)、二叉树的遍历(前序遍历、中序遍历、后序遍历)、图的遍历(广度优先遍历、深度优先遍历)、最短路径算法。
3、两者关系
- 一种数据结构、一种算法:如计算树的高度、树节点的层级。
- 一种数据结构、多种算法:数组支持的算法有排序算法、查找算法、图类算法、矩阵类算法等。
- 多种数据结构、一种算法:折半查找,使用的基本数据结构有数组、二叉树,也可以使用链表。
- 多种数据结构、多种算法:针对数据结构数组和二叉树,基本的算法有遍历类、查找类、求最大值。
2.2.2 选择与设计数据结构
1、算式与习题的基本数据结构
(1)设计1:
包含两个运算数,一个运算符及结果的数据结构
typdef struct equation{
unsigned short int left_operand, right_operand;
char operator;
unsigned short int value;
}
需要注意:
1)命名:见名知意
2)一个结构类型是否包含非独立变量,应该考虑下列因素:
- 获取非独立变量值的难易程度:比较简单的话不需要在结构体中定义,比较复杂,消耗资源则可用一个变量记录该值。
- 使用非独立变量的频繁程度:使用频繁且值比较稳定,考虑包含非独立变量;否则不包含。
(2)设计2
用一个数组[operand,operand2,operator]表示算式Equation。
2、比较
3、算法分析与其它数据结构
- 算法分析:产生有正整数n个不同算式的习题,算法的复杂度是O(n^2)。
- 其它数据结构:集合
2.2.3 选择与设计算法
1、习题与算式的分离
把习题和算式明确地从代码中抽出,并分别用合适的数据结构表示,有助于各自的设计与实现,也能实现不同的算式和习题的任意组合。
2、算式产生与其约束条件的分离
分别定义运算数生成函数与约束条件检测函数,对满足一定条件的运算数才生成算式。这样,约束条件的任意变换都不影响算式生产函数,也支持用户灵活设置约束条件。
3、加减法算式的分离
便于生产混合运算。
2.3 模块化设计理论初步
2.3.1 模块化原则
1、Meyer提出了5条标准来评价一种设计方法是否定义了有效的模块系统能力:
- 可分解性
- 可组装性
- 可理解性
- 连续性
- 保护性
2、模块的独立程度可以由两个定性标准来度量:
- 内聚:衡量一个模块内部各个元素之间相互结合的紧密程度
- 耦合:衡量不同模块彼此之间相互依赖(连接)的紧密程度
2.3.2 模块的内聚性
内聚性越高,相对地,它与其它模块之间的耦合度就会降低,模块越独立。内具有7种,由弱到强排列如下:
- 偶然内聚
- 逻辑内聚
- 时间内聚
- 过程内聚
- 通信内聚
- 顺序内聚
- 功能内聚
2.3.3 模块间的耦合性
开发中尽量追求松耦合,耦合度从低到高可分为7级:
- 非直接耦合
- 数据耦合
- 标记耦合
- 控制耦合
- 外部耦合
- 公共耦合
- 内容耦合
2.4 测试程序
2.4.1 测试需求
- 首要任务就是分析用户需求与设计,梳理含糊不清、模棱两可、互相矛盾的需求,明确、细化和罗列出需求,并且将每个需求表示成可以检测的测试需求。
- 其次,测试需求要求程序的预期结果和实际运行结果都要明确、合理、可观察并可比较。
- 找到隐含需求或隐含的不确定因素也需要进行测试。
2.4.2 测试设计与测试用例
测试设计包括测试用例的设计,此外还包括是否实施所有层次的测试,是否采用测试工具或自动化测试框架、哪些测试采用哪些工具,如何组织人员等等。
程序的运行结果可以分成以下三类:
- 产生的值
- 状态变化
- 必须一起解释为输出才有效的一个序列或一组值
测试数据全部通过,说明待测试程序在一定程度上满足需求或功能要求。测试结果的判定可能会存在误判和漏判。
待测程序使用测试用例的3种方式:
- 程序员每次从键盘输入一个测试数据,观察测试结果并和预期值比较,记录测试通过与否。
- 程序员通过编写测试程序,先存储测试用例,然后让待测程序逐个读取测试数据、运行、比较预期结果,同时记录测试结果。
- 使用测试工具完成测试程序的操作及其他更多操作。
2.5 调式测试
2.5.1 缺陷的相关术语
软件Bug的准确术语是缺陷(Defect),就是软件产品中所存在的问题,最终表现为用户所需要的功能没用完全实现,不能满足或不能全部满足用户的需求。从产品内部看,软件缺陷是软件产品开发或维护过程中所存在的错误、毛病等各种问题;从产品外部看,软件缺陷是系统所需要实现的某种功能的失效或违背。
软件缺陷源自人的过失活动产生的不正确结果,导致在软件产品、模块中出现了缺陷或故障。
所谓错误就是导致不正确结果的全部。它展示了某个故障的不正确的内部状态。可以理解Bug是程序中引起错误的具体位置,因此,debug就是找出并更改程序中的错误。
2.5.2 调式基础
- 科学的调式过程
- 定位程序缺陷
- 更正缺陷
2.6 讨论与提高
2.6.1 软件质量
ISO定义的6个独立的质量特性:
- 功能性:程序是否满足了用户需求
- 可靠性:程序保持规定的性能水平的能力
- 可用性:程序有多容易使用
- 效率:与程序运行时消耗的物理资源有关
- 可维护性:是否容易修改程序
- 可移植性:是否容易把程序移植到一个新的环境
2.6.2 软件测试的其它观点
- 正面观:证明软件是正确的。
- 负面观:证明程序有错误。
- 风险角度:对软件系统中潜在的各种风险进行评估的各种活动。
- 经济角度:以最小的代价获得高质量的软件。
2.6.3 测试设计
1、覆盖测试
(1)覆盖测试
测试所包含的软件的特征、元素、成分等方面的程度或范围。
(2)原则
- 多覆盖域原则:满足一个测试覆盖不能为软件的正确程度提供充足的保证。
- 测试覆盖原则:度量测试覆盖率并针对不断增强的覆盖率来改进测试数据,就能改进待测软件。
2、基于等价类划分的测试
等价类划分的两种不同情况:
- 有效等价类
- 无效等价类
划分原则:
- 按区间划分
- 按数据集合划分
- 按限制条件划分
- 按限制规则划分
- 按输入方式划分
- 细分等价类
3、基于边界值分析的测试
基本策略
- 若输入条件指定了以a和b为边界的范围,则测试数据应该包括a,b,略大于a,略小于b,刚刚小于a,刚刚大于b的值作为非法的测试数据。
- 若输入条件规定了输入值的个数,则用最大个数,最小个数,比最小个数少一,比最大个数多个的数作为测试数据。
- 如果程序的规格中说明给出的输入域或输出域是有序集合,则应该选取集合的第一个元素和最后一个元素作为测试用例。
- 如果程序中使用了一个内部数据结构,则应该选择这个内部数据结构的边界上的值作为测试用例。
- 分析用户需求和软件设计,找出其它可能的边界条件。
2.6.4 编程风格
- 标识符的命名域使用
- 注释
- 排版与布局:一致、符合常规、简明
第3章 面向对象的软件构造
3.1 抽象域封装
3.1.1 模块产生与合成
- 函数簇包含了对核心数据的产生、变更和使用的操作函数。函数簇与其之外的其他函数通常不能产生、变更函数簇内的核心数据,仅仅使用。从模块化理解,一个函数簇实现了一组围绕核心数据的功能,具备通信内聚和功能内聚,属于强内聚。函数簇与其他函数、函数簇、模块的连接方式主要是数据的传递及对理解函数簇数据的函数的调用。
- 局部化,如函数和复合语句的内部变量,是实现模块化的一种重要机制。函数调用可以视为模块组合的一种机制。通过函数调用把函数联系起来,构成更大规模的程序。
- 数据结构,如数组或C的结构体为组成更大、更复杂的数据提供了构建具有层次结构的组织方式。单纯的数据结构不含对数据的操作,也不能保护其中的数据元素。可以在数据结构和数据类型的基础上实现模块化机制,这就需要抽象与封装。
3.1.2 抽象与封装
1、抽象
(1)抽象
- 抽象是指对一个过程或者一件事物的某些细节有目的地隐藏,以便把其他方面、细节或结构表达的更清楚。
- 抽象是从众多的事务中抽取共同的、本质性的特征,而舍弃非本质的特征。
- 进行抽象的主要目的是希望通过把相关的属性和其它不相关的属性分开,分离关注点。
- 抽象是处理复杂问题的一个手段。
- 抽象是分离对象特性、限制对它们在当前环境关注的一个机制。
- 抽象的使用者不必理解所有细节就可以使用对象,而仅仅理解对当前任务或问题的相关。
(2)分类
- 过程抽象:使用一个函数或方法时知道它是干什么,而不知道它是如何完成的。
- 数据抽象:将一个数据类型的特征预期实现分离。
2、封装
(1)两个含义
- 把描述一个事务的性质和行为结合在一起成为构件,对外形成该事务的一个界限,封装能够集中而完整地对应并描述具体的事物,体现了事务相对独立性。
- 信息隐蔽,及外界不能直接存取构件的内部信息(属性)及隐藏起来的内部操作,外部也不必知道操作的内部实现细节才能使用这些操作;信息隐藏,强制封装。
(2)优势
- 构件外部只能通过外部可用的操作来访问内部数据和操作,降低了构件间的耦合度。
- 构件内部修改对外部的影响变小,减少了修改引起整个程序范围的“波动效应”。
- 更容易快速开发正确的程序,一旦程序员们确定了构件间的交互,每个人都可以独立地开发和测试分配的构件。
- 改善通用性和维护性。
3.1.3 抽象数据类型
- 数据类型是一个值的集合和定义在这个值上一组操作的总称。
- 数据类型明显或隐含地规定了数据的取值范围、存储方式及允许进行的运算。
- 数据可分为两类:原子类型、结构类型。
- 基本数据类型:整型、浮点型、布尔型、字符、数组、结构体等。
- 抽象数据类型(Abstract Data Type, ADT):是表示无关的数据类型,是一个数据模型及定义在该模型上的一组操作。定义有个ADT时,必须给出它的名字及各操作的名称,并且规定这些函数的参数性质。一旦定义了有个ADT及其具体实现,程序设计中就可以像使用基本数据类型那样,十分方便的使用ADT。
3.2 认识面向对象
3.2.1 设计类
1、抽象是设计类的基本方法
- 抽象是在某个东西周围画上一个白盒子的动作:识别出它做什么、不做什么。
- 抽象是对某个东西定义接口的动作。
- 抽象告诉我们做什么。但是,抽象不告诉我们是如何做到这些的。
2、封装
- 封装处理如何将这些特性模块化。封装解决的是如何划分一个系统的功能的设计问题。
- 封装是在某个东西周围画上一个黑盒子的动作:它明确某事能完成,但是不告诉是怎样做到的。换句话说,封装对类的使用者隐藏了实现细节。
- 为了使应用程序容易维护,要限制访问类的数据和操作。基本思路是:如果一个类想要另一个类的信息,要请求它,而不是取它。
3、模块化原则
- 紧内聚、松耦合仍然适用于评价面向对象程序。
- 包括类的内聚、方法的内聚;不同类之间的耦合、同一个类不同对象之间的耦合及同一个类内函数之间的耦合。
- 类的模块化准则要求一个类应当是完整的、原始的、充分的。
3.2.2 设计操作
1、类的设计
- 要尽量使其所有的操作都是原始的,每个操作仅提供简单、良好定义的行为。
- 根据松散耦合的原则,也倾向于分离操作、减少它们之间的沟通。
2、平衡矛盾
- 把复杂的行为集中在一个方法中,简化了接口,但其实现复杂了。
- 反之,方法行为和实现简单了,但方法多了,接口复杂了。
3、建议
通常在面向对象开发中,把类的方法作为整体来设计,这是因为所有这些方法的合作构成了抽象的全部协议。设计时考虑以下建议:
- 复用:这个行为在更多的环境中更有意义吗?
- 复杂:实现这个行为有多难?
- 适应︰这个行为与其置身的类有多少关系?
- 实现知识∶实现这个行为要依赖于类的内部细节吗?
4、多态
- 多态是类型理论的一个概念,一个名字可以表示多个不同类的实例,只要它们具有某个共同的超类而且相关。所以,被这个名字表示的任何对象都能以不同的方式对—组某些相同的操作做出响应。
- 由于多态,一个操作就能在层次结构中的所有类以不同方式实现。这样,子类就能扩展超类的能力或者覆盖超类的操作。
- 很多类具有相同协议时,多态最有用。如果没有多态,程序中会出现大量的if 或switch语句。
3.2.3 分类
类的使用会增加更多的类,类需要划分。分类是对整理知识的一致手段。就是试图把具有共同结构或表现出共同行为的事情分为一组。识别类和对象是面向对象开发的一个挑战。识别包含发现和发明。通过发现,我们认识到构成问题域词语的关键抽象和机制。通过发明,设计出一般化的抽象和机制,说明对象是如何协作的。发现和发明都是分类问题,其核心就是发现问题的共性。
1、类之间的关系
(1)面向对象中三种类关系
- 普通与特殊(继承),即“是一种”。例如,加法算式是一种二元算式。
- 整体-部分(聚合),即“是成员”。例如,算式是习题的一部分。
- 关联,表示没有其他关系的类之间的某种语义依赖。如,“学生”和“教师”是两个独立的类,但是它们都和“课程”相关。
(2)面向对象软件还有一种常见的关系——依赖。
- 依赖表示关系一端的成员以某种方式依赖于关系另一端的成员。它告诉开发者,如果这些元素发生变化,会影响其他成员。
- 例如,以几何图形的显示display为例,除了显示图形的形状,还可以显示图形的颜色和线条,类Geometry则依赖类Color和Style (粗细、实线、虚线)。
2、接口与实现
(1)接口
- Meyer认为程序设计本质上是契约:一个较大问题的功能通过把它们分包到不同元素的设计,而分解成若干较小的问题。
- 类的接口提供了外部视角,重在抽象,同时隐藏了它的结构和组成。接口主要包括声明。
- 类的实现是其内部视角,包含其行为,主要由所有定义在类接口操作的实现组成。
(2)类接口可进一步分成4类
- 公共的:对所有用户可访问的声明;
- 保护的∶仅允许类本身及其子类访问的声明;
- 私有的∶仅允许类本身访问的声明;
- 包∶仅允许和类在用一个包的声明。
接口是一些面向对象语言的基本元素,如Java语言的接口Interface,必须有具体的类才能实现接口定义的操作。
3.3 面向对象设计
3.3.1 面向对象的设计符号
软件设计语言或符号,如可视化图形设计符号。在面向对象设计中,普遍采用的包括描述程序静态结构的类图、描述程序动态行为的交互图。它们掩藏了类中方法的实现细节,突出了类的组成和类之间的关系,简洁清晰地表达设计意图和内容。复杂的算法、数据结构等操作的实现,仍然使用代码和伪代码补充说明。
UML不仅能使软件建模可视化,还有助于分析、评估和验证软件设计,支持从UML自动产生部分代码,指导产生测试用例。
UML类图:
- 类用一个带有类名、属性和操作的矩形表示。分隔线用来分离类名、属性和操作。类名在矩形的最上方,其次是属性,然后是操作。
- 类名∶具体类的名称正常书写;抽象类名加abstract 前缀,或用斜体书写;接口名加前缀interface,属性空着。
- 属性:类似程序语言的声明——可见性、变量名、类型、初始值;前缀表示可见性:“+”公用,“-”私有,“#”保护静态变量或常量用大写字母的标识符,成员变量允许具有初始值。
- 操作:用签名表示——可见性、返回类型、操作名称、参数及类型,可见性符号与属性的相同。
类之间的关联用一根线表示,包括每个关联类的角色名、数目、方向和约束:
- 泛化是一端带空心三角形的连线,从子类到父类,空心三角形一端是父类。
- 聚合用来描述一个元素(整体)包含另外的元素(部分),部分可以脱离整体作为一个独立的个体存在。聚合的整体端用空心菱形表示。
- 组合是一种语义更强的聚合,部分组成整体,不可分割,整体消失部分也跟着消失,部分不能脱离整体而单独存在。组合的整体端用实心菱形表示。
3.4 调试的基本技术
3.4.1 单步调试源程序
1.设置断点
在要检查的语句上设置断点,程序执行到断点处会暂停。通过窗口观察此刻变量的值、检查程序运行情况;可以输入改变变量或表达式的值,然后让程序继续运行。Eclipse中在代码行左边的页边空白处双击设置断点。
2.单步调试
- 利用单步命令,从断点处开始一次处理一条语句。变量值有助于程序员仔细观察程序的执行流程、了解程序变量值的变化、调查可疑代码。
- 在debug视图中有三种方式:执行视图中的图标、右击出现的选择(含图标)、快捷键。
3 .临时断点
- 有时需要在显示的代码中临时设置有期限的断点,以便细致观察程序。
- 在Eclipse 中,突出显示源码窗口中要设置临时断点的代码行,然后右击并选择Run to Line。
3.4.2 检查/更改变量的值
- 当调试器暂停了程序运行后,可以执行一些调试命令来显示、改变程序变量的值。
- 不使用调试器,在程序中增加打印语句显示程序变量值的变化也可以。
- 在图(a)选中变量右击选Change Primitive Value,出现图(b)所示的对话框。
3.4.3 设置监视点观察变量
- 监视点( watchpoint)结合了断点和变量检查的概念。每当指定变量的值发生变化时,都暂停程序的运行。
- 监视点对局部变量的用途一般没有对作用域更宽的变量的用途大,因为一旦变量超出作用域(如函数结束),在局部变量上设置的监视点就会被取消。
- main()中的局部变量例外,因为其中的变量要等到程序执行结束时才会被释放。
在Eclipse中设置监视点的方法︰
- 在源码窗口中右击,选择Watch,然后在对话框中填写适当的表达式,如图3.11所示。代码的执行结果将显示在表达式窗口中。
- 选中一句或一段代码右击,选择Inspect(检查)项,可以直接显示表达式的值。
3.4.4 上下移动调用栈
- 与函数关联的运行时信息存储在称为帧的内存区域中。帧中包含了函数局部变量的值、形参,以及调用该函数的位置。
- 系统为每个调用函数创建一个帧,并将其放在一个运行栈上;运行栈最上面的帧表示当前正在运行的函数,当函数退出时,这个帧就退出运行栈并释放所占的内存。
- 运用调试器可以观察运行栈,追踪函数之间的调用关系和变量值的来源、变化等信息。Eclipse中,运行栈在debug透视图本身连续可见。
3.5 软件自动化测试
3.5.1 初始JUnit
1、使用JUnit进行测试的基本步骤:1-2
- 建立测试类,命名规则∶待测类名+Test,如:BinaryOperation Test。在该类的前面用@RunWith 指定测试运行器,默认JUnit4。
- 在用@Before注解的setUp()中为测试做必要准备(测试装置fixture )。
- 为待测类的成员方法/函数编写测试方法,命名规则:test+待测方法,以@Test注解这个待测方法,其中务必包含测试断言。
- 运行测试,查看运行结果,更改代码。
- 增加方法或修改代码时,重复(3、4)。
2、JUnit4使用注解Annotation简化了测试编程。下面是用到的部分JUnit注解:
- @Test:定义之后的方法是测试方法,否则后面的方法不是测试代码。测试方法必须是public void,即公共、无返回值的;可以抛出异常。
- @Before:使用该注解的方法在每个测试方法执行之前都要执行一次。主要用于一些独立于用例之间的准备工作,比如创建一个对象,或者打开文件,或者连接数据库。
- @After:使用了该注解的方法在每个测试方法执行之后要执行一次,与@Before对应,但不是必需的。
- @Runwith :执行测试的运行器。放在测试类名之前,用来确定测试类是怎么运行的。不指定则使用默认Runner来运行测试代码。
- @Ignore:该注解标记的测试方法在测试中会被忽略。
3.5.2 编写JUnit测试代码
1.基本测试——建立测试
- JUnit把任何用@Test注解的方法当成一个测试用例。测试方法或测试函数可以随意命名,它们既没有参数,也没有返回值。
- 测试方法代码的核心是调用待测程序得到实际的运行结果,用断言assertEquals比较待测程序的运行结果与预期结果是否相等。
- 通过记录断言的真假,统计测试运行的成功与失败次数。
2.追踪失败的测试
- 如果测试没有通过,Failures显示失败的测试数及测试函数。
- 在Failure Trace下面可以查看错误。
- 还可以深入探究失败的原因:选择一个失败的测试,右击,选择“Compare Result”,可以查看预期结果与实际结果的比较。
3.6 讨论与提高
3.6.1 对调用的进一步认识
1.调试与测试
- 调试与测试都分析程序代码、选择性地运行程序,并观察程序的结果或运行过程。
- 测试与调试的目标不同,采用了不同的技术、方法和工具。
- 在软件构造过程中,开发者交替进行测试与调试∶测试发现程序可能存在错误,然后通过调试来修改错误,之后再通过测试确认程序错误得到了修改。
2.不调试就是最好的调试
精通程序调试不仅要掌握专门的调试器,还要充分利用其他编程辅助工具。最好的调试方法就是一开始就不要错误地编程:
- 首先,要熟练掌握编辑器的使用。充分利用支持编程语言的编辑器是最容易忽略的“预调试”方式。
- 其次,充分利用编译器。
- 第三,使用静态代码检查器。
3.6.2 设计原则与设计模式
1、设计模式的含义
- 面向对象技术在软件开发过程中出现了一些可反复使用、解决实际问题的解决方案,称为设计模式。
- 一个设计模式针对一个具体问题,用抽象方式描述解决一类特殊问题的、通用的设计方案元素。
- 核心元素包括∶标示模式名称,描述适用的场景,刻画设计的模板,给出代码示例。
2、在面向对象技术中,用类图描述设计模式的结构,用交互图描述设计模式的行为。设计模式已经超出面向对象,设计并应用在其他的软件开发范式。设计模式也延伸到软件的其他组成成分,如算法模式和软件架构模式。从问题分析、设计思路、设计结构、模式特点、案例研究5个方面,介绍∶策略模式和迭代器模式。
3.6.3 面向对象的设计原则
1.策略模式
(1)问题分析。算法多种多样、经常改变,其他类不使用的算法。如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
(2)解决思路。使用一个抽象的策略算法,用子类继承这个公共的抽象类,定义每个具体的策略算法,把它们封装起来,通过实现抽象类的抽象算法使它们可互相替换。该模式使得算法可独立于使用它的应用客户而变化和扩展。
(3)设计结构
(4)模式特点。策略类及其子类提供了一系列可重用的封装算法,通过面向对象的多态、动态绑定技术,对象在运行时根据需要在各个算法之间进行切换。
注意:策略模式容易造成很多的策略类。
2.迭代器模式
( 1)问题分析
在软件构建过程中要处理集合数据,集合对象的内部结构变化各异。对于这些集合对象,希望在不暴露其内部结构的同时,让外部客户代码透明地顺序访问其中的每个成员对象;同时也为同一种算法在多种集合对象上进行操作提供可能。
(2)解决思路
提供一种方法有效地按顺序访问聚合中各个成员对象,而又不暴露该聚合对象的内部表示。
(3)设计结构
(4)模式特点
- 迭代抽象——访问一个聚合对象的内容而无须暴露它的内部表示。
- 迭代多态——为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
- 健壮性考虑——遍历的同时有可能更改迭代器所在的集合结构,导致问题。
3、面向对象的5个基本原则
(1)单一职责原则(Single Responsibility Principle ,SRP )。一个类只有一种功能。面向对象程序对低耦合、高内聚原则的实践。
(2)开放封闭原则(Open Closed Principle , OCP)。软件实体(模块、函数、类)应该可以扩展,但不可修改。对扩展开放,对修改封闭。它是面向对象所有原则的核心。
- 开放封闭原则是松散耦合的具体体现:允许程序通过类的继承、合成而扩展,但是不允许或尽量减少改变已经编译好的类。
- 解决程序修改问题的核心是模块化。通过修改模块(类)而实现程序的开放封闭原则。
- 例如,案例目前的要求是100以内的加减法运算,如果想把数据扩大到500,或者要求加法是200以内、减法是100以内。
(3)依赖倒转原则(Dependency-Inversion Principle, DIP)。抽象不应该依赖细节,细节应该依赖于抽象。该原则与传统的结构化分析与设计方法对立。
(4)里氏代换原则( Liskov Substitution Principle,LSP )。子类型能够替换基类型。这是保证继承复用的基础。违反了里氏代换原则必然导致违反开放封闭原则。
(5)接口隔离原则( Interface Segregation Principle,ISP )。不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。
第4章 数据处理的软件构造
4.1 数据及其持久性
保存在程序之外(如文件、网络)的数据称为持久数据。文件处理是编程语言支持应用程序存储和处理大量持久数据的一个最重要能力。Java等编程语言通常都提供了文件处理和输入/输出流的功能。
1、从比特到文件
- 字符由比特组成。
- 一组字符或字节组成字段,一个字段是传递含义的一组字符或字节。
- 若干字段构成记录,记录是一组有关系的字段。
- 一个文件可以是一组相关的记录。
2、组织文件中的记录有多种不同的方式
- 最常见的方式是顺序文件。
- 一组相关的文件可以组成数据库。
3、一个应用程序如何选择数据的存储、管理和处理方式,要考虑下面的因素:
- 数据的持久性和使用频次
- 生产和访问数据的难易程度
- 共享与传输
- 数据的量及管理
- 数据的操作方式
4.2 文件与输入输出流
4.2.1 文件
1.字符文件与字节文件
- 在字符文件中,字节表示字符,使得人们可以查看、编辑文件。在字节文件中,字节不一定表示字符;字节组还可以表示其他类型的数据, 如整数、浮点数或汉字字符。
- 文件都是以进制格式存储的。
- 字节文本的存储无须任何编码,而使用文本文件时要考
虑字符编码。
2.记录文件和流式文件
文件分为物理文件和逻辑文件。逻辑文件从结构 上分两种:
- 无结构的流式文件,信息不划分单位,由一串字符流构成文件;
- 有结构的记录文件,信息按逻辑上独立的含义划分信息单位,称为一个逻辑记录(简称记录)。
4.2.2 输入/输出流
程序借助一个连接内存中的程序和外存设备的通道来操作存储器中的数据。
在程序中,可以把文件理解成物理概念,流是逻辑概念。把该文件与一个(对象)流联系起来。高级语言如C、C++、C#、Java 等保留了操作系统中的文件,同时引入了流,执行对文件的读/写操作。Java等语言没有定义文件结构,即记录,所以,程序员必须设计文件结构来满足应用的需求。
4.2.3 数据序列化
1.编写数据序列化代码
- “线性化" 函数将结构化数据转换为字符串类型的数据当程序需要结构化数据时,再通过“结构化"函数把线性化的数据按照原先的数据结构恢复成结构化数据。
- 如何读/写对象:要把对象转换成字符串,在Java中类似toString。首先要把具有结构的数据元素分解,转换成字符串,然后用特殊分隔符隔离。每个数据对象还要再以分隔符隔离。
2.对象序列化
- Java、 C#等面向对象语言都有实现对象序列化的类或接口(类似Serializable ),把对象数据转换成 ( =进制)字节序列的形式,与外部源共享或传输数据。
- 程序员不必准确知道系统是 如何表示对象的字节序列的。
- 序列化( Serialization )是将对象的状态信息(成员变量)转换为可以存储或传输的形式的过程。它的逆过程则被称为反序列化( Deserialization )。
- 在序列化期间,对象将其当前状态写入到临时或持久存储区。可以通过从存储区中读取或反序列化对象的状态重新创建该对象。序列化的对象是对象的字节序列,包含对象数据及其类型信息。这样,信息可以用来再创建内存中的对象。
4.2.4 CSV格式的文本文件
1、CSV
- 逗号分隔值CSV ( Comma-Separated Values )以纯文本形式存储数字和文本数据。
- CSV是一种通用的、相对简单的文本文件格式,在电子表单和数据库中有着广泛的应用。
- 一些程序设计语言如Java、R、Go和Python都内置了读/写CSV格式文件的函数或类。
2、CSV没有单一-的、 明确定义的格式。在实践中,EBCDIC或GB2312 :
- 纯文本,使用某个字符集,如ASCII、Unicode、EBCDIC或GB2312 ;
- 由记录组成(典型的是每行一条记录 ) ;
- 每条记录被分隔符分隔为字段 (典型分隔符有逗号、分号或制表符;有时分隔符可以包括可选的空格) ;
- 每条记录都有同样的字段序列。
4.3 编写健壮的程序
- 健壮性( robutness )是指程序对于要求之外的输入进行判断并处理、使程序保持运行状态,即使这有时可能导致不准确的结果。
- 程序的正确性指的是程序绝不产生不准确的结果。
- 防御性编程和断言是实现程序健壮性的技术手段。
4.3.1 防御性编程
- 防御性编程的基本思想:程序员要预计程序使用者的过错、无效的输入、甚至有害的数据及使用者的过失,即使这种事情罕见,也要采取适当措施保护自己的程序。
- 保护程序无效输入破坏的基本原则:检查每个输入参数的数据;特别要检查从程序外部进入程序的数据。一旦发现了无效数据,就要决定处理的方式。基本的方式有处理错误和使用异常。
1.处理错误
- 错误处理的方式影响软件满足正确性、健壮性及其他非功能性需求的能力。
- 数据错误出现时的一些建议:
- 继续运行程序、返回中性无害的数据。
- 用最接近的有效数据替换无效数据。
- 在日志中记录警告信息并继续运行程序。
- 调用错误处理程序或对象。
- 屏幕显示错误信息。
- 尽可能在局部处理错误。
- 返回一个错误编码,让特定程序处理这个错误。
2.使用异常
异常是处理错误的一种特殊方式,出现了错误或异常行为的程序能把错误传递给程序的调用者,让它处理。
异常处理一般有两种模型:终止模式和恢复模式
- 终止模式:假设错误非常关键,导致程序无法返回到异常发生的地方继续执行。一旦抛出异常,就表明错误已无法挽回,也不能回来继续执行。
- 恢复模式:认为异常处理程序的工作是修正错误,重新尝试调用出问题的方法,并认为二次处理能成功。恢复模式希望处理异常后程序能继续执行。
3.应用举例
- 定义方法私有的,确保只有开发者知道,仅仅在参数合法的情况下才使用该操作。
- 使用语言系统提供的异常类和异常处理机制,检查参数的合法性。
4.3.2 使用断言
- 断言:是让程序在运行过程中自我检查的代码。如果断言为真,就意味着程序如期望的正常;否则,就表示在代码中发现了意外。
- 编写代码时,我们总是会做出一些假设。断言用于在代码中捕捉这些假设。
- 可以将断言视为异常处理的一种高级形式。
可以使用断言在代码中记录一些假设,例如∶
- 输入参数的值在预期范围内;
- 程序运行时文件流已打开或者在开始的地方;
- 输入参数的数组、表或其他容器已经包含了数据;
- 指针非空;
断言的现行形式是一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。断言分为如下三类:
- 前置断言:代码执行之前必须具备的特性。
- 后置断言:代码执行之后必须具备的特性。
- 不变断言:代码执行前后不能变化的特性。
断言的基本用途是调试和测试程序,编译器启动断言检查后才能使用断言。程序布署完之后就关闭断言。Java使用断言的例子:Junit。
下面是使用断言的一些建议:
- 对预计出现的条件使用错误处理,对不应当出现的条件使用断言。
- 错误处理用于检查不合理的输入数据;断言则用于检查代码中的错误。
- 避免在断言中放置可执行的代码。因为关闭断言后,编译器可能会删除这些代码。
- 用断言来记录和验证前置条件和后置条件。
- 健壮性要求高的程序同时使用断言和错误处理
4.4 字符串处理与正则表达式
- 在操作文本文件时,数据都是字符串。基本类型(如32.5)或结构化的数据(如32+5),存储在文本文件中都是字符串“32.5”和“32+5”。字符串作为基本的内置类型,编程语言也提供了大量的字符串操作,如:查找一个字符、查找一个字串、置换一个字串、合并两个字串、字符串复制等。
- 自定义的数据类型或对象,则需要用户自己编写转换程序。如,C#、Java等需要为自定义的类编写对象的字符串显示方法toString(),覆盖从根类继承的默认显示。
- 程序对算式的文本格式要求严格:字符串不能含任何多余的字符,包括空格。如“51+11,口口19+45,92+8,80+19,73+10”有任何多余的空格,程序将运行错误。否则,程序员要仔细编写烦琐的程序,进行预处理,使字符串满足要求。
- 现代编程语言提供了更丰富的字符串处理库,其中重要而又易用的是正则表达式。
正则表达式 Regular Expressions:
- 是一串字符,它所定义的模式可用来查找、显示或修改输入序列中出现的某个模式的一部分或全部。
- Java在String类中提供了boolean matches (Stringregxep)、void replaceAll (String regxep, Stringreplacement)和String [] split(String regxep)三种基本方法,它们的作用分别是匹配、替换全部匹配内容、分割。参数都包含了正则表达式regxep。
4.5 程序中数据集的使用
4.5.1 算式基
案例的算式及习题都是在使用时由程序随机产生,即数据是按需生成和使用的。
产生的算式、习题数据都以程序变量的值出现在计算机的内存中,一旦程序结束,数据立即丢失。
数据的这种处理方式有局限性:
- 效率。
- 复用。
- 共享。
解决方法:
- 用二维数组AdditionBase[101,101]作为加法算式基,存放所有满足约束条件的加法算式,函数generateAdditionBase()生产加法算式基。
- 函数generateSubstractBase()产生一个减法算式基,SubstractBase[101,101]作为减法算式基,存放满足所有约束条件的减法算式。
- 二维数组的算式基存放的是有效算式的运算结果,对无效算式则为空或-1。算式基具有如下特性:
- 算式及其约束条件隐含在二维数组中:只要元素AdditionBase[i,j]或SubstractBase[i,j]不是空或-1,就表示i+j或i-j是有效算式,而且AdditionBase[i,j]存放的就是算式i+j的计算结果,SubstractBase[i,j]是算式i-j的计算结果。
- 两个算式不一样,当且仅当算式基的两个数组元素对应的行或列的下标不等。
利用算式基产生算式
- 随机地生成两个整数i和j,0≤i , j≤100,然后从加法基或减法基选择一个数组元素[i,j]构成算式。
- 习题的创建与算式基的创建称为两个独立的活动。
- 算式基实现了按需选择数据和数据重用,为数据共享提供了支持。
- 如果能持久地、以通用的格式存储算式基及产生的习题,就可以在不同程序、甚至不同语言的程序之间实现数据共享。
4.5.2 表驱动编程
- 算式基的设计把加法、减法算式的约束条件,以及算式产生的信息都放在了一张表中,使程序在表中通过选择条件而不使用逻辑语句( if或case)得到算式及其运算结果。
- 如此编写程序的方式称为表驱动编程。
逻辑语句与表驱动编程:
- 理论上,任何使用逻辑语句的情况都可以用存储了信息的读取表的操作。
- 如果条件简单,则逻辑语句直截了当、易用。
- 逻辑链越复杂,表驱动编程就越有吸引力。
- 使用得当,表驱动编程把复杂的逻辑编织在表中,而不是编织在代码中,使得程序结构简洁、逻辑清晰、容易修改和扩展。
使用表驱动编程方法需要考虑以下两点:
- 表项的内容。查询表得到的可以是直接结果或者是动作。在这种情况下,可以在表中存放描述动作的代码,或者对某些语言可以存放引用实现动作的函数。在表中存放动作使表的内容及其处理变得复杂了。
- 表项的查询。有三种基本的查询方式∶直接访问、阶梯访问和索引访问。
1、直接访问
- 表中的项表示一个结果或动作,通过数组下标(一维表)或矩阵下标(二维表)直接访问表。
- 例如,计算某个月的天数,可以用下标是1~12的数组存放每个月份的天数,用月份作为数据直接得到当月的天数。
2、阶梯访问
- 表中的项表示一个数据范围而不是对应不同的单个数据,而且数据的排列按照一定的顺序,这样就避免了显示比较,实际上是隐含了比较。
- 【例4.5】考虑按照输入的分数输出成绩等级的程序。
- 使用if或者case语句
- 使用表驱动编程,将成绩等级按照上升方式存入数组String []grades
- 表的设计影响程序的设计
阶梯访问方式的一个优点是容易处理不规则的数据
- 在分数等级的例子中,“不及格”对应了59个数,“优秀”对应11个数,其他的对应10个数。
- 容易修改程序,使它能处理不含“中等”的分数等级,即60~79分都是“及格”。
3、索引访问
- 首先为查询的数据建立一个检索表,用索引数据在索引表中得到关键字。然后用该值在另一个表(主表)中检索感兴趣的主数据。散列表或哈希表(Hash Table )是索引访问的一个例子。
- 索引访问的优势如下。
- 节省空间。
- 操作检索表项要比操作主表项简单。
4.6 基于程序结构的测试
4.6.1 语句覆盖测试
- 良好编程的一个基本原则,程序中每个组成(语句和变量等)都要有用,即完成指定的功能,不多也不少。
- 结构性测试试图证实这个原则得到落实或落实的程度。
- 普通的编译程序能检查程序的语法错误,有些编译也能检查程序的语义错误,甚至逻辑上的缺陷。
- 测试程序更关注语句、语句的组成及语句之间的关系。
语句覆盖测试:
- 设计测试,检测程序的每条语句是否都能执行。
- 如果一个测试用例没有使所有的语句都得到执行,就增加测试,试图增加执行的语句数量,直至所有语句都能执行。
- 否则,要么测试用例不够,不能使所有语句都执行;要么程序有缺陷,出现了不可能执行的语句。
要明确语句的概念
- 计算机程序的语句一般分为简单语句和复合语句。
语句覆盖的基本准则
- 设计测试用例,使得程序的每条基本语句都得到执行。
运用语句覆盖的测试目标:
- 使用尽可能少的测试用例,实现最大的语句覆盖,用语句覆盖率来量化;
- 语句覆盖率=覆盖的语句数/语句总数
即使是100%的语句覆盖测试也不能保证程序正确无误。语句覆盖是最容易实现、也是最弱的覆盖准则。
4.6.2 逻辑覆盖测试
1.判定覆盖
- 判定覆盖准则测试的含义是,设计测试用例,使得程序中的每个判断分支都至少经历一次。
- 由于一个判定往往代表着程序的一个分支,所以判定覆盖也称分支覆盖。
- 如,测试数据(12,48, '+'),(102,48, '+'),(88,45,'-'),(77,45,'? '),(38,81,'-'),(18,92,'+')
2.条件覆盖
- 布尔条件分简单条件和复合条件。
- 简单条件指的是布尔变量或原子布尔表达式,即不含布尔运算的布尔表达式。
- 复合条件则是至少用一个布尔运算连接的简单条件。
条件覆盖的准则是:设计测试用例,使得程序中的每个简单布尔条件的所有可能的值都至少满足一次。
4.6.3 路径覆盖测试
程序控制流图(Control Flow Graph,CFG)
- 表示程序的控制流向,指的是控制从一条指令到另一条指令的流动。
- 控制流动的方式多种多样,如按照指令的先后顺序、函数调用、消息传递或中断。
- 条件语句改变程序中的控制的正常的、顺序的流动。
控制流图的画法如下:
- 节点:圆圈表示一条可执行的基本语句,可以增加标记,如语句号或节点顺序号;
- 控制线或弧∶用带箭头的有向线表示连结的两个语句的执行顺序;
- 程序执行(控制流)的分叉或交汇处,可以用节点表示;
- 每个函数或程序都有唯一的开始和结束节点;
- 不含执行程序不可达到的语句或不能使程序停止的语句;
- 控制线必须连接两个节点,开始节点和结束节点除外。
程序的执行路径(简称路径)
- 可以抽象语句序列,即从进入程序的第一条语句到程序停止、一次运行的语句序列。
- 对于函数而言,停止语句可以是自然的最后一条语句,也可以是一条return语句。
用CFG再次简化和抽象程序的执行路径。用图的路径表示程序的执行路径∶一条路径是从CFG的开始节点经过连线到达结束节点的节点序列或控制线序列。
- 分解复合条件:把复合条件中每个简单条件用一个节点表示,并调整控制线。
- 合并简单的顺序语句:赋值语句、打印语句等不含条件,如果是顺序排列,它们的执行不会改变程序的控制顺序,即不会产生新的路径,可以将它们合并在一起,用一个节点表示。
注意:
- 路径有长有短,如果有循环语句,一条路径可能会出现在每一次的循环路径中。
- 按照等价类的思路,我们希望测试的每条路径都有所不同,但又尽量包含可能多的路径,通常采用基本路径覆盖法。
- 简单地说,一条基本路径是指,和其他基本路径相比,至少引入一个节点或一个新的控制线的路径。
- 用圈复杂数计算一个程序的基本路径数:一个CFG图G的圈复杂数是VG) = e-n+2 ,其中e是G中的边数,n是G中的节点数。
运用一个数据结构栈找出基本路径。
- 首先,CFG的开始节点入栈;
- 从栈顶按照CFG 连线,选择下一个节点∶如果一个节点的出度outDegree大于0,选择之前没有选择过的后继节点,outDegree-1 ;
- 重复2直至栈顶是CFG的结束节点。从栈内输出节点,其逆序就是一条基本路径;
- 寻找下一条基本路径,从栈顶逐个退出节点∶如果遇到一个节点的出度大于1且当前outDegree 大于0,则重复步骤2)~4);如果退出开始节点,则查找结束。
最后,几点说明:
- 圈复杂数是代码逻辑复杂度的度量。圈复杂数越大,程序复杂度越高,出错的概率越大。
- 每个程序的基本路径的数量一样,测试基本集不唯一。
- 基本路径测试不是测试所有路径的组合,它仅仅保证每条基本路径被执行一次。
- 面向对象技术倡导复用和单一职能,成员方法的圈复杂数通常都很小。
- 基本路径测试主要用在单元测试。
4.7 运用JUnit
4.7.1 异常测试
- 异常处理是提高程序可靠性的一个重要机制,需要时应该给程序编写异常抛出及其处理。
- 异常测试的含义∶如果一个程序应该抛出异常,但是运行时没有抛出,这就是一个错误。
- JUnit4通过@Test 注解中的expected属性来测试异常。expected属性的值是一个异常类。
- 测试忽略︰如果不想执行某个测试方法,在其前面使用注解@Ignore。测试运行结果中的斜线表示不执行的测试。代码前要引入org.junit.Ignore 注解包。
4.7.2 参数化测试
参数化测试
- 即只写一个测试方法,把测试用例作为参数传递进去,一次循环执行,完成多个测试数据。
- 可以把参数化测试理解成测试一个数据集合。
编写参数化测试代码的6个基本步骤:
- 为参数化测试生成一个独立的新的类,不能与其他测试共用同一个类。
- 为参数化测试类指定特殊的运行器,语句RunWith(Parameterized.class)为这个类指定org.JUnit.runners.Parameterized运行器,注解必须放在测试类的前面。
- 为测试类声明若干变量,分别用于存放期望值和测试数据
- 为测试类声明一个带有参数的公共构造函数,为3中声明的变量赋值。
- 为测试类声明一个使用注解@Parameters修饰的、返回值为java.util.Collection的公共静态方法。方法中列出测试用例,即测试数据和期望值。该方法没有参数,名字没有特殊要求。测试用例是一个二维数组,数组中数据的一部分是与测试类的变量相同的数据,另一个部分是预期结果。测试数据及预期结果的个数和顺序要与4的构造函数中的参数完全一致。
- 编写测试方法,使用定义的测试用例作为参数进行测试。
4.7.3 测试套件
- JUnit提供了测试套件作为容器,将所有需要运行的测试类集中起来,一次性运行,提高了测试效率。
- 测试套件使用一个特殊的Runner,要向注解@RunWith传递一个参数Suite.class,然后使用注解@Suite.SuiteClasses来表明一个独立的测试类(容器类)是一个测试套件。
在其中把需要一起测试的类作为参数传递给该注解——列举每个测试类。容器类的名字无关紧要,甚至内容也可以为空 - 在其中把需要一起测试的类作为参数传递给该注解——列举每个测试类。容器类的名字无关紧要,甚至内容也可以为空
4.7.4 JUnit的断言
断言是JUnit测试中最基本的组成部分。它们实际上是Assert类的一些静态方法。前面的例子使用了断言assertEquals。
第5章 用户交互的软件构造
5.1 程序及其功能的使用
5.1.1 程序的两个观察视角
程序可以从两个角度观察和理解:
- 程序员是程序的产生者。他们看到程序的内部组成——变量、语句、函数、类及其关联所形成的结构。程序员通过编写函数、方法、类等程序单元,实现用户要求的功能。
- 对用户而言,程序实现其需求,用户可以使用程序完成所要求的功能。
用户使用程序——用户交互:
- 不论是传统的命令行、菜单选择的程序输入/输出,还是流行的图形用户界面GUI、触摸式,以及声控、手势或脑控制的操作方式,都是用户使用程序、与程序进行交互的形式。
- 程序最终是要把用户的要求或指令转换成相应的函数或方法。
程序员都应该理解和掌握用户和程序交互的基本原则和编程实现技术。
5.1.2 多个功能程序的整合
当一个程序不止一个功能(用户角度),或者包含多个函数(程序员角度)时,如何呈现并执行这些功能涉及两方面问题:
- 如何把若干功能合理地呈现出来,便于用户操作;
- 如何把若干函数整合,便于程序执行。
- 问题1∶是用户界面或交互的设计
- 问题2∶是程序的集成。
5.1.3 多个功能的组织与呈现
用户界面可以视为集成程序的一种方式。程序的用户界面集成实质上就是如何把一组功能合理地组织并呈现给用户使用。如果不考虑文字或图形的方式,可以把功能以某种顺序全部罗列出来,或者把所有功能按照某个规则分组、分层地结构化地呈现。例如∶文件目录。
- 用户界面:把程序众多功能划分成组(抽象),按照层次结构,从抽象到具体直至一个功能,为用户使用提供了方便易用的界面。这种用户交互程序的形式无论是采用文字还是图形,基本原理都一样。
- 目前的移动应用APP众多,手机屏幕大小有限,有两种基本方式组织应用:
- 一种方式是以屏为单位连续摆放应用图标,排满后增加一个屏幕,继续顺序放置;
- 另一种方式是文件夹,它可以按照顺序放置把一定数量的应用。
- 图形化界面可以通过单击表示功能或应用的图标进入下一级菜单,直接打开应用或某个功能。
- 菜单类似文件夹,菜单中可以包含(子)菜单,构成层次结构。菜单结构像一棵树。叶子节点表示独立的、具体的用户功能(原子功能),是提供给用户的服务或操作。树根和内部节点是一个子菜单,表示一组抽象的功能(功能组),用户必须继续从中选择,直至原子功能。
- 菜单的设计可以是自顶向下的方式,即从抽象的主菜单(根)逐步具体到子菜单,直至原子功能;也可以是自底向上的方式,把具体的功能逐步抽象、分组成子菜单,直至主菜单。无论哪种策略,都需要运用抽象和分类的基本原则。
5.1.4 基于菜单式功能选择的用户交互
- 电话IVR:在自动语音应答系统或交互式语音应答(InteractiveVoice Response ,IVR)系统中,用户通过拨打指定电话号码,根据系统的语音提示收听所需语音信息或服务,或者参与聊天、交友等互动式服务。
- IVR系统的应用领域:银行、保险公司、航空公司、电信、旅游及政府、医疗、健康、保险、教育和其他金融服务等。
- IVR系统通常把服务功能与数字、若干特殊符号((如井号#和星号*)关联起来,让用户输入数字或特殊符号获取相应的功能或进入某个子菜单(功能组)。
- 特点:用户需求对功能进行了初步划分和分层,菜单结构应该尽可能与用户要求的结构一致。
但是,从设计和实现的角度,要对一组或一层中较多的功能进一步划分,使得每个菜单中的选项在一定的数量范围内。 - 第一级菜单,即主菜单包含11个功能。在IVR中通常使用数字0~9对应某个功能,此外还要有返回上一层菜单、直接返回主菜单的功能,有时还提供重听的功能。而一般的电话(含手机)包含星号键和井号键。这样,主菜单及任何一级菜单中的功能就不能超过9个,如果超过9个,则需要把相关的功能抽象地组成一个功能组,放在下一级菜单中。
- 那么如何分组呢?
- 原则一:按照分类法,把相似、相近或相关的功能放在一个组里。
- 原则二∶应以最少的交互次数提供最常用的功能,即把最常用的功能尽可能地放在高层菜单。
这样,最少应该把主菜单11个功能中的两个功能放在一个功能组,就能用数字0~9对应剩余的9个功能和一个功能组。
模拟实现:
- 以便让用户体验界面是否合适。
- 所谓模拟,指的是没有实现或不调用已经实现的用户功能,而是用简单的、通常只包含一条打印语句的模拟函数代替。
- 模拟程序的主要目的是检测菜单结构、菜单项的访问及菜单之间的跳转。
- 如果已经实现了某个用户功能,则可以调用执行。
- 最终,程序要经过集成的策略和步骤,用实现的用户功能替换每个模拟函数。
5.2 用户交互概述
5.2.1 基本概念
- 用户交互或人机交互是有关交互式计算机系统的设计、评估、实现及与之相关现象的学科。
- 可以把人机交互理解为是关于可用性的学习和实践,是关于理解和构建用户乐于使用且易于使用的软件和技术,并能在使用时发现产品有效性的学科。
- 人机交互的目的是开发及提高计算机相关系统的安全性、效用、有效性、高效性和可用性。
5.2.2 交互设备
- 借助交互设备对计算机系统输入数据、指令或信息,并从系统获得反馈。
- 一个计算机系统通常使用以下几种交互设备。
- 键盘是文本输入的主要设备,也是应用最广的输入设备。
- 定位设备在屏幕上通过指点物体实现对物体的操作或完成某个功能。如:鼠标、光笔、触摸屏/板、轨迹球、操纵杆等。
- 显示器是用户从计算机得到反馈的主要输出设备。
5.2.3 交互风格
设计者完成任务分析并识别出任务对象和动作时,可以选择以下5种交互风格︰
- 直接操纵:主要针对可视化的用户交互,用户通过单击可视化对象和动作快速执行任务,并观察到结果(如把某个图标拖放到垃圾桶中)。
- 菜单选择:用户阅读选项列表,然后选择最合适自己任务的选项,系统分析和处理输入、转换成相应的功能。
- 表格填充∶用户看到适当的提示,在需要的地方输入数字,系统分析和处理输入、转换成内部程序的参数。
- 命令语言∶对常用用户而言,命令语言提供强烈的掌控感。如Linux操作系统的命令。
- 自然语言︰用自然语言(如汉语)发出命令和接收相应的方式操作计算机(软件)。
5.2.4 交互界面
- 是人和计算机进行信息交换的通道,人通过交互界面向计算机输入信息、进行操作,计算机则通过交互界面向用户提供信息,以供阅读、分析和判断。
- 用户界面是实现用户交互、使用程序的手段。用户交互是使用程序的本质。
- 只能通过特定的界面才能让用户更好地体验程序。
以下是几种常见的基本人机交互界面:
- 命令语言用户界面,比如DOS操作系统(用cmd进入)或Linux。
- 图形用户界面GUI。
- 直接操作用户界面。
- 多媒体用户界面,引入了动画、音频、视频等交互媒体手段。
- 多通道用户界面。是为了消除当前GUI和多媒体用户界面通信宽带不平衡的瓶颈,综合采用视线、语音、手势等新的交互通道、设备和交互技术,使用户利用多个通道以自然、并行、协作的方式进行人机对话,通过整合来自多个通道的精确和不精确的输入来捕捉用户的交互意图,提高人机交互的自然性和高效性。
- 虚拟现实技术。比以前任何人机交互形式都有希望彻底实现以人为中心的人机交互界面。
5.2.5 交互设计的原则
人机交互涉及心理学、认知科学、计算机科学、产品设计、图形设计等。3条基本原则:
- 学习性。指的是新的用户能用它进行有效的交互并获得最大的性能。
- 灵活性。是指用户和系统能以多种方式交换信息。
- 健壮性。是指在决定成就和目标评估方面对用户提供的支持程度。
8条黄金规则:
- 尽量保持一致。
- 满足普遍可用性。
- 提供信息反馈。
- 设计对话框以产生结束信息。
- 预防并成立错误。
- 允许撤销操作。
- 支持内部控制点
- 减轻短时记忆负担。
5.3 用户交互的开发
5.3.1 交互设计基本过程
- 标识和建立用户需求。交互设计的用户需求包括:功能需求、数据需求、使用环境和可用性需求。
- 提出满足需求的候选设计方案。概念设计和物理设计。
- 构建交互式版本。开发用户交互模型,通常是可运行的原型软件,包含用户交互界面、交互形式、完成任务的基本流程等。
- 设计评估。即评估交互设计的可运行和可接受性。
5.3.2 快速原型法
是快速建立起来的可以在计算机上运行的程序,它所能完成的功能往往是最终产品能完成的功能的一个子集。
使用快速原型目的:
- 在获得用户交互基本需求说明的基础上,快速建立一个可以运行的软件,使用户及时运行和看到交互的形式和使用效果,并对交互需求说明进行补充和精化,提出改进意见;
- 开发人员进一步修改完善,如此循环迭代,直到得到一个用户满意的模型为止。
(1)功能选择
原型和最终的软件系统不同,两者在功能范围上的区别主要有以下两个方面︰
- 最终系统是软件需求全部功能的实现,而原型只实现所选择的部分功能;
- 最终系统对每个软件需求都要求详细实现,而原型仅仅是为了试验和演示用的,部分功能需求可以忽略或者模拟实现。
(2)构造原型
根据用户初步需求,开发出一个可以运行的、主要包含界面的系统,它应满足用户提出的基本要求。
(3)运行和评价原型
用户在试用中能亲自参加和面对一个可以实际运行和操作的模型,能较为直观和明确地进一步提出需求,提出修改意见。
(4)修改和完善原型
- 根据修改意见进行修改,修改系统原型,然后再进行试用和评价。
- 这样经过有限次的循环反复,逐步提高和完善,直到得到一个用户满意的系统模型为止。
5.4 静态测试
5.4.1 程序的可用性与静态测试
程序的可用性指的是程序是否有用,包括用户界面是否易用,主要有三个特征∶
- 有效性,是用户完成特定任务和达成特定目标时所具有的正确和完整程度。
- 效率,是用户正确完成任务的程度与所用资源(如时间)的比率。
- 主观满意度,是用户在使用产品过程中所感受到的主观满意和接受程度。
可用性的指标:
- 易学性——产品是否易于学习;
- 交互效率——客户使用产品完成具体任务的效率;易记性——客户搁置某产品一段时间后是否仍然记得如何操作;
- 容错性——操作错误出现的频率和严重程度如何。
根据是否运行待测程序,软件测试分为动态测试和静态测试。
- 动态测试通过设计有效的测试用例,运行观察程序的运行行为、状态变化及输出等来判断软件是否存在问题。
- 静态测试不执行程序,而是通过阅读和分析代码及相关材料、发现软件错误的活动。静态测试又称人工手动测试,是动态测试和自动化测试的补充,是软件质量保障的重要组成。
静态测试的主要作用包括:
- 发现程序在功能、逻辑构造方面的错误;
- 验证实现的程序在需求和设计方面符合用户的要求;确认程序符合预先定义的开发规范和标准;
- 保证软件开发过程的规范性;
- 有助于程序员之间相互学习。
静态测试类型:
- 桌面检查
- 代码走查
- 正式审查
- 同行评审
- 静态程序分析
5.4.2 桌面检查
桌面检查(Desk Checking ):程序员个人模拟计算机“阅读”程序,发现代码错误的方法。
5.4.3 代码走查
代码走查(walkthrough )
- 是通过组织其他程序员共同参与的团队检查,是对传统的程序员桌面检查方式的改进。
- 代码走查至少由两人组成,其中一人协调走查,另一人扮演测试者。
- 在走查过程中,由测试者提出一批测试用例,在走查会议上对每个测试用头脑来执行程序,在纸上或黑板上演变程序的执行状态,从而发现程序错误。
- 在这个过程中,测试用例用作怀疑程序逻辑、计算或控制错误的参照,测试用例本身并不重要。
代码走查检查程序的错误∶
- 数据引用错误、数据声明错误、逻辑错误、计算错误、判断错误、控制流程错误、接口错误、输入/输出错误。
- 代码开发人员对照讲解设计意图和程序代码,特别是对有异议之处进行解释,有助于验证设计和实现之间的一致性。
5.4.4 正式审查
正式审查(inspection )
- —种正式的结构化检查和评估方法,一般有计划、流程、结果和追查。
- 审查小组至少有4人:一人负责协调、分发材料、安排进程、确保错误随时得到改正,被检测试程序的编码人员,其他程序开发人员和一名测试人员。
- 审查小组最好还要包括丰富经验的程序员、编程语言的专家、未来的代码维护人员、其他项目组成员,以及同组的程序员。
代码审查通过会议实施,基本过程如下︰
- 协调人在代码检查前几天分发程序清单、编码规范和检查清单;
- 编码人员讲述程序的逻辑结构,其他人员提问题并判断是否存在错误;
- 对照编码规范、检查清单分析程序;
- 审查人员的注意力集中在发现错误而非纠正错误上(非调试);
- 会议结束后,程序员会得到一份已发现错误的清单。如果发现重大缺陷,在修改之后,还要重新召开会议审议。
采用正式审查时要注意以下几点:
- 以会议形式审查,要制定会议目标、流程和规则,结束后要编写报告;
- 按缺陷检查表逐项检查,避免漫无目标检查;
- 发现问题适当记录,避免现场讨论和修改;
- 发现重大缺陷,改正后会议需要重开;
- 检查要点是缺陷检查表,根据不同的项目,该表要不断积累和完善。
5.4.5 同行评审
同行评审( reviewing )
- 是对代码的全面质量评审,包括代码的可维护性、可扩展性、可使用性,以及安全和编程规范是否得到遵守,是软件开发队伍对程序质量和信赖性进行的自我评估。
- 评审时,一般挑选一个程序员作为组织者,由他再选择若干同行参加评审。
- 同行必须是真实的,即具有被评审者相同的背景(例如,均是Java程序员)。每个评审者都要挑选两段程序进行评审,比较和给出这两段程序质量上的优缺点。
- 可以定义更详细的评价标准,例如,对发现问题的严重程度分等级或加权,从而定量地说明代码的质量和可信赖程序。
5.4.6 检查表
正式审查和同行评审都需要缺陷检查表或检查单
- 它列出了容易出现的典型错误,以便让编程人员和评审人员集中精力,依据代码检查单的问题,实施静态测试,并记录代码中的错误,以便在后期总结和统计错误的类型、原因等,从而避免和预防代码错误。
- 例如,Myers 从数据引用错误、数据声明错误、计算错误、判断错误、控制流程错误、接口错误、输入/输出错误等方面给出检查单。
5.4.7 静态程序分析
静态程序分析
- 通过扫描源程序而发现可能的故障或异常。
- 它不要求程序运行,因而属于静态测试。
- 它分析程序正文,识别程序中语句的差异,检测语句构造是否规范、推测程序的控制流、在很多场合计算出程序所有可能的值。
- 静态程序分析补充了语言编译器提供的错误检测功能。
- 静态分析的目的是引起程序员对程序中异常(如未初始化的变量、未使用的变量、数值超出范围)的警觉。尽管这些异常不一定是错误条件,但是,它们经常是程序设计错误、遗漏或疏忽的结果所造成的。
- 静态程序分析器不仅检测代码的故障,也检查代码是否遵循了规定的规范或风格。
5.5 软件集成与测试
软件集成:
- 把实现各个功能的模块、数据、用户交互界面等模块组织起来,构建成一个完整的应用程序的过程称为软件集成。
- 与之相对应的是集成测试。
- 软件集成主要体现并依赖于软件设计或软件架构设计,明确组成软件的各个模块的功能和接口。
- 只要严格按照要求开发,模块之间就能通过良好定义的接口整合在一起,构成一个完整的软件系统。
功能集成和界面集成是基本的软件集成
- 功能集成指把具有不同功能的程序或模块(函数、类、接口、库等)通过函数调用、消息传递、继承、包含、引用及共同的数据集等方式关联起来,作为一个整体完成更复杂的功能或提供比新程序组成单元更多的功能。
- 界面集成指为具有不同功能的程序或模块提供一个统一的用户交互界面,方便用户使用。
软件集成含三个方面:
- 集成策略指对软件的组成单元按照什么顺序组织起来、构成一个软件,考虑的问题包括软件组成单元之间的依赖关系开发任务分配等。
- 集成内容主要包括流程、数据、功能和界面。
- 集成技术指的是如何通过语法、语义、逻辑等关系把软件的组成单元“粘合”起来。
集成测试的主要目的:
- 是检测软件组成单元的接口是否符合要求,它们的交互是否流畅,
- 集成后的软件能否按照设计合作完成要求的功能等。
- 集成的前提是各个组成单元已经完成开发并通过了(单元)测试。
5.5.1 驱动模块和桩模块
- 在集成软件及其组成单元时,为了使得集成的软件可以编译和运行,需要模拟集成时尚未完成的组成单元、相关联的环境之间的交互。常见的基本的模拟程序有驱动模块和桩模块。
- 驱动模块的主要任务是搜集或产生数据、把数据传给待测程序、启动和执行它。
- 使用模拟程序的主要原因和目的如下:
- 模拟复杂的代码。增量集成代码时,要关注模块间的接口是否一致、模块的交互逻辑是否矛盾、模块的合作是否流畅等。采用模拟程序可以分离关注点。
- 模拟尚未实现的代码。
- 模拟程序的整体性。把所有或部分代码整合到一起,测试运行一次可能会耗费资源、时间等,为了检测程序的整体结构、用户界面、全局逻辑、关键功能等,采用模拟程序替换实际程序可以加快实现特定的目标。
5.5.2 集成策略
传统方法用树状的结构图示意一个程序的组织结构。图5.11表示了案例程序的功能结构,其中方框图表示程序的功能模块,连线表示模块之间的控制关系或者功能的组成;树的根表示总控模块,它调用其他模块完成程序的所有功能。基于程序结构的功能分解属于基本的集成策略,主要有如下5种方式:一次性集成、增式集成、自底向上集成、自顶向下集成、基干集成。
1.一次性集成
- 构成一个程序的所有模块在一次集成过程中组装成最终的软件。对图5.11而言,一次性集成就是,首先完成这16个模块的开发和测试,然后放在一起,构成最终的软件。
- 难以发现和修改在一次性集成中出现的错误。
2.增式集成
- 增式集成,即以一个基本模块或主模块为基础,每次增加一个或一组模块形成部分程序,在确保正确后再逐步增加模块,直至所有模块都集成至部分程序构成软件整体。
- 增量集成中出现的错误容易定位和修复。
3 .自顶向下集成
- 集成模块从根部的控制模块开始,以控制层次的顺序,逐次增加模块,直至形成整个程序的增式集成方式,称为自顶向下集成。增量集成时要为其他尚未集成的模块编写桩,通过测试后,再逐步用模块替换桩、执行集成与测试。
集成测试的步骤:
- 测试主模块,以桩模块代替没有完成开发的下级模块
- 依次用实现的模块代替桩模块;
- 每集成一个模块,就测试部分程序,重点是新增的模块及其与部分程序的交互;
- 必要时进行回归测试——重复之前的测试。
- 自顶向下集成策略的优点:在集成的任何时候都有一个可以运行的程序。
- 缺点:桩模块的开发代价有时会较大,而且正是由于桩模块的存在使得部分程序难以得到充分的测试。
4.自底向上集成
- 选择独立性最大或者对其他模块依赖性最小的模块开始,从程序结构图的底部开始,逐次增加控制模块,直至完成整个程序。
- 需要编写驱动模块。
集成测试的步骤:
- 编写调用底层模块的驱动模块;
- 测试驱动模块与底层模块组成的部分程序;
- 用实现的控制模块替换驱动模块,并测试部分程序的功能;
- 必要时进行回归测试——重复之前的测试。
自底向上集成的优点:
- 底层模块的测试与集成可以独立、平行地进行;
- 不需要桩模块。
缺点:
- 驱动模块的开发一般比较耗费;
- 难以充分测试高层模块的可操作性。
5 .基干集成
- 选择一个程序的基干模块,将自顶向下与自底向上的集成方式结合起来,逐步地集成其他模块的策略,称为基干集成,又称三明治或混合策略。
- 优点:
- 减轻了自顶向下与自底向上的不利因素;
- 集成过程总有一个可运行的部分程序;
- 适合大型、复杂软件的集成。
- 缺点:
- 需要仔细分析系统结构和模块间依赖性,同时要分析用户需求的优先性;
- 有时需要开发桩模块和驱动模块,工作量较大;
- 局部采用了一次性集成,接口测试可能不充分。
回归测试:
- 复用之前的测试对集成后的软件或其中的某个子集重新进行测试,以确保修改的模块或其他程序变更没有传播不期望的副作用。这种测试称为回归测试。
- 它是保证在程序修改的情况下保证程序功能正常的一致性测试策略和方法。
- 回归测试无须对程序进行全面测试,而是根据修改情况进行有限的测试。可以选用测试库中的测试用例,也可以增加新的测试用例。
回归测试的基本过程是:
- 识别软件中发生变更、受影响的部分;
- 选择可以复用的测试用例,用以验证程序的变更不改变程序的原有功能;
- 补充新的测试用例,侧重检测可能会受到影响的功能或软件子集,特别是包含新增或修改的部分,以及与其他软件部分的联系;
- 依据一定的策略对程序进行测试。
实施回归测试策略时,应该兼顾效率和有效性两个方面,可以采用不同的测试用例选择方式:
- 再测试全部测试用例。
- 基于风险选择测试。
- 再测试修改的部分。
5.5.3 集成测试与策略
集成测试关注模块间的调用、消息传递、数据传输与处理是否正确及不同模块的可组合性,它们之间的协作能否完成一个包含了若干操作的特定的应用功能,即是否能实现用户的功能要求。
集成测试应该遵循下列几条基本原则∶
- 应该测试所有的公共接口;
- 要充分测试关键模块;
- 应该按照一定的层次实施集成测试;
- 选择与开发、软件集成相匹配的测试策略;
- 集成后任何模块接口和实现的变动都要进行回归测试
协作集成与测试的过程如下∶
- 对待集成的模块依赖性、性关系等逻辑关系进行分析,确定出用户指定或密切关联的子系统;
- 运用之前的策略集成并测试每个子系统;
- 逐步集成每个子系统并测试整个系统,直至完成。
- 图5.12为一个软件的组成模块与关系。
- 一个集成顺序是按照子系统的序号由低到高。由于子系统3相对独立,可以独立地集成与测试。
5.7 讨论与提高
5.7.1 软件建模
模型可以是模拟,也可以是样板。
- 对尚不存在、拟构建的客体建立的模型,称为样板。它可以作为样板用以创建设想中的客体。
- 模拟是对已经存在客体的简化而建立的,模型是对客体的,它的主要作用是理解和分析客体,进而作为改进的手段,建立样本模型,创造新的客体。
物理模型和数学模型是基本的模型形态。
- 物理模型的表现形式主要是金属、塑料、木材等物理材料。
- 数学模型是用数学符号、方程、式子、图形等对客体本质属性的抽象而又简洁的刻画。
建立物理模型让人直观感受到待建物体的外形和结构。建立数学模型分析待建物体的物理、结构等性质。在经济、金融、管理等领域,数学模型也是基本、常用的建模手段。
数学是常用的软件建模手段,包括数理统计、代数学、数理逻辑、集合论、图论及时态逻辑、形式语言等。为了开发、分析和评估软件及软件过程,我们还使用软件建模语言或符号,特别是可视化的图形符号。
- 控制流图(CFG)来模拟程序的控制结构。
- 类图刻画面向对象程序的组织结构。
- 树(图的一种)描述用户交互的菜单结构。
- 实体-关系(E-R)图为数据关系建模。
UML有机地整合了较多的软件建模方法,在软件分析与设计中获得了广泛应用。需要指出的是,图形建模语言要有严格的语法和语义,它们规定了采用的图符(如线、框)的组成、结构和(用数学表示的)含义,以便对软件模型进行推理、分析和评估。
状态转换图:
- 是一个有向图。
- 图的节点表示抽象的、可以具有名称的状态。
- 带箭头的连线表示在一定输入(条件或动作)下从一个状态转换到另一个状态。连线离开的状态称为箭头指向状态的前驱,箭头指向的状态称为后继状态。前驱和后继状态可以是同一个状态。状态、引起状态转换的输入及连线的个数都是有限的。
- 状态转换图有唯一的起始状态和若干结束整体。可以用逗号分开引起两个状态转换的不同的输入。
5.7.2 基于模型的测试
1.基于有限状态图的测试
- 状态转换图把程序抽象成函数之间的关联与交互,忽略了函数内部的实现细节。
- 基于状态转换图的测试即对节点表示函数的黑盒测试。
- 状态转换图刻画了函数之间的交互。本例的状态转换图详细说明了每个菜单在接收有效用户输入后,程序在菜单之间的转换。故基于状态转换图的测试又可视为对整个程序的白盒测试。白盒测试技术可以应用到状态转换图模型。
(1)满足节点覆盖的测试
- 满足节点覆盖实际上就是满足状态覆盖。测试准则可以要求设计测试用例使得每个状态都至少执行一次。同语句覆盖,这是最基本、也是最容易满足的测试准则。
- 为了测试有些状态,如menu_0_4,首先需要将程序执行到其前驱状态menu_0,接收用户输入4后,才能测试状态menu_O_4。这就需要从一个状态迁移到另一个状态。
(2)满足连线覆盖的测试
- 实际上就是测试两个状态间的迁移。测试准则可以要求设计测试用例使得两个状态间的迁移都至少执行一次。首先,从连接的状态数量上考虑,该准则可以要求覆盖的状态不止两个,还可以是多个。
- 其次,要满足100%的迁移覆盖准则,就要对不同的动作或条件设计测试用例。
迁移的严格定义包含了迁移条件或动作及前驱状态、后继状态,而且要求在同一个状态下面临的一个条件或动作,最多有且只有一个后继状态。
另外,如果把流出连线多于一个的节点视为判定语句,那么,满足了迁移覆盖准则,就类似于满足单元测试中的判定覆盖准则。
(3)满足迁移路径覆盖的测试
- 把从起始状态到结束状态所经过的状态的排列定义为迁移路径,这个迁移路径测试就和单元测试中的路径测试类似。
- 由于存在环,迁移路径的数量可能是无穷多,因而,可以借鉴基本路径和图复杂度的概念及测试循环语句的基本原理,计算出状态转换图的图复杂度,找出基本的迁移路径,设计测试用例覆盖它们,最后再运用循环语句的测试原理测试含迁移环的测试。
2.基于场景的测试
- 基于场景的测试适合测试程序的一系列操作,发现程序模块之间的交互错误。
- 场景是描述用户需求的一种手段,是从用户的角度描述用户与系统的交互行为、反映系统的期望运行方式。场景是由一系列相关活动或事件组成的,它就像一个剧本,演绎系统预期的使用过程。
基于场景的测试步骤如下:
- 设计场景。根据用户对软件的需求和使用画出场景图,通常包括基本流和备选流。基本流是完成一个功能的、正常的活动序列;备选流是在基本流的过程中,对异常情况、不同条件的处理活动。
- 设计测试用例。使其覆盖基本流和每个备选流。
- 设计测试数据。对于每个测试用例设计测试数据(如具体的文件名)并执行测试。
- 重复上述步骤,直至测试完所有的场景。
5.7.3 执行函数名符号串的表驱动编程
把方法名以符号串的形式存入表中,通过下标找到表中的相应方法名来执行方法。
5.7.4 持续集成
持续集成是敏捷开发方法的一项基本的软件开发实践。项目成员频繁地集成他们的工作,使得每天都有多次集成的代码。每个集成都由自动构建验证,尽快地发现错误。持续集成的核心包括自动化构建、自动化测试及版本控制中心等工具。构建是执行编码、测试、检查和交付软件的一组活动。集成构建就是把软件模块(程序、数据和文件)结合到一起形成软件系统的活动。所谓自动,就是通过编写可以运行的脚本程序,完成诸如代码测试、构建等,无须人工干预。
第6章 软件重构与交付
6.1 代码重构
6.1.1 代码重构的案例研究
代码重构的含义:
- 在不改变软件外部行为的前提下改善它的内部结构。
代码重构的主要原因:
- 增量迭代的开发方式不断地增加编写的代码,由于是边思考、边设计、边构造,容易造成许多模块有重复的代码片段或者在一个模块中临时命名了变量等,使得代码欠规范,结构不清晰。
- 在完成构造、交付代码前改进代码质量,有助于今后的软件维护和更新。
代码重构的地位:
- 代码重构是增量迭代开发不可或缺的技术,已经成为现代软件开发的基本技术,并在很多常用的IDE中成为标准模块。
1.重构大函数
- 运用重构“提炼方法”( Extract Method )。
- 运用“移除临时变量”。
- 运用重构“以查询取代临时变量”。
- 可以用“函数移动”的策略。
1)运用重构“提炼方法”
- 把从switch语句开始的计算罚金与奖励积分部分设计成一个方法calculateFineAndBonus(Rental aRental)
- 如果一个方法的代码太长或者代码需要很多注释才能理解其意图,可用一个能说明其意图的方法替换那些代码。
- 运用“提炼方法”抽取代码时,要使用良好的编码风格设计方法名和参数,允许读者在较高的函数级别理解程序,提高程序的阅读性。
“提炼方法”的步骤如下:
- 设计一个新方法,并按照提炼代码的意图给方法命名。
- 把原来方法中要提炼出的代码直接复制到新的方法中(目标方法)。
- 检查目标方法中的局部变量、引用变量和临时变量,做出相应修改,有时要运用“移除临时变量”或“分解临时变量”的策略。
- 在源方法中调用目标方法,取代提炼的代码。
- 通过编译和测试。
“提炼方法”calculateFineAndBonus的步骤:
- 择语句“finedAmount = 0;”到“addBonus(bonus)"之间的代码,右击,选择Refactory→Extract Method ,
- 在出现的对话框中输入一个方法名,确定后得到重构后的代码。
2)运用“移除临时变量”,消除returnedMessage中的临时变量bonus。
再次运用提炼方法,将calculateFineAndBonus中的else分支语句都改成方法addBonus(aBonus),参数分别是原来赋值语句右边的值,即分别是1、2、3。 再次运用提炼方法,将计算FineAndBonus中的分支语句都改成方法addBonus(ABonus),参数分别是原来赋值语句右边的值,即分别是1、2、3.
目的是便于今后改变奖励积分的策略。 目的是便于今后改变奖励积分的策略.
3)运用重构“以查询取代临时变量”
消除方法returnedMessage中的临时变量finedAmount,它只被使用了一次,其值是通过函数调用或表达式计算得到的。要注意提炼的函数没有副作用,即它只是单纯的计算
4)“函数移动”
- 罚金的计算实质上是“借阅”对象的责任,计算所需要的信息都在“借阅”对象中,与借阅者Student无关,因而可以用“函数移动”的策略,把罚金计算方法calculateFineAndBonus()从Student移动到Rental ,这个方法不再需要参数aRental。
- 在returnedMessage中变成向aRental发送计算罚金的消息。
同时还要改变奖励积分addBonus的计算:积分是奖励Student的,是在该类实现的。
积分的计算要向借阅者——良即Student对象发送请求getStudent().addBonus()来实现。
“函数移动”的基本步骤-1:
- 检查被源方法使用的、在源类中定义的所有的特性,考虑是否要移动。
- 检查源类的子类和超类是否声明了要移动的方法。如果有其他的方法申明,则不能移动,
- 选择一个更合适的方法名,在目标类中定义。同时,把源方法的代码复制到目标方法中,然后适当调整代码。如果移动的方法要使用原来的资源,要考虑如何从目标方法中引用源对象。
- 决定如何从源代码中正确引用目标对象。
- 把源方法改成派遣方法。例子中就是把calculateFineAndBonus(aRental)中的参数作为发送消息的(派遣)对象,即改为aRental .calculateFineAndBonus()。
- 决定是否删除源方法或者把它留作派遣方法。如果移除源方法,则要把它的所有引用都替换成引用目标方法。
- 通过编译并测试。
2.用多态替换分支语句
改造最为复杂、庞大的代码calculateFineAndBonus()中的分支语句。分支语句的形成是因为罚金和奖励积分的计算与书籍借阅的天数、书籍价格、特别是书籍种类等多个条件有关。有3中基本技术:
- 用类替换类型码。
- 用子类取代类型码。
- 用多态取代类型码。
1)用类替换类型码
- 使用符号名称或枚举类型表示不同的书籍种类,进而实现不同的计算操作,这是过程式语言(如C语言)最基本的特性,也是分支语句的典型应用场景,它提高了代码的可读性。
- 但是,编译程序在分析程序、检查类型到分支语句的条件时,检查的是符号名称后面隐含的数值,而不是符号名称。任何一个类型码作为参数的函数期待的是数值,没有任何机制强迫使用符号名称,所以,符号名称可能成为错误源。
2)用子类取代类型码
- 如果类型码不影响程序的行为,则在面向对象中使用“用类替换类型码”的策略。但是,如果类型码影响程序行为,最好使用多态来处理多变的行为。程序中有分支条件语句就表明这种情形的出现。
- 优点是,把变化行为的知识从类的使用者搬迁到了类本身。要是增加新的变化,只需增加子类。
- 若编程语言没有多态机制,就只能检查所有条件、改变检测的条件,所以,这个重构特别适合代码扩展,支持变化。
3)用多态取代类型码
- 如果类型码影响程序行为,最好使用多态来处理多变的行为。基本思路是把原先的方法设计成抽象方法,在子类中重载每个分支条件。如果不同对象的行为根据条件发生变化,多态就可以避免编写这些条件。
- 优点是,避免在程序中多处出现一组相同的条件。设计新的子类来提供合适的方法,而类的使用者无须知道子类的存在,减少了程序的耦合性,便于更新和扩展。
- “用多态取代类型码”改造方法calculateFineAndBonus ,消除其中的分支语句。
- 把类Book改造为抽象类,新增三个子类NewBook、Reference、TextBook。
- 删除类Book中的三个静态符号常量及类型变量intcategory。
- 完成新增类的构造方法、成员变量访问方法是toString。
- 观察Rental中calculateFineAndBonus的任何一个分支:计算与书籍类型有关,语句finedAmount +=(getDaysRented()-30)*getBook().getPrice()*O.001;中的罚金系数与书籍的价格和类型有关。运用“提炼函数”的方式,用getFine()替换表达式getPrice()*0.001在基类Book中将其声明为抽象方法,三个子类分别实现它,采用不同的罚金系数0.001、0.005和0.01。
- 同样处理计算基本罚金的语句finedAmount+= 1,不同类型书籍的基本罚金、奖励点也不同。在Book及其子类中增加一个方法abstract doublebaseFine(),在其子类中实现该方法。在Book中增加一个奖励积分的方法abstract int getBonus(),在其子类中实现该方法。测试改动的代码,确保修改正确。
- 考虑方法Student中的returnedMessage,它通过for循环语句对每个借阅对象计算罚金和奖励积分。对循环语句的优化可以提高程序运行效率:把循环体内的代码移到循环体外。前提是代码与循环变量没有直接关系,或者可以简单变换。本例中就是while语句。
- 可以运用函数抽象把循环封装到一个方法中,尝试ExtractMethod。
- 两个变量的计算都缠绕在一个循环语句中。
- 由于for循环语句中还包含不确定的循环语句,抽象方法时要特别注意。一个基本原则是首先分离与循环变量无关、与其他变量无关的计算,然后再抽取与嵌套循环有关的、非独立的变量的计算。
- 用多态取代类型码——把循环封装到方法
- 首先,构造方法getRentalList(),它产生显示退还的借阅清单信息。
- 其次,构造方法getTotalFine(),它计算罚金总计。
- 可以可继续构造discountedBonus(),略。
- 最后,回归测试确保新增和重构的代码都要通过编译、产生正确的结果。
- 对通过了增式集成与重构的代码进行综合测试,包括回归测试、功能确认测试。重构后的类图和交互图。
- 重构后returnedMessage及其中计算罚金、计算奖励积分的交互图。
- 重构后的优点
- 原先复杂的功能划分到若干相互交互的、短小的方法中。程序具有面向对象的特点:
- 庞大复杂的功能分解到类,通过对象之间的协作完成;
- 功能单一的代码容易复用;
- 松散耦合的类容易扩展和维护。
6.1.2 代码重构概述
1.重构的基础
- 在极限编程或其他敏捷方法学中,重构常常是软件开发循环的一部分∶开发者轮流增加新的测试和功能,重构代码来增进内部的清晰性和一致性。
- 重构既不修正错误,也不增加新的功能,主要用于提高代码的可读性或改变代码内部结构与设计,使其更容易维护。
- 重构代码可以是结构层或语意层,不同的重构手段可能是结构的调整或语意的转换,但前提是不影响代码在转换前后的行为。
代码重构主要有三个时机:
- 给程序增量地添加功能的时候定
- 位错误的时候
- 评审代码的时候
造成重构的主要原因是:
- 改进软件设计
- 使软件更容易理解
- 有助于查找错误
2.再识重构
- 重构代码把常见的、证明切实有效的重构模式——重构的目标和重构的步骤——进行归档分类,形成了软件开发最佳实践。
- 重构代码时要记住以下三点。
- 不要为了重构而重构。
- 为理解而做简略重构。
- 不要顾虑复查或测试对代码的改动。
6.2 软件交付
6.2.1 构建与打包
- 软件开发和软件维护这两个阶段之间的开发活动统称为软件交付。
- 其作用是让最终用户使用开发的软件。
- 软件交付的基本活动包括构建、打包、发布、安装和部署。
1.开发环境和运行环境
- 软件交付的核心是把程序从开发者的机器上迁移到用户的机器上。
- 应用程序是在包含计算机操作系统、应用程序、配置文件等的软件环境上运行。
- 软件环境可以分为开发环境、运行环境、测试环境等。
2.虚拟机
- 大多数现代编程语言能在不同的计算机、工业控制机、平板设备、手机、嵌入式设备等上运行,尽可能地独立于计算机及其操作系统(平台或环境)。
- 虚拟技术为每个应用创建一个运行的容器,把应用程序与计算平台隔离,从而实现了应用的跨平台运行。
- 例如,Web服务器,Java 虚拟机(JVM ) ,AndroidDalvik VM虚拟机,CLR ( Common LanguageRuntime ) 。
3.构建和打包
- 软件交付前的活动是编译和连接然后把应用代码构建(build )成可运行的程序(如C程序)。
- 软件交付的首要工作是把构造的程序从开发环境中分离出来并打包。
- 程序打包就是创建计算机程序的安装,即把各种编译好的文件、依赖的资源、数据和配置文件等组装成一个可以自行解压的压缩文件,允许软件文件在多个计算机上安装运行。
4.安装活动
- 确保满足必要的系统需求;
- 检查软件的版本;
- 创建或更新程序文件和目录;
- 增加配置数据,如配置文件、Windows注册项或环境变量;
- 使用户能访问软件,如链接、快捷键或书签;
- 配置自动运行的组件,如Windows服务;
- 激活产品,更新软件版本。
6.2.2 实现构建自动化的工具
- 现代的IDE 如Eclipse、NetBeans、Visual Studio都包含了build 操作,利用目录结构管理开发的代码、利用各种配置指定需要资源的信息等,容易完成项目代码的编译和连接。
- IDE通过可视化完成资源配置、路径设置、外部库引入等代码的管理,简化了构建活动。
- 使用Ant、make 等独立的构建工具可以得到与操作系统或IDE无关的代码。
- 更有意义的是,通过编写脚本或批处理文件能使程序的构建工作自动化。
- 经典构造工具make
- Java的构建工具Ant
Java的构建工具Ant
Ant ( Another neat tool)是一个跨平台的Java 库和命令行工具,用编写的脚本执行编译、汇编、测试和运行等构建任务。
Ant具有如下特点:
- Ant是用Java编写的,而且它的构建规则用扩展性标识语言XML ( Extensible Markup Language)描述,具有跨平台性;
- Ant由一系列任务组成,这些任务是用XML文件描述的脚本,结构清晰,容易书写和维护;
- 由于Ant的跨平台性和操作简单的特点,它很容易集成到一些开发环境和技术中,包括持续构建技术、Android开发环境。
Jar包:
- 开发的Java程序有两种基本的应用方式:供其他开发者复用的代码,独立于开发环境的可运行程序。
- 无论哪种形式,都要将Java程序(类)打包成jar 文件(Java Archive File,Java档案文件)。
- jar文件是一种压缩文件,可以用WinRAR、WinZip 等打开。
- jar与ZIP的区别是: jar中包含一个META-INF/MANIFEST.MF的清单文件,作用类似Makefile,是用XML格式描述的。
6.2.3 Java程序的打包与交付
- 复用Java代码的打包
- 可运行Java程序的打包
- 在Windows上直接运行Java程序的制作
复杂的软件则需要系统地部署和实施。软件部署包含三个任务:
- 配置并管理应用软件要运行的环境(硬件配置、系统软件、外部服务);
- 安装应用程序的正确版本;
- 配置应用,包括数据或需要的状态。应用软件的实施,包括客户化开发(对软件产品按照客户要求更改)、应用部署、应用初始化(设置用户角色和权限、录入基本数据等)、人员培训等活动。
6.3 讨论与提高
6.3.1 测试层次
为了能系统、全面地测试软件,测试可以先从程序的基本单元开始,然后按照一定方式——如软件集成的顺序,逐步测试集成后的程序,直至测试完成整个软件。
(1)单元测试
- 对程序基本单元(函数、方法、类或对象及构件或服务)进行的测试。
- 关注程序单元的基本功能、算法实现、数据结构等内部组织结构。
(2)集成测试
- 对两个及以上相互关联的程序单元测试。
- 具有调用关系的函数、具有继承或聚合关系的类,以及具有合作关系的子系统、软件使用或依赖独立的外部系统,甚至是软硬件的交互。
- 集成测试的重点是检测程序模块的接口、模块之间的交互及开发的软件与外部系统的交互。
(3)系统测试
- 对整个软件的测试称为系统(级)测试。
- 重点是检测软件是否满足了用户需求、完成既定的功能和任务。
- 同时,还要检测运行速度、存储占用、事务处理、数据量及是否稳定、可靠、安全、易用、易维护等非功能需求。
( 4)验收测试
- 确保软件准备就绪,最终用户可以在用户环境执行软件的既定功能和任务。
- 验收测试是在产品发布之前进行的测试活动,也称交付测试。
6.3.2 测试驱动开发
含义:
- “测试驱动开发”(Test-Driven Development,TDD)或“测试先行开发”,是一种不同于传统软件开发流程的新型的开发方法。
- 它要求在编写某个功能的代码前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个软件的开发。
- 有助于编写简洁可用和高质量的代码,并加速开发过程。
测试驱动开发与传统的软件开发的对比
测试驱动开发的基本过程如下:
- 编写一个测试,检测待测程序是否满足一个(新增)功能。
- 运行测试——由于还没有代码,甚至可能都不能通过编译!
- 编写待测程序足够的代码,并通过编译。
- 编写待测程序满足测试,直至测试通过。
- 必要的话重构代码,用测试验证。
- 重复上述步骤,直到更多的需求都编码实现并通过测试。
理解TDD
- TDD的原理是在开发功能代码前,先编写单元测试代码,确定需要编写什么产品代码。
- TDD整合了需求分析、设计、重构和质量控制,通过测试推动整个开发进程。
- TDD的目标不是测试软件。
- TDD考虑的是用户需求(对象、功能、过程、接口等),通过测试用例框架帮助设计和编码实现需求,实现持续的验证。
TDD具有如下特点:
- 只开发满足需求的软件。
- 促使实现松耦合的设计。
- 尽早地避免、发现和修改错误。
- 与软件同步的文档。
- 提供了快乐工作的基础。
第7章 GUI软件构造
7.1 GUI简介
7.1.1 GUI发展轨迹
- 图形用户界面(GUI ,Graphics User Interface )
- 1964∶恩格尔巴特(Douglas Engelbart,1997年图灵奖获得者)发明鼠标
- 1973∶施乐公司(xerox )的Palo Alto研究中心(PARC)的Alto计算机系统——现代GUI的雏形
7.1.2 Java GUI的构造工具
1 .Java Swing工具包
Swing作为一个用于开发Java图形界面应用程序的经典开发工具包,jdk1.2 即包含了Swing。
2 . Java GUI可视化设计工具——WindowBuilder
- 已成为Eclipse的插件
- 官网: http://www.eclipse.org/windowbuilder/
7.2 GUI的基本元素与设计规范
7.2.1 GUI的基本元素
1.窗口与对话框
- “窗口”是一种容器类,而且是顶层容器类。所谓顶层容器类,即可以容纳其他容器或组件、本身可独立显示、不依赖其他容器类的类。
- 窗口一般是指javax.swing.JFrame类,依次派生自java.awt.Window类、java.awt.Container类。
- “对话框”是一个与窗口类似的顶层容器类,对话框与普通窗口不同。对话框用于交互,一般会向用户提示一些信息并能获取用户的信息反馈。
- javax.swing.JDialog是一个对话框类,它依次派生自java.awt.JDialog类、java.awt.Window类,与jFrame类同源。另一个可以创建对话框的类JoptionPane,它有4个常用的静态方法,用于创建4种常用的对话框,如表7.2所示。
2.菜单和工具条
菜单包括:
- 菜单条(JMenuBar )
- 菜单(JMenu )
- 菜单项(JMenuItem )
工具条(JToolBar ):
- 是一个容器类,可以放置各种常用的工具或组件。
3.图标
在某些场合,使用图标( icon )比使用文字更加简洁、更加容易辨析。
4.组件
派生自Component 的类称为“组件”,大致分三类:
- 顶层容器类(派生自Window ),如窗口或对话框;
- 非顶层容器类(如菜单条和工具条),称为中间容器类;
- 基本组件:必须放置在容器上的那些非容器类。
基本组件:
- 标签(JLabel )
- 文本域(JTextField )
- 密码域(JPasswordField )
- 单选按钮(JRadioButton )
- 复选框(JCheckBox )
- 组合框(JComboBox )
- 文本区域(JTextArea )
- 按钮(JButton )
- 进度条
- 滑块
7.2.2 GUI基本设计规范
1.界面合理
合理的界面:合理的布局和颜色搭配
- 窗口中的组件要排列整齐,既不能太拥挤,也不能太空旷
- 界面的颜色要与软件的功能、界面布局搭配协调,且遵循人们对于颜色的习惯性理解。
2 .风格一致
- 应使用标准的组件、明确定义的术语,与用户的习惯认知和知识领域一致。
- 界面信息表现方式前后一致
- 不同功能模块的操作方式、字体、标签风格、颜色方案、错误提示信息等方面应一致
3.元素标准
(1)窗口与对话框的标准
- 能适应不同分辨率的屏幕,能正确地关闭和缩放
- 组件在窗口缩放时应具有正确的位置
- 组件应具有恰当的焦点顺序
- 根据不同场合正确使用不同类型的对话框
(2)菜单和工具条的标准
- 菜单应提供线索以帮助用户识别、而非强迫用户去记忆
- 菜单项措辞准确、顺序合理
- 菜单的层次不宜过多
- 工具条应具有最常用的功能
- 应允许用户自定义工具条的功能
- 工具条的功能按钮可采用图标或文字相结合的方式
(3)图标的标准
- 图标应轮廓清晰、与背景区分明显
- 图标应采用能表达出实际功能的图形,避免过度抽象
- 图标的设置符合多数用户的表达习惯和使用习惯
(4)基本组件的标准
- 组件间距合理、属于同组的组件区域明显
- 文本编辑区域的组件尺寸应符合大多数情况的文字长度
- 按钮的位置符合用户使用习惯
- 根据功能正确使用如单选按钮、复选框等不同的输入组件
- 组件不可用或内容不可编辑时,要设置相应属性,以免误导
7.3 Java GUI设计模式
7.3.1 观察者模式
- 在观察者模式中,那些状态将会发生改变的对象作为“被观察者”或“观察对象”( Subject )
- 当被观察者的状态发生改变时,则会通知另一类被称为“观察者”( Observer )或“侦听器”(Listener)的对象
- 观察者会根据新状态做出相应的反应
- 被观察者可以将不同的观察者加入到对不同状态变化的侦听对象列表中。
弊端︰
- 观察者一方面接收状态变化的通知,另一方面还要做出相应的动作——执行相应的业务功能,这等于说在一个类当中整合了输入解析和业务逻辑两部分功能,不符合单一职责的设计原则。
- 这种功能耦合的设计,使得观察者类把输入解析和业务逻辑捆绑在一起,不能使用同一个观察者类处理不同的业务逻辑。
7.3.2 MVC模式
7.4 事件驱动编程
- 事件驱动编程主要用于GUI和其他为响应用户的交互做出特定动作的应用程序。
- 在事件驱动编程的应用中,通常有一个监听事件的主循环,一旦监测到其中的一个事件,就触发一个函数调用,执行相应的动作。
- 事件驱动编程可以理解为实现MVC模式的一种技术方案
7.4.1 事件捕捉与处理
- 用户对于组件的动作或组件状态的改变都可以列入事件
- 事件源:发生动作或状态改变的组件
- 在Swing 中,采用事件类(Event)表示某种事件,采用了侦听器类(Listener)来捕捉与处理事件。侦听器类对不同的事件进行侦听,当发生特定事件时,即进行相应的处理。
Swing中常用的事件有窗口事件、动作事件、键盘事件、鼠标事件、文本事件、焦点事件等,如表7.3所示。
7.4.2 焦点事件与Tab顺序
- 焦点就是获取键盘或鼠标输入的能力。
- 组件获得焦点,即可获取键盘输入,而组件失去焦点,键盘输入的接收方也就随之改为其他组件。
- 焦点如何切换呢?常用方法︰
- 使用Tab键,每按一次Tab键,焦点按照一定的次序在组件之间切换;
- 使用鼠标单击组件,被单击的组件获得焦点,而原先获得焦点的组件则失去焦点;
- 使用其它键,如︰回车键
- “默认焦点”指当GUI程序(或网页)运行时,无须用户操作而首先获得焦点的那个组件。
- 默认焦点应当遵循以下两条设计原则:
- 默认焦点应当处于用户最经常选择的最先输入组件。
- 对于具有连续多个文本输入组件的界面,焦点切换顺序应当从第一项输入组件开始,按照从左至右、由上到下的顺序依次切换
7.5 讨论与提高
7.5.1 GUI的设计原则
- 关注用户及其任务,而不是技术
- 首先考虑功能,然后才是表示
- 对任务的看法与用户保持一致
- 设计要符合常见情况
- 不要分散用户对目标的注意力
- 促进学习
- 传递信息,而不仅仅是数据
- 设计满足响应需求
- 通过用户试用发现并gai'zheng'cuo'w
7.5.2 GUI的测试
1、测试的困难
- 程序流程图不可预知
- 输入空间大,测试用例多
- 传统方法难以覆盖
2、GUI测试模型
(1)基于有限状态自动机的测试模型
- 有限状态自动机(Finite State Machine, FSM)是一个具有离散输入/输出的数学模型,在任何时刻都能处于某个特定状态。
- GUI组件中属性值的改变使得系统的状态发生改变,这些状态就可以描述为FSM模型的状态集合。
- 状态转化图是FSM的图形化表示,提出的测试方法适用FSM。但对复杂的GUI程序,状态数量庞大,FSM建模成本很高,维护困难,降低了测试效率。
(2)基于事件流图和事件交互图的测试模型
- 事件流图(Event Flow Graph, EFG)是一个有向图,它以GUI的窗口和事件作为建模元素,顶点表示事件,边表示事件之间的关系,图中的一条路径就是一条事件流。
- 在事件流图中,有一些事件与其它业务逻辑没有交互,这类事件往往不是由应用软件开发人员编写的,而是由底层GUI框架提供的。这些框架出现在事件流图中,会增加不必要的测试用例。可以将这类事件移除,转化成事件交互图(Event Interaction Graph, EIG),仅关注业务逻辑间发生交互的事件,依据事件交互图可以设计测试。
- 将被测试的GUI软件按照事件关系建立事件流图,测试需要满足以下覆盖准则:
- 事件覆盖。每个事件至少执行一次。
- 事件交互覆盖。要求一个事件被执行后,所有与之交互的事件至少被执行一次。
- 长度为n的事件序列覆盖。考虑一个事件在不同的上下文序列中执行,结果可能是不同的,这个覆盖要求长度为n的时间序列至少执行一次,也就是覆盖事件流图中长度为n的路径。
3、GUI测试用例的生成
(1)录制/回放技术
将用户对被测软件的操作录制为测试脚本,在测试时回放这些脚本并检验软件的事件响应、业务逻辑是否出错。也可以编辑,修改录制的脚本,以应对软件功能的改变。这一过程不对测试的软件进行建模,而是需要大量的人工辅助,所以不是热点,但在业界比较成熟,是应用较广的一类测试用例的生成方法。
(2)基于FSM生成测试用例
能够描述GUI的状态及状态之间的变迁关系,因此可以复制GUI生成测试用例。但是建立FSM模型需要大量的手工工作,且复杂的GUI会导致FSM的状态数量庞大,因此建模与生成测试用例的难度和工作量尚令人不满意。
(3)基于事件流图生成测试用例
例如,事件流图中一条连接就是测试中的一个测试用例序列,对事件流图使用遍历算法即可生成GUI测试用例。这种方法依然会造成测试用例冗余,同时也需要人工操作。
第8章 应用数据库
8.1 数据库概述
- 数据库是长期存储在计算机内、有组织、可共享的大量数据的集合。它的目的是提供—种可以方便、高效地管理数据库信息的途径
- 数据库管理系统(DBMS)是位于用户(含应用程序)和操作系统之间的一种数据管理软件,负责数据的组织、存储和管理
- 数据库、DBMS、应用程序及数据库管理员构成的系统称为数据库系统
8.1.1 关系数据库
- 数据库领域中最常见的数据模型有:层次模型、网状模型、关系模型、面向对象模型、对象关系模型
- 其中,关系数据库系统是支持关系模型的数据库系统
- 按照数据模型的三个要素,关系模型由关系数据结构、关系操作集合和关系完整性约束三部分组成
1.关系数据结构
- 关系模型中数据的逻辑结构就是一张二维表。关系数据库由表的集合构成,每个表都有唯一的名字
- 元组(行)、属性(列)、域、码、候选码、主码、主属性、组合码、全码、外码、关系模式.
2.关系操作集合
3.关系完整性约束
关系模型中有三类完整性约束:实体完整性、参照完整性和用户自定义完整性。实体完整性和参照完整性是关系模型必须要满足的完整性约束条件,有DBMS支持,进行自动检查。
- 实体完整性(Entity Integrity ) :若属性(一个或一组属性)A是基本关系R的主属性,则A不能取空值。
- 参照完整性(Referential Integrity ) :若属性或属性组F是基本关系R的外码,它与基本关系S的主码Ks 相对应(基本关系R和S不一定是不同的关系),则对于R中每个元组在F上的值必须为空值或者等于S中某个元组的主码值。
- 用户自定义完整性(User-defined Integrity ):关系数据库系统应该根据应用环境的不同,满足用户对数据关系之间的特定的约束条件。
8.1.2 关系数据库的数据模型
关系数据库管理系统支持数据库的三级模式结构
内模式对应存储文件,模式包含数据库中最重要的基本表,外模式对应于部分基本表和由基本表导出的视图
8.2 结构化查询语言SQL
8.2.1 SQL概述
SQL ( Structured Query Language,结构化查询语言)包括以下几个部分:
- 数据定义语言(Data Definition Language ):针对数据库和基本表的结构所做的定义
- 数据操纵语言(Data Manipulation Language) :对已定义的数据增、删、改、查
- 数据控制语言(Data Control Language )
- 完整性约束(Integrity )
SQL 实现了关系模型的一些主要特点:
- SQL是集数据定义语言DDL、数据操纵语言DML和数据控制语言DCL功能于一体的查询语言。
- SQL是高度非过程化的查询语言。用户只需指明“怎么做”、“想要什么”,而无须说明查询的步骤。
- SQL采用的是集合的运算方式。操作对象是元组的集合,插入、删除、修改的对象及查询得到的结果都可以是元组的集合。
- SQL使用动词来表达功能。核心动词9个。
8.2.2 创建基本表的CREATE语句
CREATE TABLE语句定义基本表
CREATE TABLE<表名>(
<列名><数据类型>[列级完整性约束条件]
[,<列名><数据类型>[列级完整性约束条件]]
……
[,<表级完整性约束条件>]
);
8.2.3 插入元组INSERT语句
INSERT插入语句的一般格式为∶
INSERT INTO<表名>[(<属性列1>,<属性列2> ....….)]VALUES(<常量1>,<常量2>....);
8.2.4 删除元组DELETE语句
DELETE语句的一般形式为∶
DELETE FROM<表名>[WHERE<条件>];
8.2.5 更新元组UPDATE语句
UPDATE语句的一般形式为:
UPDATE<表名>SET<列名>=<表达式>[<列名>=<表达式>].….[WHERE<条件>];
8.2.6 选择元组的SELECT语句
SELECT语句的功能是查询数据库中存储的数据。SQL查询的基本结构由三个子句组成:select、from和where。查询的基本结构为∶
SELECT [ALLIDISTINCT]<目标列表达式>[<目标列表达式>]...FROM<表名或视图名>[<表名或视图名>]...
[WHERE<条件表达式>]
[ORDER BY<列名>DESC | ASC]
[GROUP BY<列名>HAVING<条件表达式>;
8.3 数据库的开发过程
数据库系统开发分为6阶段:
- 需求分析阶段;
- 概念结构设计阶段;
- 逻辑结构设计阶段;
- 物理结构设计阶段;
- 数据库实施阶段;
- 数据库运行和维护阶段。
1、需求分析阶段:
- 首要任务是通过详尽调查明确用户的组成,包括用户的身份、权限、职责及分配的工作等;
- 其次,详细调查各用户组对数据库的操作,包括各用户组将对数据库输入的信息、如何使用这些信息、输出什么信息及其格式等;
- 将输入的信息分类存储,以便在逻辑分析阶段生成数据库模式,即基本表;
- 最后,需要确定系统的边界。对前面调查的结果进行初步分析,确定哪些功能由计算机完成或将来让计算机完成,哪些活动由人工完成。
(1)用户的组成
(2)用户需要的信息
- 学生身份信息∶记录学生的编号、姓名等。
- 家长身份信息∶记录家长的编号、姓名等,以及家长和学生之间的关系。
- 教师身份信息∶记录教l师的编号、姓名等,以及教师和学生之间的关系。
- 管理员身份信息∶记录管理员的编号、姓名、权限等。
- 算术题目:记录所有题目及其计算结果。
- 学生成绩:记录学生完成某套题目的成绩。
(3)系统边界
- 系统可以自动生成算术题目并得到运算结果,可以完成答题结果的检查、统计成绩分布;
- 可以根据人工设定的难度级别来生成相应难度的算术题目;
- 无法分析错误题目的出错原因。
2、概念结构设计阶段
- 将需求分析得到的用户需求抽象为信息结构(概念模型)的过程就是概念结构设计,是整个数据库设计的关键。
- 设计的概念结构一定要真实、充分地反映现实世界,包括事物和事物之间的联系,满足用户对数据的需求。
- E-R模型是描述概念模型的有力工具。“E”表示实体型,E-R图中用矩形表示,矩形框内写明实体名;用椭圆表示实体的属性,并用无向边将其与相应的实体连接起来;“R”表示联系,在E-R图中用菱形表示,菱形框内写明联系名,并用无向边分别与有关的实体型连接起来,同时在无向边旁标记联系的类型。
- 两个以上的实体型之间联系类型可以分为三种:
- 一对一联系(1:1 )
- —对多联系( 1:n)
- 多对多联系( m:n )
3、逻辑结构设计阶段
- 逻辑结构设计阶段的主要任务是将概念结构设计阶段设计好的基本E-R模型转换为数据库管理系统所支持的数据模型相符合的逻辑结构。
- E-R图转换为关系模型规则(步骤)如下:
- 一个实体型转换为一个关系。实体名称(属性、主码)即关系名称(属性、主码)。
- 一个联系转换为一个关系。联系名称(属性)即关系名称(属性),和联系相连的实体主码也是关系的属性。(1:1:任取一个实体的主码为关系的主码。1:n:取n对应的实体主码为关系的主码。m:n :两个实体的主码为关系的组合码。)
- 合并相同主码的关系。
4、物理结构设计阶段
- 数据库在物理设备上的存储结构和存取方法称为数据库的物理结构
- 物理结构设计主要包括确定数据的存放位置;确定关系、索引、聚簇、日志、备份等的存储安排;确定系统配置等
- 设计存储结构时,一般考虑存取时间、存储空间利用率和维护代价等三方面因素
- 存取方法一般采用索引存取方法或聚簇存取方法
5、数据库实施阶段
- 数据库实施阶段的主要任务是数据的载入和应用程序的编码与调试。
- 首先要强调基础数据的正确,数据形式、组织方式、数据结构的合理、清晰。
- 为提高数据载入效率和准确度,应尽量采用人工和计算机辅助相结合的方式载入数据。
- 载入数据之后,要试运行数据库,对数据库进行测试,主要包括功能测试和性能测试。
- 功能测试需要运行数据库应用程序,执行对数据库的各种操作,测试应用程序的功能是否达到设计要求。
- 性能测试主要检查数据库和应用程序运行中的相关物理参数值,测试其是否达到设计要求。
6、数据库运行和维护阶段
- 主要任务是维护数据库的稳定,保证数据库平稳运行
- 同时,对数据库设计进行评价,听取、总结、分析用户的需求,必要时可以对数据库进行适当的补充和修改
8.4 编程操作数据库
JDBC ( Java DataBase Connectivity , Java数据库连接)是一种用于执行SQL语句的Java API,它由一组用Java语言编写的类和接口组成,可以为多种关系数据库提供统一访问。
使用JDBC操作数据库的主要步骤是:
- 与数据库建立连接;
- 向数据库发送SQL语句,需要java.sql包的支持;
- 处理SQL语句返回的结果。
8.4.1 连接数据库
准备工作:
- 安装数据库管理系统Microsoft SQL Server 2012
- 安装Java程序编译环境Eclipse
- 在微软官方网站下载Microsoft SQL Server 2012 JDBCDriver,需下载4.1(及以后)版本的JDBC Driver以适应SQLServer 2012及更新版本
连接数据库:
(1 )JDBC驱动程序并未包含在Java SDK中。因此如果要使用该驱动程序,必须将classpath设置为包含sqljdbc.jar文件。
如果classpath缺少sqljdbc项,应用程序将引发“找不到类”的异常。
WINDOWS系统中,假设我们将下载的sqljdbc驱动文件放在C:Program Files文件夹中,则classpath的设置实例为∶classpath=.;%ProgramFiles%sqljdbc41.jar。
(2)设置SQL Server服务器。Microsoft SQL Server
2012采用默认设置即可,一般不需要配置。如果需要配置端口,可以按照以下步骤∶
a )“开始”→“程序”→“Microsoft SQL Server 2012"→“配置工具”→"SQL Server配置管理器”→"SQL Server 2012网络配置”→"SQL2012的协议”
b )如果“TCP/IP”没有启用,右键单击选择“启动”
C )双击“TCP/IP”进入属性设置,在“IP地址”里,可以配置“IPAIl”中的“TCP端口”,默认为1433
d )重新启动SQL Server或者重启计算机,以完成SQLServer服务器的配置
(3)连接数据库
(4)创建工程,连接创建的数据库。连接成功后,向数据库中发生指令,访问数据库,返回结果显示在控制台。
8.4.2 查询数据库
1、一般查询
- 单查询和多查询
2、控制游标
SQL面向集合数据,一条SQL语句可以产生或处理多条记录。有时需要在结果集中前后移动、显示结果集中的某条记录或随机显示若干条记录等等,因此引入游标的概念。游标是系统为用户开设的一个数据缓冲区,用于存放SQL语句的执行结果。
8.5 讨论与提高
8.5.1 事务与并发
事务是数据库应用程序的基本逻辑单元它是用户定义的一个数据库的操作序列,这些操作要么做全,要么全不做,是一个不可分割的基本单元。在一个多用户的数据库应用系统中,对数据库的操作通常会要求多个事务并发执行,或者需要使用一个事务中的多个资源。
1、在JDBC中批量执行SQL
JDBC的批量更新特性允许多个更新操作被传递到DBMS上来简化过程。事务通常有多个SQL语句来执行完成一个任务,而在JDBC和DBMS之间只有一个调用被交换,这就要求我们一次传递多条更新语句到DBMS,这减少了应用程序和DBMS之间的通信量,因此语句将很快被执行到。
2、选择合适的隔离级别
隔离是指一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能相互干扰。事务的隔离级别越高,越应该小心避免并发的冲突。
隔离级别 | 读脏数据 | 不可重复读数据 | 虚读(丢失修改) |
TRANSACTION_NONE | 不支持事务 | 不支持事务 | 不支持事务 |
TRANSACTION_READ_UNCOMMITED | √ | √ | √ |
TRANSACTION_READ_COMMITED | × | √ | √ |
TRANSACTION_REPEATABLE_READ | × | × | √ |
TRANSACTION_SERIALIZABLE | × | × | × |
8.5.2 使用存储过程
存储过程是在数据库系统中,一组为了完成特定功能的SQL语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数来执行存储过程。使用存储过程可以在数据库层级别提高数据库的性能。
8.5.3 查询优化——消除不必要的循环
循环会造成大量的网络通信,降低查询效率。为了减少不必要的循环,可以在查询之前,准备好需要查询的ID,以逗号分割后存放在字符串变量中,然后放到IN谓词中。
8.5.4 测试数据库
测试的主要内容是数据的增删改查及其组合的序列操作。另外是SQL:
- 编码是否符合规范、标准
- 使用增删改查语句测试程序的准确性,使用Constraints测试数据的1完整性
- 用SQL测试连接表、数据库对象、视图、存储过程、触发器、用户自定义函数等
- 在脚本语言中用SQL代码测试数据库模式、数据库及其驱动程序、表和列的类、默认值和规则、码和索引等。
第9章 基于复用的软件构造
9.1 软件复用
软件复用,是指在两次或多次不同的软件开发过程中重复使用相同或相近的软件或软件模块的过程。该软件可以是已经存在的软件,也可以是专门的可复用软件,简称(软)构件。软件复用是在软件开发中避免重复劳动的解决方案,使得应用系统的开发不再采用一切“从零开始”的模式,而是在已有工作基础上,充分利用过去应用系统开发中积累的知识和经验,从而将开发的重点集中于应用的特有构成成分上。
软件复用具有如下优点:
- 提高生产率。
- 减少维护代价。
- 提高互操作性。
- 支持快速原型。
软件复用也面临挑战,主要如下:
- 软构件与应用系统之间的差异。
- 软构件要达到一定的数量,才能支持有效的复用,而建立软构件库要有很高的投入和长期的积累。
- 难以发现合适的软构件。
- 基于复用的软件开发方法和软件过程是一个新的研究实践领域,需要一些新的理论、技术及支持环境,目前这方面的研究成果和实践经验都不够充分。
依据复用的对象,软件复用分为两种:
- 产品复用。指复用已有的软构件,通过构件集成(组装)得到新系统。产品复用是目前复用的主流途径。
- 过程复用。指复用已有的软件开发过程,使用可复用的应用生成器来自动或半自动地生成所需系统。过程复用依赖于软件自动化技术的发展。
9.1.1 软件产品复用
- 软件产品的复用包括代码、设计、测试数据和需求规格等。
- 代码复用是最基本、最普通的软件复用形式,包括可执行代码和源代码。
- 可执行代码和源代码称为软构件或构件。
- 人们把一些相关的可复用的程序编译、打包、压缩在一个程序库,方便使用。
- 设计文件是设计决策的通用表示,其本质是捕获的设计/解决问题的知识。与代码不同,设计不可执行。与需求规格不同,设计捕获结构信息而非功能信息。
- 设计表示成用不同方式产生具体设计的模式。
- 不像函数或模块,设计不能用其功能特性检索,而是通过设计解决的一组问题的特性来检索。
设计复用有三种基本途径∶
- 第一种途径是从现有系统的设计结果中提取一些可以复用的设计构件,并把它们直接应用到新系统的设计中;
- 第二种途径是把一个现有系统的全部或部分设计结果用新的语言或在新的平台上重新实现;
- 第三种途径是综合现有系统,或者根据需求重新开发一些专门用于复用的设计构件。
设计构件分为构件级和架构级:
- 软件体系结构定义了作为一组执行数据的构件聚合体的软件系统的结构。
- 构成体系结构的结构元素比程序设计语言的结构元素更加抽象,具有不同的性质。
- 它们规定了信息流、控制流或构件之间的通信协议。
- 体系结构用特殊符号表示,用体系结构特征检索。
- 可复用的软件体系结构通常是显式地复用软件体系结构
,并通过集成其他软件体系结构,建立新的更高层次的体系结构。
最常用的可复用设计是设计模式和架构模式。
测试数据和测试代码是典型的可复用件。测试数据的表示直截了当,检索测试数据的方式可以是软件系统输入域的描述,也可以是该系统功能的某个通用特征。测试代码的表示和检索同普通的逻辑代码一样。
需求规格是提取用户需求、用某种符号记录用户需求的结果。规格书可以用自然语言表示,也可以用形式化符号(一阶逻辑、公理系统、形式化语言)或混合式。规格书可以通过它们俘获的功能特性检索,也可以复用在构建复合规格书。可复用件必须组织成复用库,以便方便地使用。要有效地使用复用库,必须清楚地理解库的内容,这样才能决定库能否满足需求。
库的内容有不同的组织和定义方式,可以分为三种类型。
( 1)通用构件。(2)特殊领域构件。(3)特殊应用代码。
9.1.2 基于复用的软件开发
软件复用必须解决三个基本问题:( 1)必须有可复用的对象;
(2)所复用的对象必须是有用的; ( 3)复用者要知道如何使用被复用的对象。
基于复用的软件开发改变了传统的软件开发过程和技术,它包括两个相关的过程:
(1)领域工程:可复用软构件的开发(Development forReuse )或面向复用的软件开发,是产生软件资产的过程;( 2)应用工程:基于软构件的应用系统构造(集成和组装)(Development with Reuse)或基于复用的软件开发,是使用软件资产生产新系统的过程。
领域工程是应用工程的基础,它的目标是建立可复用的软构件库。
基于复用的软件开发:
- 首要任务是把用户需求转换成系统规格,特别是要按照可复用构件修改系统规格。
- 系统设计的核心是软件体系结构。
- 设计要依据已有的构件,在系统规格、应用架构和可获得的构件之间做出妥协和平衡。
- 其中关键的一步是根据系统规格、软件架构和系统设计,查找可复用的软构件。
- 最后,把可复用构件及开发的软件部分,按照软件架构组装起来。
- 最常见的方式是使用集成语言(如脚本语言Python、Unix shell、TCL/TK)把构件“粘合”起来或粘合到框架上,最终得到应有软件。
基于复用的软件开发面临一系列挑战:
- 确认复用任务及其完成这些任务的技术;
- 提供方法学和工具支持完成任务;
- 把复用任务集成到一个软件开发的工作流程。
复用任务可能是用高级语言(领域语言)说明待开发应用的规格,尽可能地复用已有的构件合成满足一组需求的应用系统。
对于目标系统的任何部分,开发者必须:
- 以支持获取可能的、有用的可复用件的方式表达这一部分的需求;
- 理解获取的软构件;
- 如果得到的软构件足够接近需求,并且具有达到足够的质量,就可以采用它。
在复用过程,理解软构件和程序是一项重要的脑力工作和成本因素。
软构件理解意味着三个因素:
- 理解它的功能;
- 理解它的工作方式;
- 理解如何修改它使其以一点不同的方式完成。
9.1.3 程序库
程序库是软件复用最基本、最普通形式。程序库是-些经常使用、经过检验的规范化程序或子程序的集合。程序库中的程序是经常使用的功能。
程序库通常是编译后的二进制可执行码或虚拟机可执行码,把它们放到目录中,设置环境变量,在程序中引入后就可以如同普通函数、对象、类等一样编程使用。
过程式语言(如C)的程序库主要是各种函数。面向对象语言的程序库统称为类库,实质是一个综合性的面向对象的可重用类型集合,包括接口、抽象类和具体类。
程序库可以是语言系统内置的,也可以是第三发独立开发的。
不同语言打包和引用库的形式略有不同。在程序中引用库的关键字有include、using、import、with等。
除了可以引用相同语言的库外,现在的编程语言也允许引入其他语言的库,最常见的是C语言的库。使用第三方开发的程序库与使用系统的程序库,本质上没有区别。通常是理解API设计、查阅API使用方式、学习示范代码及不断练习实践。
程序库不同于软件包。软件包是指具有特定功能,用来完成特定任务的一个或一组程序。软件包由一个基本配置和若干可选部件构成,可以是源代码或目标码形式。软件包本质上就是一个可以运行的软件,可以打包、压缩,便于发布。经过安装、配置,就可以使用软件包提供的功能和服务。一般而言,软件包不作为可复用件而在软件开发中使用、不能集成到开发的软件中。
9.2 设计模式
9.2.1 基本概念
- 设计模式(Design Pattern)是对给定环境下反复出现问题的一个通用的、可复用的解决方案。
- 它是可以在很多不同场合用以解决问题的一种描述或样板。模式是程序员在设计一个软件或系统时解决共同问题最佳实践的正式描述。
- 面向对象的设计模式表示类或对象之间的关系与交互,没有说明涉及的最终应用软件的类或对象。一个设计模式不是一个可以直接转换成代码的完成的设计。
设计模式涉及的抽象和应用的范围很广:
- 体系结构模式描述了很多可以用结构化方法解决的设计问题。
- 数据模式描述了重现的面向数据的问题及用来解决这些问题的数据建模解决方案。
- 构件模式,即设计模式,涉及与开发子系统和构件相关的问题、它们之间相互通信的方式及它们在一个较大的体系结构中的位置。
- 界面设计模式描述公共用户界面问题及具有影响因素(包括最终用户的具体特征)的解决方案。
设计模式有如下优势︰
- 设计模式在软件开发中提供了一种公共的词汇和理解;
- 设计模式是软件设计建立文档的一种手段;
- 设计模式通过支持软件的功能属性和质量属性来构造软件;
- 设计模式有助于建立一个复杂的和异构的软件结构;
- 设计模式有助于管理软件的复杂度。
9.2.2 基本设计模式目录
基本的设计模式有23种,分为创建型模式、结构型模式和行为型模式三种:
- 创建型模式着眼于对象的创建、组合及表示,它封装了有关系统使用的具体类的知识,但同时隐藏了如何创建和组合这些类的实例。创建型模式提供了一种机制,使对象实例在一个系统内更容易生成,并坚持在一个系统内创建的对象类型及数量方面的约束。
- 结构型模式着眼于有关如何将类和对象组织和集成起来,以创建更大结构的问题和解决方案。本质上,结构型模式用来帮助建立系统内实体之间的关系。
- 行为型模式解决与对象间任务分配以及影响对象间通信方式的有关问题。它不仅描述对象或类的模式,还刻划了运行时难以跟踪的复杂的控制流,把开发者的注意力从控制流转移到对象间的关系上来。
9.2.3 设计模式举例
一个模式描述了一个问题,使用户能够理解问题所处的环境,并列出了影响因素,用来表明在环境中如何解释这个问题,以及如何应用解决方案。描述设计模式的方式不完全一样,基本内容包括模式名称、解决的问题、设计元素及其之间的交互(通常用UML的类图描述类及其关系,用时序图描述交互关系)、应用场景、特点分析、实际例子等。
1.工厂方法模式
问题描述:
- 在一个图形编辑器中,可以绘制直线line、矩形square、圆circle 等各种图形shape。
- 不同的图形的绘制draw是不同的,但是画笔对所有图形都统一使用draw。
- 以后可能会修改某个具体图形的绘制方式(编码实现),也可能还要增添不同的图形。为了便于软件的维护和扩展,如何设计?
解决方案:
- 定义一个用于创建对象的接口或抽象类,让子类根据条件或参数决定实例化哪一个类或调用哪一个方法。
- 工厂方法模式使一个类的实例化延迟到其子类。
工厂方法模式结构:
- 工厂( Factory )是工厂方法模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
- 抽象产品(Product)是所创建的所有对象的父类,它负责描述所有实例共有的公共接口。
- 具体产品(Concrete Product)是工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
工厂方法模式的核心思想∶
- 有一个专门的类来负责创建实例的过程。它把产品视为一系列的类的集合,这些类是由某个抽象类或接口派生出来的一个对象树。
- 而工厂类用来产生一个合适的对象来满足客户的要求。
- 如果工厂方法模式所涉及的具体产品之间没有共同的逻辑,就可以使用接口来扮演抽象产品的角色;如果具体产品之间有共同的逻辑,就必须把这些共同的东西提取出来,放在一个抽象类中,然后让具体产品继承抽象类。
为更好地复用,共同的东西总是应该抽象出来的:
- 实现时,工厂方法模式可以参数化;
- 返回对象可以是抽象对象,也可以是具体对象;
- 遵循命名规则有助于开发者识别代码结构。
使用工厂方法模式的场合及益处是:
- 平行连接类的层次结构;
- —个类想让其子类说明对象;
- 一个类不预计子类,但必须创建子类;
- 一簇对象需要用不同的接口分隔开;
- 代码要处理接口而不是实现的类;
- 连接子类的方式比直接创建对象更加灵活;
- 对客户隐藏具体的类。
JDBC是工厂方法模式的一个很好的例子:
- 数据库应用程序不需要知道它将使用哪种数据库,所以它也不知道应该使用什么具体的数据库驱动类。
- 相反,它使用工厂方法来获取连接、语句和其他对象,这使得改变后台数据库变得非常灵活,同时并不会改变应用的数据模型。
在JDK中使用工厂方法设计模式的例子︰
- valueOf()方法会返回工厂创建的对象,这等同于参数传递的值;
- getInstance()方法会使用单例模式创建类的实例﹔
- java.lang.Class 中的方法newInstance()从每次调用工厂方法时创建和返回新实例;
- java.lang.Object 中的方法toString() ;
- java.lang.Class中的方法forName()。
为了使设计者使用模式来思考,可以考虑下面的方法∶
- 保证理解全局——将要建立的软件所处的环境。需求模型表达了这一点。
- 检查全局,提取此抽象层上表示的模式。
- 从“全局”模式开始设计,为将来的设计工作建立环境或架构。
- 在更低抽象层寻找有助设计方案的模式。
- 重复1到4,直到完成完整的设计。
- 通过使每个模式适应将要建立的软件细节对设计进行优化。
9.3 框架
9.3.1 基本概念
概念
- 框架是整个或部分系统的可复用设计,表现为一组抽象构件及构件实例间交互的方法。
- 框架是可被应用开发者定制的应用骨架。
- 一个框架是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类及其实例之间协作的方法,它为构件复用提供了上下文关系。
框架方法是构件技术、软件体系结构和应用软件开发三者发展结合的产物:
- 框架通常以构件库的形式出现,但构件库只是框架的一个重要部分,构件库的大规模复用需要框架。
- 框架的关键在于框架内对象间的交互模式和控制流模式。在某种程度上,将构件和框架视为两个不同但彼此协作的技术或许更好。框架为构件提供复用的环境,为构件处理错误、交换数据及激活操作提供了标准的方法。
根据软件的层次结构,软件框架分为∶
- 基础设施框架。对系统基础功能的接近完整的实现,并留有扩展余地。
- 中间件框架。对一些常用的中间件按需定制或按需扩展而成。例如,可以运行Web程序的Web 服务器。
- 应用框架。面向应用领域中应用系统的骨架,但它并不提供完整的应用软件系统的全部。如Web应用框架Struts、Android应用框架。
根据应用范围的不同,软件框架分为:
- 技术框架。致力于解决某一技术领域内的通用技术问题,并提供定制和扩展机制。技术框架又称为水平框架。水平,强调的是通用性、使用范围的广泛性。例如,Hibernate就是解决面向对象与关系数据库映射问题的技术框架;Junit是解决单元测试问题的技术框架。
- 业务框架。在特定业务领域内通用的框架。业务框架又称为垂直框架,垂直,就是强调专门化。例如,一个网络管理软件的垂直框架针对网络管理这个专门领域提供了完善的功能。
框架在软件开发中具有显著的特点:
- 应用领域内软件结构一致性好,便于建立更加开放的系统;
- 复用代码大大增加,软件生产效率和质量也得到了提高;
- 软件设计人员要专注于对领域的了解,使需求分析更充分;
- 存储了经验,可以让那些经验丰富的人员去设计框架和领域构件,而不必限于低层编程;
框架在软件开发中具有显著的特点:
- 允许采用快速原型技术;
- 有利于在一个项目内多人协同工作﹔
- 大粒度的复用有助于降低开发和维护费用、加快开发速度、提高软件生产效率、提高产品质量,而参数化框架使得适应性、灵活性增强。
9.3.2 框架和设计模式
比较:
- 框架是软件,而设计模式是软件的知识。
- 构件通常是代码复用,而设计模式是设计复用,框架则介于两者之间,部分代码复用,部分设计复用,有时分析也可复用。
- 设计模式是对在某种环境中反复出现的问题及解决该问题的方案的描述,它比框架更抽象;
- 框架可以用代码表示,也可以直接执行或复用,而对模式而言,只有实例才能用代码表示;
- 设计模式是比框架更小的元素,一个框架中往往含有一个或多个设计模式,框架总是针对某一特定应用领域,但同一模式却可适用于各种应用。
9.3.3 框架开发
面向对象系统获得的最大的复用方式就是框架:
- 一个大的应用系统可能由多层互相协作的框架组成。
- 一个基于框架开发的应用系统包含一个或多个框架、与框架相关的构件类,以及与应用系统相关的功能扩展。
- 与应用系统相关的扩展包括与应用系统相关的类和对象。
- 应用系统可能仅仅复用了面向对象框架的一部分,或者,它可能需要对框架进行一些适应性修改,以满足系统需求。
框架的开发从代码、设计和分析三个层面复用了软件∶
( 1)复用代码。
从已有构件库中建立应用变得非常容易,因为构件都采用框架统一定义的接口,从而使构件间的通信简单。
(2)复用设计。
它提供可复用的抽象算法及高层设计,并能将大系统分解成更小的构件,而且能描述构件间的内部接口。
(3)复用分析。
所有的人员若按照框架的思想来分析事务,那么就能将它划分为同样的构件,采用相似的解决方法,从而使采用同一框架的分析人员之间能进行沟通。
框架的复用周期如下∶
- 用能够与得到描述可复用件相匹配的术语说明需求;
- 搜索并获取最相关的复用件;
- 评估它们的复用能力;
- 选择最合适的候选复用件,改编它适合当前的需求;
- 把它集成到当前的应用系统。
最后
以上就是酷酷茉莉为你收集整理的软件项目管理第1章 软件开发概述第2章 模块化软件构造第3章 面向对象的软件构造第4章 数据处理的软件构造第5章 用户交互的软件构造第6章 软件重构与交付第7章 GUI软件构造第8章 应用数据库第9章 基于复用的软件构造9.1 软件复用的全部内容,希望文章能够帮你解决软件项目管理第1章 软件开发概述第2章 模块化软件构造第3章 面向对象的软件构造第4章 数据处理的软件构造第5章 用户交互的软件构造第6章 软件重构与交付第7章 GUI软件构造第8章 应用数据库第9章 基于复用的软件构造9.1 软件复用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复