近期部门在做新系统的开发,子系统和子系统之间免不了需要约定各种各样的接口;我们负责的子系统内部,在进行系统设计和模块划分时,也需要定义各个模块之间的接口。由于各个子系统固有的异构性,以及参与人员的个人背景和对问题理解等方面的不同,对接口的约定难免有分歧,因此,经常会翻来覆去的讨论和修改接口。
这里有一篇关于API设计的文档,API是应用编程接口,虽然我们平时所约定的接口并不全是公开的API,但是关于API设计的原则和方法,对于普通的接口设计,同样具有很好的借鉴意义。我摘录其中的主要思想,结合在别处所见的关于接口设计的原则,并加以整理,记录于此(原文中出现的“API”,这里大多换成了“接口”,替换之后某些地方的意思可能不大通顺)。
—————————分割线—————————
好接口的几个特征
1.易于学习和记忆
2.产生易读的代码
3.难以被误用
4.易于扩展
5.完整性
注意:“最小化”和“一致性”没有出现在这个列表之中,它们已被其它特征所涵盖了。因为,臃肿的、不一致的接口,很难被学习和记忆,并且难以扩展。
“最小化”是指尽可能让模块对外只公开最少的部分。这里的“模块”可以是一个类、一个包、一个组件或一个子系统等。类应尽可能拥有最少的public方法或成员;包尽可能拥有最少的公开类;组件尽可能拥有最少的配置参数;子系统对外提供尽可能少的公开接口,等等。
“一致性”等同于“概念完整性”,要让一个复杂系统保持架构师愿景中的条理清晰、前后一贯的设计,必须遵循“概念完整性”原则。《人月神话》及《设计原本》的作者Frederick Brooks曾写道:“概念完整性是最重要的系统设计原则。一个省略了某些不协调的功能特性、但保持一组合理设计意图的系统,要远胜于一个包含很多初衷良好、但相互独立且不协调的意图的系统”。
1.易于学习和记忆
易于学习的接口应当具有一致的命名规范和模式,为相同的概念使用相同的名字,为不同的概念使用不同的名字。
最小化的接口容易记忆,因为需要记住的东西也少。一致性的接口容易记忆,因为你可以在一个地方应用先前在其它地方已经学会的规则。
接口并不仅仅是类和方法的名字,而且还包含它们的语义。接口的语义应尽可能的简单、清晰,并且遵循最小惊奇原则(the principle of least surprise),让常见的任务简单易行;不常见的工作可行,但不会让用户过分关注;解决特殊问题时,不要让解决方案不必要的过度通用。
2.产生易读的代码
程序代码一经写就,会被不同的开发者读(并且除错和修改)多次。易读的代码可能需要花点时间来写,但是可以节省产品周期中的其他时间。易读的代码容易文档化和维护,而且不大可能包含bug,因为易读性让bug无处遁形。
易读的代码可能简洁也可能啰嗦,不管哪种情况,它总是处在恰当的抽象层级上——既不隐藏重要的事情,也不强制程序员指定不相关的信息。
3.难以被误用
经过良好设计的接口,更容易编写出正确的代码,而且会促进好的编程实践。它不会强制使用者按照严格的顺序调用方法、注意隐含的副作用或奇异的语义。
4.易于扩展
接口会随时间变化,有可能向已有模块添加新类、向已有类添加新的方法、向已有方法中添加新的参数、增加新的枚举值等,设计接口时应当将此谨记于心。接口越复杂,在新概念和已有概念之间越有可能发生冲突。此外,在接口初始设计阶段就应该考虑二进制兼容性。
5.完整性
理想情况下,接口应当是完整的,允许使用者完成任何他们想做的事情。完整的接口并不是要提供所有的功能,但至少可以允许用户扩展或自定义现有接口。
完整性会随时间而变化,随着时间的推移,可能会有新功能逐步添加到已有接口之中。
接口设计的流程
1.仔细研究需求
咨询他人,看看别人想要什么样的功能。
2.设计之前先写用例
一个很常见的错误——编写组件库时,先实现功能,再设计接口,然后发布。这种方式设计的接口,会反映出底层代码的结构,而不是调用者愿意看到的样子。
3.看看有没有现存的相似接口
这样有可能从现有相似接口的设计中学习有用的东西,避免重蹈覆辙,复用已有的概念,并保持一致性。
4.先设计,后实现
首先定义接口和它的语义,不要担心实现的复杂度。一个接口直观但实现复杂且布满trick的库,要优于一个实现简洁但接口复杂的库。
5.找人帮你评测接口
寻求反馈,特别是负面的。这些意见更能帮助你改进设计。
6.使用你的接口编写示例
这有助于你发现潜在的问题,而且可以帮助别人了解如何使用你的接口。
7.做好扩展的准备
这要求你了解现实的场景,并思考可能的情况。
8.内部API没评测之前不要发布
有些API一开始是内部使用的,后来大家觉得很有用,才公开发布。一个常见的错误就是发布之前没有进行完整的测试。
9.宁缺毋滥
如果对接口的功能不是很确定,先不要发布。
接口设计原则
1.命名
名字要能解释自己,要遵从英语语法。千万别一会英文单词,一会汉语拼音。
参数的命名也要清楚明白。尽量少用bool类型的参数,这样的代码不好读。
命名要统一。不要混用类似widget和control这类词语,这会让用户乱猜。参数的顺序也要一致。
避免二义性。为相近的事物选取无歧义的名字。一个名字要严格对应一个概念。假如你有两种事件传递机制,一个是同步的,一个是异步的,分别叫做sendEventNow()和 sendEventLater()就不错。
注意完整性。接口设计如同写作,要注意对称和前后照应。格式尽量一样,过程尽量一样,这样使用者能更容易了解你的意图。比如所有的set函数都用set开头,这样用户更容易习惯。
避免用缩写。但也有一些常见的例外,如min,max,dir,rect,prev。但是要注意一致性,不能有的用有的不用。
尽量用专名,不要用通用的名字。
不要太过迁就下层接口的命名。如果你要在一些接口外面再包装一层接口,不要被内部接口的命名方式所支配。按照你自己的命名规则统一命名方式。你设计的目的是让用户使用方便高效,而不是迁就下层的库。
2.语义
选择合适的缺省值。
不要聪明过度。接口应该简单清楚,尽量少让用户产生惊讶的感觉。如果过于自作聪明把API弄得不易用,就远离了API的本来目的。尽量贴近你用户的习惯而不是试图教他们怎么做,否则你就等着写文档去吧。
注意边界值。对于类库来说,边界值的处理相当重要,认为边界值发生概率很小就不加注意是很幼稚的。边界值造成的问题会在使用这个类的其他类中得到扩散和放大。
小心定义虚接口。虚接口一般更难定义,并且很容易在新版本发布时出错。设计接口时,必须全盘考虑,逐个过滤来决定哪些接口应该是虚的,哪些不应该是,在文档里应该详细说明你的类如何使用这些虚方法。
3.结构
采用基于属性的接口。很多接口要求使用者指定一大堆的参数。太多参数对于使用者来说是个噩梦。一种更好的方式就是基于属性的设计。用户创建一个参数对象,把需要指定的参数作为属性设置进去,其它取默认值。这样做有许多优点:
* 看起来比较简单
* 不用记住参数的顺序
* 可读性强,不需要特别说明注释
* 属性可以有缺省值,不是必须指定所有的属性
* 随时可以更改属性
* 可以随时取得属性,便于除错
* 方便进行可视化图形化设计
做开发,不管哪种开发——无论是组件库的开发还是整个系统的开发,无论是前端开发还是后端开发,无论是底层开发还是上层应用的开发——所做出来的功能特性都是要给别人使用的,使用者可能是最终用户,也可能是其它开发者,怎么给使用者提供一个好用的接口,是每一个开发者都应当努力思考的事情,而不是仅仅把功能实现就完事。面向最终用户的接口设计——UI/UE,随着互联网的兴起,已经形成了一个专门的领域。面向开发者的接口设计,需要每一个开发者为之努力,因为这是关乎我们自身的事情。
最新评论