You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
9.5 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 13 | 接口规范,是协作的合约
一个软件项目,一般需要交付两类文档。一类文档是面向开发者的,另一类文档是面向最终用户的。这两类文档,由于面向用户的不同,无论是内容还是形式,都有巨大的差异。今天我们先来聊聊面向开发者的文档。下一讲中,我们再接着聊面向最终用户的文档。
## 区分外部接口和内部实现
为了便于维护和协作一个软件通常被划分为几个不同的部分。比如我们通常使用的MVC架构把软件分为模型Model、视图View和控制器Controller三个部分。这样做可以降低复杂度让程序结构更加直观。同时这种架构也很容易对程序进行修改和扩展并且可以重复利用基础的功能。
不同功能的分离,让程序员之间产生了分工,专业人员可以更聚焦于个人的专长领域。这是一个多赢的局面,也能让软件的质量得到提升。
既然有分工就要有协作。MVC架构把软件拆分为三块是分工而MVC模块之间的调用关系就是协作。
一个好的软件设计,要区分外部接口和内部实现。外部接口,就是协作的界面,要简单规矩;内部实现,可以是千变万化的复杂小世界。
这种区分无处不在即使是最普通的API。比如我们常用的InputStream一旦我们获得这个对象实例就可以调用它的read()方法。 我们不用去关心它的底层实现是一个文件一段内存还是一个远程连接。InputStream的接口定义只有十个方法短短的500多行代码。
但是它的内部实现却是一个更大的世界,广泛地分布在不同的类库、不同的模块,实现着不同的具体功能,有些实现甚至超出想象的复杂,比如一个安全连接的\_InputStream\_的实现一般有着数万行的代码。
幸运的是,我们区分了接口和实现,调用者就不用去关心这些复杂的实现了,只需要理解接口规范就好。
提高协作效率的最高技巧不是提高沟通技巧,而是要减少沟通的数量,提高沟通的质量,尤其是要减少数量。如果你参加了工作,没完没了的会议,没完没了的文案,都会加深你对这条原则的理解。软件的设计也是这样,外部接口,要少、要小、要描述清楚。
## 接口规范是协作合约
由于外部接口是协作的界面,是调用者和实现者之间的合约,所以对它就有了更加严格的要求。这里我总结了合约的四个原则:成文、清楚、稳定、变更要谨慎。
具体要怎么实践这些原则呢?
**合约要成文**
无论对于调用者,还是实现者来说,外部接口的使用都要有章可循,有规可依。如果调用者需要去看实现代码来理解外部接口,那么外部接口和内部实现的分离还有什么用呢?不就背离了外部接口和内部实现分离的初衷吗?这样做既是对实现者的纵容,也是对调用者的无视。
比如说Java的每个版本的API文档和指南就是Java语言的合约。
**合约要清楚**
合约既然是我们协作的依靠,就一定要清晰可靠、容易遵循,不能有模棱两可的地方。如果接口规范描述不清,既误导调用者,也误导实现者。
如果接口规范复杂难懂,说明接口的设计也很糟糕。
那么接口规范要怎么描述呢?
接口规范主要用来描述接口的设计和功能,包括确认边界条件、指定参数范围以及描述极端状况。比如,参数错了会出什么错误?
这里需要注意的是,接口规范不是我们定义术语、交代概念、提供示例的地方。这些应该在其他文档中解决,比如我们下次要聊的面向最终用户的文档。
**合约要稳定**
既然是合约意味着调用者必须依赖于现有的规范。比如InputStream.read()这个方法接口规范描述的是读取一个字节8-bit返回值是介于0和255之间的一个整数。如果我们要把这一个规范改成返回值是介于-128到127之间的一个整数或者是读取一个字符比如一个汉字都会对现有的使用代码造成灾难性的影响。
接口的设计和规范的制定,一定要谨慎再谨慎,小心再小心,反复推敲,反复精简。一旦接口合约制定,公布,然后投入使用,就尽最大努力保持它的稳定,即使这个接口或者合约存在很多不足。
**变更要谨慎**
世界上哪里有一成不变的东西呢!技术的进步、需求的演进,总是推着我们朝前走。合约也需要跟得上变化。
可是接口合约毕竟不是租房合约可以一年一续每年变更一次。租房合约的变更成本很小但软件的接口合约变更的影响要严重得多。特别是兼容性问题稍微一丁点儿的接口规范变化都可能导致大面积的应用崩溃。越成功的接口使用者越多变更的影响也就越大变更的成本也就变高变更也就越困难。你可以试着想一想如果InputStream.read()这个方法在Java中删除会造成多大的影响会有多少应用瘫痪
所以,对于接口规范,我们的原则是,能不变更就不变更;必须的变更,一定要反复思量该怎么做才能把影响降到最低。
## 使用Java Doc
说完了接口规范的几个原则,我们就来讲一下,如何实践这些原则。接口的规范描述,应该怎么组织?
从使用者角度出发,包括接口的调用者和实现者,接口的规范应该便于阅读,便于查找。从制定者的角度出发,接口的规范应该便于定义,便于维护。
JavaDoc就是一种顾及了多方利益的一种组织形式。它通过文档注释的形式在接口声明的源代码定义和描述接口规范。这种和源代码结合的方式可以方便我们维护接口规范也有利于保持接口规范和接口声明的一致性。
JavaDoc工具可以把文档注释转换为便于阅读为HTML文档。这样就方便规范的使用者阅读了。
当然也不是所有的规范都一定要使用JavaDoc的形式特别是冗长的规范。如果有两种以上不同形式的规范组织文档**我建议一定要互相链接、引用**。比如冗长的规范可以单独放在一个文件里。然后在Java Doc对应的文件里加上改规范的链接。
比如下面的例子中“Java Security Standard Algorithm Names Specification”就是一个独立的较长的规范文档。当需要使用这个文档的时候就要在对应的接口中指明该文档的位置这样方便用户进行检索。
上面的文档注释经过JavaDoc的处理就变成了便于用户阅读的文字。
> protected Signature(String algorithm)
>
> Creates a Signature object for the specified algorithm.
>
> Parameters:
> algorithm - the standard string name of the algorithm. See the Signature section in the Java Security Standard Algorithm Names Specification for information about standard algorithm names.
## 谁来制定接口合约?
这本来不是一个问题。但是由于我们选择在源代码中,需要通过文档注释表达接口合约,这就成了一个很严肃的问题。
源代码的维护者,是不是对接口合约拥有无限的修改权利呢?
肯定不是的。
既然是合约,就是大家都认可并且接受的规范和细节,只有形成共识才能编辑和修订。合约的编写和修订,一般不应该由源代码的维护者一人决定,而应该由参与各方充分沟通和协商。
“三个臭皮匠,顶个诸葛亮”,我们要充分尊重参与各方的能力,信任充分的沟通可以成就更好的规范。
一个软件项目,不管大小,只要参与者超过两个,都要讨论清楚彼此之间的分工协作方式。这当然也包括,讨论清楚如何制定、修改程序接口。
比如OpenJDK的接口制定和修订就一定要经过下面的步骤
1. 起草接口规范,或者起草提议的修订规范;
2. 找相关领域的专家,审议草案,并根据评审意见,修改接口规范;
3. 如果领域专家审议通过,提交兼容性和规范性审查程序; 并根据审查意见,相应地修改接口规范;
4. 兼容性和规范性审查通过,修改接口合约;
5. 按照议定的接口规范,编写最终的实现的代码。
当然了你的软件项目也许和OpenJDK有巨大的差异。你要找到适合自己公司和项目的接口合约制定和修改的适当方式。
## 小结
对于接口规范,我们要有意识地使用下面的这条原则:
> 接口规范是使用者和实现者之间的合约。
我们在工作过程中,如果有和接口相关的迷惑或者争执,可以多想一想上面的这条原则。
## 一起来动手
2018年12 月 25 日,部分开发者突然发现他们开发的 Web 网页的界面发生了变化,按钮上方出现“积雪”。这超出开发者的脑洞和认知,难道是圣诞老人的礼物,或者是黑客的祝福?经过探索发现这是前端 UI 组件库 Ant Design简称 antd提前埋入一个未经声明的“彩蛋”。事件迅速发酵引起了巨大争议。
前人的危机都是后人的财富。该怎么做,才可以避免类似的事情?欢迎你在讨论区留言,我们一起把这个事件转化成我们的见识和能力。
也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来交流。