如何保障API设计的稳定性,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。计算机行业有句名言 —— 计算机科学领域的任何问题,都可以通过增加一个间接的中间层来解决。当前的计算机领域,无论广度还是深度,已经没有一个人能完全掌握了。但是,通过各种中间层的组合使用,我们不需要了解其内部细节,也可以像搭积木一样,开发出各种有趣的服务和应用。
而各个中间层之所以能组合工作,正是因为大家都通过定义好的 API 交互和通信。每个模块在对外提供经过抽象 API 的同时,也需要使用其他模块的 API 作为自身运行的基础。今天我们来聊聊在设计 API 过程保障稳定性的一些实践。API(Application Programming Interface) 又称为应用编程接口。而接口,本质可以理解为契约,一种约定。
计算机接口的概念起源于硬件。早期各家研发的各种元器件都不通用也没有标准,相互使用非常困难,于是大家约定了功能和规格,就产生了接口,后来蔓延到软件中。接口蔓延到软件之后,又分为 ABI(Application Binary Interface) 和 API(Application Programming Interface) 。
前者主要约定了二进制的运行和访问的规则,后者则专注于逻辑模块的交互。本文以下内容仅讨论开发者经常接触的 API。很多人对 API 的印象只是包含一些函数的 Class 或 头文件。但 API 在我们生活中无处不在,只是我们有时并没有注意到。比如,当我们在拨打电话时,手机和基站通信的整个系统是非常复杂的。好在我们不需要了解内部的细节,仅需要把 11 位的电话号码传给“电话系统”的接口就可以,而隐藏的国家区号(如+86)可以理解为接口的默认参数。
这个高度抽象的 API 背后,隐藏了非常多的细节。借助上面的中间层理论,我们可以系统性地讨论设计一个 API 所需要考虑哪些内容。模块对上层暴露的 API 如何被使用?API 从使用的耦合方式上,可以分为两类:一种是通过协议调用,如调用 HTTP 接口;另一种是语言直接通过声明调用。
如设计 HTTP Restful API 时,并不需要关心使用者的操作系统、使用的编程语言、内存线程管理等,因此会比后者简单一些。API 从使用者的规模和可控范围上,可以分为 LSUD(Larget Set of Unkown Developers) 和 SSKD(Small Set of Kown Developers) 两种。
前者一般都是公网开放的云服务,任何开发者都可以使用,无法提前预知以何种姿势被使用,版本也不可控制。融云提供的通信云就是这种 API。
后者用户群有限,一般都在同一家公司或团队内。比如前段时间比较火的组件化,即对内提供的模块化 API,使用范围和方式均可控,在更新时一般不用太纠结向后兼容。API 的第一受众是人,然后才是机器,所以“可理解性”在设计时需要优先考虑。
而良好的 API 文档、简单扼要的 Demo、关键的 log,可以提升 API 使用者的体验。API 所属模块对下层有什么依赖?API 所属模块都运行在一定的地址空间中。而其中的环境变量、加载库、内存和线程模型、系统和语言特性都需要考虑。API 所属模块的内部实现对其他层有什么影响?一般而言,设计良好的 API 在使用时,并不需要理解其内部实现。但如果能了解其内部架构并辅助关键 log,有助于提升使用 API 的效率。
并且模块的内部实现,有时也会影响到 API 设计的风格。
如一个强依赖 IO 的接口,可能需要使用异步的方式。大量异步的方式,就衍生出了 RxJava 等框架。因为 API 如此重要,涉及的范围又如此广泛,广大开发者对 API 的向后兼容可以说要求非常高。
毕竟谁也不想在开发过程中,频繁的更新接口和代码,想想《 swift 从入门到精通到再次入门到再再次入门》的惨案就心有余悸。我们不仅问,为什么很多公司或者项目都无法向后兼容,仅仅是投入不够或不够重视,还是说 100% 的向后兼容实际就是不可能的?假设设计是理想和经过论证的,正如一个完美的圆圈。
设计是要落实到编码中的,而编码的过程中总是不可避免的引入一些 bug,而带着bug 的某个版本实现,其实正如一个 Amoeba 变形虫,形态是不固定的。而随着版本不断演进,不可避免会产生一定的差异。第一个版本实现:第二个版本实现:所以说 100% 向后兼容本身就是不可能的。因此,大家平时在谈论 API 稳定性时,其实默认是可以包含一定程度变更的。但由于 API 涉及的范围太广泛,保障向后兼容都需要极大代价。
比如 Linux 就希望快速迭代,完全不保证 API 的稳定性。针对这个问题,Linux 还特意写了 stable-api-nonsense 文档。
有兴趣的可以点击阅读:stable-api-nonsense.rst所以说,保障 API 的稳定性会面临很多挑战,比如:
* 业务形态还不稳定,还在高速发展
* 业务和 API 历史包袱较重
* 多个平台和语言的特性不一致
* 用户群和使用方式不明确我们回顾一下正常的开发流程,看看是否能通过一些指标和工具,改善 API 的稳定性,主要涉及:需求、设计、编码、Review、测试、发布、反馈等步骤。※需求普通的产品开发,在启动的时候,用户需求都比较明确,但对于 LSUD 的云服务而言,无法提前预知用户群都有哪些,以及用户在他的产品中如何使用 API。
这容易造成,没有明确的用户需求,API 就不好进行设计和迭代,没有设计就没有用户,需求更无从谈起。这是一个鸡生蛋、蛋生鸡的问题。建议可以在 API 发布之前,内部先针对典型的使用场景,设计几个完整的 Demo,验证 API 的设计和使用是否合理。
需要注意的是,Demo 需要有完整应用场景,达到上架地步,如果能内部使用,Eating your own dog food最好,过于简单的 Demo 无法提前暴露 API 的使用问题。Demo 的开发人员最好与 API 的设计者有所区分,避免思维固化,更多内容大家可以参照 Rust 语言开发在自举过程中的一些实践。※设计在设计 API 的时候,有很多需要注意的点和普通开发不太一样。普通开发,快速实现功能始终被放在第一位。比如大家会用一些敏捷开发的方式,优先实现功能再快速迭代等。
但 API 设计时,接口无法频繁变更,所以首先需要考虑的是“少”,少即是多。l每个API做的事情要少一个接口只做一件事,把这个事情做好就足够了。
需要避免为了讨好某个场景,在一个 API 上进行复杂的组合逻辑,提供一个类似语法糖的接口。否则,场景的业务自身在演进时,很难保证 API 的行为不变。
如果需要支持多种业务,可以考虑将 API 分层,比如融云客户端的 API 会分为下面几层。举个例子,融云考虑通用性,基于订阅分发的模型,抽象了 RTCLib,客户端能处理媒体的任意流,非常的灵活,但是对于用户而言开发代价可能高些,要思考和做的工作比较多。
考虑到大量的用户,其实需要的是音视频通话的业务,基于 RTCLib,融云分装了不带 UI 的 CallLib 以及集成了 UI 的 CallKit。
如果一个用户,需求和微信的音视频通话类似,可以集成带 UI 界面的 CallKit,开发效率会非常高;
如果用户对通话音视频通话 UI 的交互有大量需求,可以基于 CallLib 进行开发,对 UI 可以进行各种定制。l暴露的信息要少成熟的 API 设计者都会尽可能的隐藏内部实现细节。
比如字段不应该直接暴露而是通过 Getter/Setter 提供,不需要的类、方法、字段都应该隐藏,都已经成为各个语言的基础要求,在此就不细述了。
但容易被忽略的一点需要提醒大家,应尽量隐藏技术栈的信息。
比如:APIhttp://api.example.com/cgi-bin/get_user.php?user=100,就明显混入了很多无用的信息,并且以后技术切换升级想维持 API 稳定非常麻烦。l行为扩散要少在语言直接调用的 API 中,需要避免基础接口通过继承导致行为扩散。
在普通的编码过程中,抽象类和继承都是面向对象的强大武器。但是对于 API,更建议通过组合使用。
比如一个管理生命周期的类,如果被继承,子类有些行为就有可能被修改而导致出错。这时候建议使用 Interface + 工厂的方法提供实例。
由于 Java 8 之前 interface 没有 default 实现,为了避免增加功能需要频繁修改接口,可以使用 final class。
Objetive-C 则可以使用 __attribute__((objc_subclassing_restricted))和 __attribute__((objc_requires_super)控制子类继承行为。l画风切换要少API 命名要做到多个平台的业务命名统一,与每个平台的风格统一。
这点 HTTP 的接口要简单一些,只需要选定一种风格即可,Restful 或者 GraphQL 或者自己定义。
语言调用的 API 命名,建议首先遵循平台的风格,然后再是参考语言标准,最后才考虑团队的风格。
比如:iOS 平台的 API 开发,需要首先参照 iOS 的命名风格,did 和 will 之类的时态就非常有特色。
命名上细节较多,词汇、时态、单复数、介词、⼤小写、同步异步风格等都需要考量,需要长时间的积累。l理解成本要少一般 API 每个接口都会有相应的注释说明,但是值得注意的是,大部分开发者并不看注释。
大部分开发者对接口的了解,都仅源于 IDE 的补全和提醒。一个接口看着像就直接用,不行再换一个试试,这其实是一种经验式编程的方式。
也就意味着接口命名需要提高可理解性。有一个办法可以验证,将接口的所有注释抹掉,使用者能否非常直接的看懂每个接口的含义。如果很困难,则需要改进。API 设计还有一处和普通开发不太一致。普通开发设计好架构即可,每个模块的开发可能是同一个人,接口并不需要在设计时确定下来。
但是 API 的设计阶段,需要进行 Review 并直接确定接口的设计,以保证多端在开发时遵循完全一直的规则。※编码在 API 的编码过程中,有以下几点需要注意。在 API 中,预定义好版本号。
这个主要是针对 HTTP API,如:http://api.example.com/v1/users/12345?fields=name,age。 如果目前仅有一个版本,也可以暂时不加,第二版时再区分。注意 API 版本检查。
当分层提供多种 API 时,每层 API 需要在启动时,先校验一下版本号,避免不匹配的情况。
比如在以下 Java 代码中,大家可能觉得判断版本号相等的代码非常奇怪,应该永远是 true 才对。但是抽象类和实现类出现在不同的分层模块中,并且实现类先编译,抽象类版本更新后再编译,就会出现不一致的情况。有很多语言或平台能提供类似的方式来确定版本。提供规范性的 log 输出。
普通开发的log,主要用于自己定位问题。但是 API 在编码时,最好针对性的添加一些 log,有利于 API 的使用者理解并简单排查问题。
但出于性能考虑,需要定义好 log 的级别并可以调整。注意废弃与迁移。
当一个以前设计的 API 不再符合要求或者有重大问题时,我们可以对外标记成已废弃,并在注释中建议使用者迁移到另一个接口。
如果是类似的被废弃接口,内部编码时最好能使用新的接口来实现,以降低向后兼容的维护成本。
HTTP 的 API,需要预定义好迁移的错误码,比如在 HTTP 规范中,可以使用 410 Gone说明已经不再支持某个接口。※ReviewAPI 的 Review 基于普通开发的 Code Review。
如果基础的 Code Review 都没有做好,肯定无法保障 API 的质量和稳定性。可以通过一些工具,为 API 的 Review 提供一些参考报告。
比如可以使用 SonarLint 分析代码复杂度,如果接口层的代码复杂度较高,会是一个危险的信号。
还可以借助 Java 反射、Clang 语法分析,获取当前的 API 接口列表,生成接口变更报告,也有利于减少无用接口的暴露。
另外,自动化工具生成的接口文档也是 Review 重要的一环。※测试在测试环节,我们可以通过 unit test 来关注 API 的稳定性。
与敏捷开发经常修改 test case 不同,API 的 test case 基本代表了接口的稳定性。所以在修改旧 case 时需要特别明确,是 case 自身的 bug 还是接口行为发生了变更。※发布我们可以通过区分 dev 和 stable 版本,为不同阶段的开发者提供更好的体验。dev 版本包含最新的功能,但是 API 接口有变更风险。stable 版本 API 稳定,但功能不一定是最新的。
如果开发者还在开发过程中,可以选用最新的 dev 版本,基于最新 API 开发。
如果应用已经上线,可以选择开发云主机域名升级直接到最新的 stable 版本。关于如何保障API设计的稳定性问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注开发云行业资讯频道了解更多相关知识。
相关推荐: 如何将instance部署到OVS Local Network
这篇文章将为大家详细讲解有关如何将instance部署到OVS Local Network,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。今天我们会部署一个 instance 到该网络并分析网络结构。launch…
免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。