- 本博客为哈工大计算机科学与技术学院大二软件构造课程的课件翻译。同时记录了部分本人上课时的学习笔记和感悟
- 该博客8500字左右,主题为4-1可复用性的度量、形态和外部表现,已经全部更新完成
- 由于水平有限,翻译可能不是特别流畅、通顺,并且存在一定错误,观点、笔记不一定完全正确,敬请各位批评指正!
本次课程目标:
- 软件复用的优点和缺点
- 面向可复用性的构造
- 一般的可重用模块的特征
- 开发便携式应用系统的方法
- 第3章介绍了软件构造的核心理论(ADT)与技术(OOP),其核心是保证代码质量、提高代码安全性。
- 本章面向一个重要的外部质量指标:可复用性——如何构造出可在不同应用中重复使用的软件模块/API?
- 4-1节探讨可复用的软件应该“长什么样”,下一节学习“如何构造”
目录
- 什么是软件可复用
- 如何度量“可复用性”
- 可复用模块的级别和形态
- 源代码级别的复用
- 模块级别的复用:类/抽象类/接口
- 库级别的复用:API/包
- 系统级别的复用:框架
- 可复用性的外部表现
- 类型可变
- 功能分组
- 实现可变
- 表示独立性
- 分解出共同行为
- 总结
1、什么是软件可复用
硬件天生被复用
软件复用
- 软件复用是一个使用现存的软件模块来实现或更新软件系统的过程
- 软件复用的两个角度
- 创造:以系统的方式创造可复用的资源 ( programming
for reuse 面向复用编程:开发出可复用的软件) - 使用:为创建新系统而以构建模块的方式复用资源 (programming with reuse 基于复用编程:利用已有的可复用软件搭建应用
系统)
- 创造:以系统的方式创造可复用的资源 ( programming
- 为什么要可复用
- 创造可重复使用而非转瞬即逝的艺术品的动力既有美学和智力上的动机,也有经济上的动机,是人类不朽愿望的一部分。
- 它把人类和其他生物区分开来,把文明社会和原始社会区分开来 (Wegner, 1989)
面向/运用可复用性的编程
回顾Lab2
为什么要可复用
- 可复用能够降低成本和开发时间
- 通过缩短软件生产周期来提高软件生产力(软件开发更快,人员更少)
- 不浪费资源做不必要的“造轮子”
- 降低维护成本(可以生产出更优质、更可靠、更高效的软件)
- 复用产生可靠的软件 (经过充分
测试,可靠、稳定)- 重用已经存在了一段时间并经过调试的函数是在稳定的子系统上构建的基础
- Reuse yields standardization
- GUI库的重用在应用程序中产生了常见的外观
- 与规则的一致性,连贯的设计。
可重用的消耗
- 可重用组件应该以明确定义的、开放的方式设计和构建,具有简洁的接口规范、可理解的文档和对未来重用的关注。
- 重用是昂贵的:它涉及跨越组织、技术和流程更改,以及支持这些更改的工具的成本,以及培训人员使用新工具和更改的成本。
面向可复用的开发
- 可重用组件的开发成本高于特定等价组件的成本。这种额外的可重用性增强成本应该是一个组织,而不是一个项目成本。
- 与特定的组件相比,通用组件的空间效率可能更低,执行时间可能更长。
运用可复用的开发
- 必须开发和维护用于体系结构、设计、文档和代码的组件管理工具(如存储库)。
- 一个关键的问题:适配性
- 可能需要将额外的功能添加到组件中。添加了这个组件后,就可以重用新组件了。
- 可以从组件中删除不需要的功能,以改进其性能或减少其空间需求
- 某些组件操作的实现可能需要修改。
JDK中的可重用库和API
Java中丰富的第三方库和API
2、如何衡量“可复用性”?
衡量可复用性
- 软件资产在不同的应用场景中重用的频率如何?(复用的机会有多频繁?复用的场合有多少?)
- 资产被使用的机会越多,其可重用性就越高。
- 编写一次,重用多次。
- 重复使用该资产需要支付多少费用?(复用的代价有多大?)
- 购买资产和其他强制库的成本 搜索、获取
- 调整和扩展它的成本 适配、扩展
- 实例化它的成本 实例化
- 改变系统中与之交互的其他部分的成本 与软件其他
部分的互连的难度
可复用性
- 可重用性意味着对构建、打包、分发、安装、配置、部署、维护和升级问题进行一些明确的管理
- 具有高可重用性的软件资产应该:
- Brief (small size) and Simple (low complexity) 小、简单
- Portable and Standard Compliance 与标准兼容
- Adaptable and Flexible 灵活可变
- Extensibility 可扩展
- Generic and Parameterization 泛型、参数化
- Modularity 模块化
- Localization of volatile (changeable) design assumptions 变化的局部性
- Stability under changing requirements 稳定
- Rich documentation 丰富的文档和帮助
3、可复用模块的级别和形态
可重用的级别
- 可重用组件可以是代码(最主要的复用是在代码层面)
- 最普遍的:大多数程序员与重用有关的问题
- 但是,从更广泛和更高级的角度来看待什么可以重用,会带来好处。
- 需求
- 设计和规约
- 数据
- 测试用例
- 文档
代码重用的类型
- White box reuse 白盒复用:源代码可见,可修改和扩展
- 代码本身可用时,代码的重用。通常需要一些修改或适应
- 优:您可以自定义模块以适应特定的情况,这允许在更多的情况下重用
- 劣:你现在拥有自定义的结果,所以它增加了你的代码复杂性。您需要组件内部的内在知识。
- Black box reuse 黑盒复用:源代码不可见,不能修改
- 以结合现有代码的形式重用,通过提供一些“粘合剂”,但不用改变代码本身——通常是因为你没有访问的代码(只能通过API接口来使用,无法修改代码)
- 优:简单和清洁
- 劣:很多时候这是不可能的
(1)源代码级别复用
源代码级别复用-Lowest Level
- 把部分/全部复制/粘贴到你的程序中
- 维护问题
- 需要更正多个地方的代码
- 太多的代码无法使用(很多版本)
- 过程中出错的风险很高
- 可能需要有关所使用的软件如何工作的知识
- 需要访问源代码
Example code search: grepcode.com
GitHub code search: github.com/search
Searchcode: searchcode.com
(2)模块级别的复用:类/接口
(接口使用Composition/aggregation Delegation/association)
复用类
- 类是代码重用的原子单位
- 源代码不需要,类文件或jar/zip
- 只需要在类路径中包含
- 可以使用javap工具获取类的公共方法头信息
- 非常重要的文档(Java API)
- 封装可以重用
- 需要管理的代码更少
- 版本控制、向后兼容性仍然存在问题
- 需要将相关的类打包在一起——静态链接
复用类的方法:继承
- Java提供了一种叫做继承的代码复用方式
- 类扩展了现有类的属性/行为
- 此外,它们可能会覆盖现有的行为
- No need to put dummy methods that just forward or delegate work
- 更好地捕捉现实世界
- 通常需要在实现之前设计继承层次结构
- 不能取消属性或方法,因此必须小心不要做得太过
复用类的方法:委托
- 委托是指一个对象依赖另一个对象来实现其功能的某个子集(一个实体将某些内容传递给另一个实体)
- 例如,Sorter正在把功能委托给某个Comparator
- 明智的委托支持代码重用
- Sorter可以与任意排序命令一起重用
- Comparator可以与需要比较整数的任意客户机代码重用
- 显式委托:将发送对象传递给接收对象
- 隐式委托:根据语言的成员查找规则
- 可以将委托描述为在实体之间共享代码和数据的低级机制。
(3)库一级复用:API /包
库
库:一组提供可重用功能的类和方法(api)
框架
- 框架:可定制到应用程序中的可重用框架代码
- 框架回调到客户端代码
- 好莱坞的原则:“别打电话给我们。我们会打电话给你。”
- 好莱坞的原则:“别打电话给我们。我们会打电话给你。”
一般区别:库与框架
一个好的API的特征
- 易于学习
- 易于使用,甚至不需要文档
- 难以被滥用
- 易于阅读和维护使用它的代码
- 足够强大来满足需求
- 容易进化
- 适合观众
Guava:用于Java的谷歌核心库
Apache Commons
- Apache Commons是一个Apache项目,关注可重用Java组件的所有方面。
- https://commons.apache.org
- https://github.com/apache/commons-*
Web/Internet上的API: Web Services/Restful API
(4)系统级别复用:框架
应用框架
- 框架是子系统设计,包含抽象类和具体类的集合,以及每个类之间的接口(框架:一组具体类、抽象类、及其之间的连接关系)
- 只有“骨架”,没有“血肉”
- 框架是一种抽象,在这种抽象中,提供通用功能的软件可以通过附加的用户编写的代码有选择地进行更改,从而提供特定于应用程序的软件。(开发者根据
framework的规约,填充自己的代码进去,形成完整系统)
(骨骼清奇,天赋异禀) - 可重用性利用了应用程序领域的知识和有经验的开发人员的前期工作
- 数据处理、GUI等
- 将framework看作是更大规模的API复用,除了提供可复用的API,还将这
些模块之间的关系都确定下来,形成了整体应用的领域复用
如何把你的Lab2 P3变成一个Framework,允许其 他人通过特定机制往里添加各类棋类规则,从而 不断扩展它的能力?
符合真实棋类规则的Action 除了围棋和国际象棋规则之外的其他棋类游戏 AI自主下棋? 深度学习? 漂亮的GUI?
其实你的P3离AlphaGo距离并不远
框架设计
- 框架不同于应用程序
- 抽象级别是不同的,因为框架为一系列相关问题提供了解决方案,而不是单个问题
- 为了适应这一系列的问题,框架是不完整的,结合了热点和挂钩来允许定制
- 框架可以根据用于扩展它们的技术来分类。
- 黑盒框架
- 白盒框架
白盒框架和黑盒框架
- 白盒框架
- 可扩展性通过继承和动态绑定实现。
- 现有的功能是通过继承框架基类和覆盖预定义的钩子方法来扩展的
- 通常使用模板方法模式之类的设计模式来覆盖钩子方法。
- 黑盒框架
- 可扩展性是通过为可以插入到框架中的组件定义接口来实现的。
- 通过定义符合特定接口的组件,可以重用现有功能
- 这些组件通过委托与框架集成在一起
4、可重用性的外部表现
- 类型可变
- 功能分组
- 实现可变
- 表示独立
- 共性抽取
(1)类型可变
- 可重用的组件应该是参数化,这样他们就可以适应不同的数据类型,(输入、计算和输出)
- 一个可重用的模块应该适用于许多不同类型的元素,而不需要开发来执行手动更改软件的文本
- 换句话说,我们需要一种工具来描述类型参数化的模块,也称为泛型模块。
- 通用性:可重用组件应该是通用的
- 类型可变(泛型):适应不同的类型,且满足LSP
(2)实现可变
- 在实践中广泛各种适用的数据结构和算法。
- 我们不能指望一个模块就能解决所有的可能性;它将是巨大的。
- 我们需要一个模块涵盖所有的family
- 实现可变:ADT有多种不同的实现,提供不同的representations和
abstract function,但具有同样的specification (pre-condition, postcondition, invariants),从而可以适应不同的应用场景
(3)功能分组
- 自给自足的可重用模块需要包含一组例程,每个例程对应一个操作。
- 完整性
- 提供完备的细粒度操作,保证功能的完整性,不同场景下复用不同的操作(及其组合)
(4)表示独立
- 可重用模块的一般形式应该允许客户端在不知道操作是如何实现的情况下指定操作。
- 代表独立性是规则的一种延伸
信息隐藏对大型系统的顺利开发至关重要:实现决策经常会改变,客户端应该受到保护。 - 表示独立性反映了客户对可重用性的看法
-忽略内部实现细节和变量的能力 - 表示独立性、信息隐藏
(5)共性抽取
- 分解出常见的行为,反映了供应商的视图,更一般地,反映了可重用类的开发人员的视图。
- 我们的目标是利用实现家族或子家族中可能存在的任何共性。
- 将共同的行为(共性)抽象出来,形成可复用实体:父类、抽象类
- 如前所述,某些问题领域中可用的各种实现的变量通常需要基于一系列模块的解决方案。
- 这些类别中的每一个都包含许多变体,但是通常可以在这些变体之间找到显著的共性。
总结
- 什么是软件可复用
- 如何度量可复用性
- 可复用模块的形态和层级
- 源代码级别复用
- 模块级别复用:类或接口
- 库级别复用:API或包
- 系统级别复用:框架
- 可复用性的外部观测:
- 类型可变
- 功能分组
- 实现可变
- 表示独立
- 共性抽取