软件框架设计的艺术
2021-09-23 11:59:27 15 举报
AI智能生成
软件框架设计的艺术
作者其他创作
大纲/内容
序言
“API一旦发布,便于我们永恒共存”
想要将知识传授给其他人的时候,就会因为他们没有类似于我们的经验,也就很难说服他们接受我们的知识
理论与理由
”艺术“还是”工程“
API的重要独特性:一致性
组内如何一致?
合作无间
共同的愿景
公共术语
P2 一年后,我证明了当时的这一猜测。因为Netbeans是一个开源项目,有一个API吸引了一个外部的开发人员。开始的时候,这位外部代码贡献者主要是做一些修复Bug的工作。随后,他开始单独负责一个子项目,并设计相关的API。原先负责设计这个API的人说他发现这个子项目的API变得越来越差,但他找不出原因,希望我帮一手。他说:非常不喜欢这些新的API,更不想把它们集成到他负责的项目中。但他也同样说不清楚这些新的API到底有什么问题。最后,他说这些新的API看起来和他原来的设想不一致。我曾经也对他说过类似的话。以我的标准来评价的话,他所编写的API与NetBeans的API的设想不一致!
方法论
理性主义
经验主义
无绪
浅层理解
对某种事物的了解程度只限于掌握使用方法即可
深层理解
掌握某种事物背后的原则、规律
大多数时候”浅层理解“已经足够
针对性无绪(selective cluelessness)
某些内如需要深入了解
应用程序开发的无绪原则
尽量让最终负责集成的相关人员不需要深入了解系统也可以把集成工作做好
对象:人
评价API好坏的标准
Why?
我们希望能够将大块的构建模块”无绪“的集合成应用程序
what?
方法、类、字段签名
文件(unix管道)
环境变量和命令行选型
文本信息(toString)
协议
行为
判断兼容性最重要的方式是”正常运行“,但这一点却是由内部具体实现决定的
null返回值
国际化
……
Quality
可理解性
设计人员和开发人员之间交流的途径
范例的重要性
一致性
降低学习曲线
保持兼容
可见性
提供”入口“,以便找到需要的解决方案
基于实际或预期的目标与任务
用户不关心具体的类
简单的任务应该有简单的方案
普通的使用者
扩展的使用者
保护投资
多为用户考虑
不断变化的目标
向后兼容
源代码兼容
二进制兼容
功能兼容
容易被遗漏
态度,“好老板”不会抱怨客户
面向用例的重要性
用例
我应该怎么做
场景
你应该先做这件事,然后再做那件事就可以了
API设计评审(优秀API规则)
用例驱动的API设计
设计API时,要基于一些具体的场景和对API的认识进行抽象分析,最终给出设计
API设计的一致性
API往往是由多位设计者来完成的,但整个团队中必须能够保持“最佳实践”的一些基本原则。一个接口设计得再好,只要它违反整个团队的一致性,就宁愿退而求其次
简单明了的API
简单而常见的任务应该更容易处理。如果基于用例驱动的方式进行设计,就可以很容易的通过那些可以简单实现的场景来验证这些API是否可以完成那些重要的用例
少即是多
一个API对外提供的功能应该只包括用例中说明的功能。这样可以避免出现需要的功能与实际提供的功能两者之间出现差异。
支持改进
必须能够持续的维护该类库。即使出现新的需求,或者原作者离开,都不会放弃这个类库
设计实战
只公开你要公开的内容
添加容易,移除困难
发布第一个版本时,移除不必要的内容
方法优于字段
除了static和final声明的基本类型、string常量、枚举和不变对象外,其他字段都不应该被公开
工厂方法优于函数
工厂方法返回值不一定是声明类型的实例,可以是子类的实例
每次返回的对象也不一定是新建的对象,可以将其缓存
同步控制,对创建对象前后的代码进行统一处理
让所有内容不可更改
如不希望拥有子类,就应该让其不可被继承
避免滥用setter方法
如无必要,绝不要在正式的API中声明setter方法
setEnable
可能上下文敏感
isEnable判断,使setEnable失去意义
尽可能通过友元方法来公开功能
java默认的package访问方式,内部类
赋予对象创建者更多权利
访问权限
避免暴漏深层次继承
代码复用
功能复用
“继承”不是用来改变具体的行为,而是用来添加一些额外的动作
如果去继承一个类,只是为了切换某些方法的执行路径,那么这种做法应予以避免
建议:避免深层次继承,定义程序接口,并让用户来实现这些接口
面向接口而非实现进行编程
抽象定义(接口)与实现分离
合作
向API用户讲清楚:使用APi时,应该遵循正确的原则。如果需要使用一个API就要遵守API的原则,不要破坏
清楚的说明API的需求,定义相应的用例,帮助API设计者实现这样的API
protected方法不可移除,导致子类修改
在接口中增加抽象方法,会强迫其非抽象子类实现该方法
模块化架构
面向对象并不能避免意大利面条式的编程
模块化的目的:实现各组成部分的松耦合
模块化架构将规范与实现分离
在规范所在的模块中,至少会有一个小的“入口”
依赖注入
Spring
Netbean lookup
扩展
P111 2001年,我们给JavaOne大会提交了一份名为“组件定位与协作”的议题,该议题中的内容与本章多少有些关系,我们认为该议题涉及的内容对于所有的模块化程序都很重要。然而,当时的JavaOne会议的组委会可能仅仅因为觉得这个议题的名称不够酷,或者可能因为他们觉得组件这个词用得太滥了,要知道“组件”这个词几乎包含了所有可以想象的内容,所以没有接受该议题。直到2006年,我和同事Tim Boudreau又提交了一份类似的议题,名为“模块化架构中那些发现注入和依赖注入的模式”。果然,如我们所料,议题被组委会接受了。这说明一定要找到能够贴近目标人群的术语才能打动他们。一旦听到“依赖注入”这个词,他们的心就被打动了。就像API这个词一样,恰当的名称有益交流。对于用户来说,他们越容易理解API这个词,就越容易接受API的相关内容。
其实还有几个词:架构、模式
设计API时要区分其目标用户群
API
可以添加但不能移除
供他人调用来完成某些功能
final类
内聚,自包含
SPI
Service Provider Interface
可以移除但不可增加
供其他人扩展API功能
接口
避免两者混用
合理分解API
API
SPI
NetBeans的分类
核心API
支持API
核心SPI
支持SPI
根据用户群体的不同需求来组织你的API结构
牢记可测试性
Mock对象
为API提供Mock对象
测试代码是规范的一部分
好工具让API设计更简单
向导
让客户尽快的上手
项目的第一目标快速推向市场
NetBeans中每一项重要的技术都有一个相应的向导来建立初始的基础框架
兼容性测试套件
面向SPI用户和API实现者
与其他API协作
谨慎使用第三方API
包装模式
只暴露抽象内容
暴露的越多,演进的余地就越小
强化API一致性
代理和组合
避免API的误用
所有的层次之间都需要保持一致性。
API具体运行时的一些内容
”臭名远扬“—与其抱怨用户,不如改变自己的态度
”保卫者“——自动化测试
同步与死锁
正确且恰当的描述其线程模型
Java Monitors中的陷阱
继承影响synchronized
对于公开的API,不能将对外方法申明为同步
处处陷阱的Java同步与死锁
声明式编程
基本思路:不是让API用户一步步告诉程序如何做,而只需要告诉程序他们的结果,然后交给API去完成
NetBeans Lookup实现,只需要简单XML描述,不需要用户注册、注销
自描述性
日常生活
现实生活充满了变化因素,理论的东西不可能一成不变的用在生活中。不仅要知道哪些事情可行,哪些事情不可行,还要知道如何运作才能可行
极端意见有害无益
API必须
是漂亮的
是正确的
如果为了正确性而牺牲了易用性,那么可能会引发更多的问题
Mercurial和SVN
尽量简单
是高性能的
绝对兼容
是对称的
API设计中的矛盾
自相矛盾
Wiki定义:“同时相信两种互相矛盾的观点,而没有意识到两者是矛盾的”
安全部门
飞机安检
开发人员抱怨的事项总是不断,如评审、编程规范,等等。但API监护人需要时刻保持警醒的状态,否则就可能因为一个小小的疏忽而引发问题。另一方面,不是出个小问题,至少说明了你所做的工作确实很有用。
少数派报告
改进API
团队协作
在提交代码前进行代码评审
说服开发人员为他们的API提供文档
倒推法
在写代码之前
概述
FAQ
尽职尽责的监控者
工具监控所有需要关注的变更情况
没有工具,需要一个”全知全能“的人
接收API的补丁
只接受正确的建议
制定要求
要求不能太复杂
利用竞赛游戏来改进API设计技巧
0 条评论
下一页