软件框架设计的艺术----读书总结
总结
软件开发的艺术
理想主义,经验主义和无绪
文艺复兴时期,现代科学产生了两个重量级理论: 理性主义和经验主义。
理性主义认为理智是信息的首要来源。给出一个假设,只要通过思考就能理解和描述这个世界,如著名的伽利略自由落体实验。
经验主义则认为人类对世界认识的主要来源是经验。
我们开一辆车,不必知道其内部实现细节。
如果孤立地基于两种极端的方式来观察世界都是片面的。对大多数人来说,懵懂无知是一种生活方式,也是理性主义和经验主义结合在一起的结果。今天的程序开发和软件工程方法也是如此。
软件的演变过程
- 上世纪40年度,用机器语言, 那时debug的时候可能还得带一个扳手
- 然后 FORTRAN ,他允许程序员只关心数学公式而不是内部机器内部实现 ---- 经验主义
- 然后COBOL来了, 他简化了数据库的操作 ---- 经验主义
- 当然同时期lisp出现了,他更强调纯粹的数学模型---理性主义
- 然后cpp, 然后java
我们可以发现在软件演变的过程中, 理性主义几乎已无存身之处。
因为软件的趋势是: 程序员在可以不深入了解很多内容的情况下就可以写出非常好的代码。
大型软件是如何开发的
现状: 开发团队往往直接复用现有的一些软件框架,完全不重视这些重量级的框架是否超过我们的需要的。现代软件都是基于大型组件的方式进行组装的。 我要一个web服务器就装一个tomocat, 要一个数据库就装个mysql。 这完全是一种推土机式的开发方式,不管你的组件有多大, 总会找到合适的推土机把他推上去。 运行效率太差 就 加内存,搞服务器集群,
这种方法是好还是坏呢?实际上绝大多数公司已经用这种方式了。
因为推土机式的工作方式可以使你在不关注内部细节的情况下,也可以得到不错的结果。 我们可以在不了解汽车原理的情况下,可以把汽车开得很好。 在写win32程序时候,也不必了解系统是怎么实现。 我们只需要关注windows 系统API, 以及这些API的功能。
理解一个系统有两个层面:
- 浅层理解: 紧限于了解使用方法
- 深层理解: 理解其原理
而在软件开发中, 一般只要做到浅层理解就可以了。
我们说明软件开发其实是一个经验的积累过程,并且可以是复用前人的经验积累的。
设计API的动力之源
好的API可以使功能的使用者聚焦在使用层面而不是其内部细节
为什么需要好的API:
- 分布式开发: 通常我们一个程序部署由一个人能独立完成的
- 模块化应用程序 : 我们的程序是分模块的,不同模块的交互就是用API
- 交流互通才是一切: 模块之间相互依赖
- 开发第一个版本通常比较容易
设计API过程中遇到的最大问题--- 不断变化的需求
一个软件开发的生命周期:
- 第一版吧总是非常漂亮的
- 快乐总是短暂的
- 软件熵不断增加
- 天哪 千万不要动他
- 重新开发一个版本吧。
变化是万恶之源。
那么如何才能设计好的API
评价API好坏的标准
第一步先确认什么才叫好。
很多人认为所谓的API,不过是类和方法。但是这是比较片面的。
强调一点, 我们为什么需要开发好的API:我们希望能够将大块的构建模块,”无绪“地集合成应用程序。
那么如何评价一个API的质量: 漂亮? 但是评价漂亮的标准是很主观的。我们应该设计易于使用、广为接受且富有成效的API。我们可以有一下几个方面来衡量一个API的好坏。
- 可理解性: 每个人的世界观都会限制自己d视野,所以对于一个优秀的API来说,他涉及的概念都要在用户的可理解范围之内, 即使有新的概念也应该是渐进式的。
- 一致性: 向下维持兼容
- 可见性: 最好提供一个入口用来作为用户API的起点。 为什么大家都喜欢开源,因为开源很多东西网上可以直接拷贝。
- 简单的任务应该有简单的方案: 所以API应该是分层的。
- 保护投资: 善待API的用户。 尽量想办法让API漂亮点。如方法名,如结构等。 在发布第一版之前这些都是非常合适的。但是发完第一版以后我们要保证我吗的代码改动不会影响正在运行的代码了。
实现API的步骤
第二步弄清楚写API的步骤
写一个API有三个步骤:用例, 场景, 文档。
一个用例就是一种用法的描述,他指出用户可能要面临的问题,而这个问题不是一个具体的问题,而是很多问题的抽象。
举个例子
用例: 设计一个数据库管理器,他的功能是注册JDBC驱动。
场景:对用例的回答。我们把API要描述的每一个功能下列出来:
- 注册有一个JDBC可以写一个能够描述驱动的XML, 有格式
- 这个XML放置在DataBase/JDBCDrivers目录下
- 用URL来表示驱动地址
注意一些设计原则
第三步 学习一些设计原则和通用方法
- 只公开你要公开的内容
- 面向接口而非实现进行编程
- 模块化架构
- 声明式编程
只公开你要公开的内容
我们所设计的API都会被可能误用。几乎所有的API设计者都会有这样的共识:一个人API设计的时间越长,他设计的API公开的内容会越少。
设计API的几种方法:
1. 方法优于字段
这个不用说了。 geter setter。 这样做会有很多改变的余地。 如计算、转换、校验、覆盖
2.工厂方法优于构造函数
工厂方法会带来很大的灵活性,三个好处:
- 返回不一定是声明的类型可以是子类
- 构造的对象可以被缓存
- 同步的控制。 可以都构造对象前后的代码进行控制。
3. 让所有的内容都不可改变
通常情况下, 在设计一个类的时候,如果不考虑让拥有子类,那就不应该让这个类被继承。 用final 来修饰。 还有些其他方案来: 不公开构造函数转而提供工厂方法。把大部分方法变为final或者private
4. 避免滥用setter方法
一个宝贵的教训: 如无必要,绝对不要再正式的API中声明setter方法。
5. 尽可能通过友元的方式来公开功能
在java中,所谓的友元就是用默认的package方式访问,即允许同一个包内的代码进行访问。
有个实际的例子
6. 赋予对象创建者更多的权利
7. 避免暴露深层次继承
避免深层次的继承,定义程序的接口,并让用户来实现这些接口。 如果一个类继承了某个类或者接口,那么就可以作为响应的类或接口被使用。
如在swing中, frame间接继承了compoment,这样就表示所有使用compoment的地方,都可以使用frame. 实际上frame继承 compoment是为了复用compoment的代码。这是一种典型的面向对象的复用的误用。 所以如果发现继承提醒超过2层,一定要想清楚“我是在设计API还是在复用代码”,如果是后者,则做好子类化准备。
面向接口而非实现进行编程
本质上讲,这个原则倡导的是,当我们写一个函数或一个方法时,我们应该引用相应的接口,而不是具体的实现类。接口提供了非常优秀的抽象归纳,让我们的开发工作变得容易很多。 让API的使用者和API的实现者解耦出来。
模块化架构
随着软件规模的增大以及功能的复杂性增加。只要代码开始访问其他无关模块的内容,那么架构的退化不可避免。模块化能有效变缓这种退化。
模块化的目的非常简单,就是要实现程序中各个组成部分的松耦合。如果两个模块是独立的,那两个模块就不需要知道对方的存在。如果两个模块要交互,那么他们应该通过具有良好定义的接口来进行交互。
声明式编程
声明式编程的基本思路, 不是让API用户一步一步告诉程序如何做,而只是需要告诉程序他们要的结果,然后交给API去完成。声明式编程的好处是能在较高的抽象层次来定义操作。
比如写一个资源管理API: API的使用者很容易找到一个方法去注册一个功能,运行一下成功了,然后不再往下继续找注销的方法了。 声明式编程就是解决这个问题的一剂良药: 开发人员只需要声明注册什么,响应的注销和清理由系统完成。
极端的意见有害无益
最后提醒一点,任何极端的想法都是有害的,在软件框架设计中也一样, 有以下单不仅限几个点:
- API必须是漂亮的
- API必须是正确的: 有时候易用性和正确性还重要。比如巨大文件
- API应该尽量简单
- API必须是高性能
- API必须绝对兼容
- API必须是对称的
团队协作
现代的大型工程很少是有一个人单独开发完成的,所以团队协作非常重要。一下几个点能很好帮助在大型软件工作中完成好的API设:
- 在提交代码时候进行代码审核
- 为API提供文档
- 尽职尽责的监控者
- 接受API的补丁
以上非常简单,但是在外面平常工作中缺少的就是执行。