AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

API 设计的“不可承受之轻”

  • 2007-09-03
  • 本文字数:3139 字

    阅读完需:约 10 分钟

API 的设计影响着所有的开发者。有些 API 用起来很舒服,而有些则用起来让人焦头烂额,更有甚者,让人完全丧失了继续用这套 API 来做开发的勇气。但它们的区别在哪里呢?是哪种品质会让一套 API 易用而另一套复杂难解?ACM Queue 最近刚发布了 Michi Henning 的一篇有关 API 设计的文章,作者在文中剖析了 API 的好坏之分,并指出了 API 对生产力的影响。

当我们开始用 API 的时候,我们就能知道它是好是坏。好的 API 会让人心情愉悦,用起来得心应手:当你想调用某个特定的任务时,最合适的方法会在最恰当的时候出现在你面前,容易发现,易于记忆,文档说明详尽,接口设计符合人的思维习惯,而且正确的处理了边界条件。 那为什么现在还有这么多垃圾 API 呢?其主要原因在于,对于每一种正确的 API 设计方式而言,都同时伴随有很多种错误的设计方式。简单来说,设计一种坏的 API 要比设计好的 API 容易的多。即使是很微不足道的、无伤大雅的设计上的小小缺陷,最终也会被放大成巨大的阴影,因为 API 只需要提供一次,但是却会被调用很多次。如果一个设计缺陷会导致笨拙或是低效的代码,那么这个问题就会在 API 每次被调用的时候都显现出来。而且,有些设计缺陷单独看起来很小,但是却会以令人讶异的破坏性的方式来相互影响,从而很快就会导致不计其数的间接性破坏。

Michi 在给出自己对 API 设计的建议之前,先举了一个反例为证。他对.NET 框架中的 Select() 方法调用进行了分析,展示了很多 API 中都存在的共通问题。

Select() 方法中传入的参数是需要进行监控的 socket 列表。在大多数应用程序中,所使用的 socket 不会常常变化,所以这些列表基本上在很长一段时间内都是不变的。但是:

因为 Select() 方法对参数进行了重写,所以调用者在把每一个列表传入 Select() 方法之前必须要保留一个备份。这就给用户带来了很大的不便,而且不宜扩展应用的规模:服务器常常都要监控上百个 socket,而在每一次循环中,调用 Select() 方法前都要复制所有的列表。

因为 socket 会进入等待或是阻塞状态,所以 Select() 还接受一个时间参数用来标识超时,如果在参数所指定的时间内没有任何 socket 可用,那么方法就会返回。但是,因为这方法是 void 类型,所以没有很便捷的方式可以标明 Select() 方法是因为有了可用的 socket 而返回,还是因为超时返回的。

为了判断是否有可用的 socket,调用者必须要对方法参数中的三个列表各自的长度进行测试;如果三个列表长度都是零,那么就是没有可用的 socket。如果调用者碰巧很关心这一点的话,那就要写一个相当别扭的测试了。更有甚者,如果没有 socket 就绪而发生超时,那么该方法就会销毁调用者传入的参数,所以就算是什么事情都没有发生,调用者还是必须要在每一个循环中对这三个列表进行备份!

而且,在 API 文档中,也没有明确的指出该怎样无限期地等待 socket 就绪。Michi 还是通过自己的试验才发现,如果超时参数为 0 或是负数,那么该方法就会立刻返回,而且也没有任何办法可以让该方法等待的时间超过 35 分钟。这就逼着他只好自己实现了一个 Wrapper,来完成持续性等待的功能。

在写这个 Wrapper 的时候,Michi 又发现了 API 设计中新的潜在问题:

Select() 方法的另一个问题就是它接受的参数是 socket 列表。列表中可以允许同一个 socket 多次出现,但是这样做一点意义都没有:从概念上讲,传入的应该是由 socket 组成的 set。那么为什么 Select() 会使用列表(List)呢?原因很简单:.NET 容器类中没有包括对 set 的抽象。使用 IList 来对 set 建模是让人徒呼奈何的一件事情:它会带来语义上的冲突,因为 list 允许出现重复的元素。(Select() 方法遇到重复的 socket 会发生什么样的行为大家只能瞎猜,因为没有明确的文档记录;而要实际去检测它的真实行为也是没有意义的,没有文档说明,我们怎么知道实现会不会在什么时候突然改变?)

一个劣质的 API 设计所带来的问题就是,差不多每个用这套 API 的开发人员都要设法去弥补其中的缺陷。该 API 的用户群越大,开发人员浪费的时间也就越多。

劣质的 API 不但会带来更多的编码,而且代码结构也会更加复杂,潜藏 bug 的地方也就越多。

不过设计错误所发生的位置不同,其后果也会有所差异。

在抽象层次中,如果 API 缺陷出现的位置越低,其后果也就越严重。如果我在自己的代码中错误地设计了一个函数,那么影响到的人就只有我自己而已,因为我是这个函数的唯一调用者。如果我在我们的项目库中错误地设计了一个函数,那么或许所有的同事都会遭罪。如果我在一个广泛采用的代码库中错误的设计了一个函数,那么或许就会有成千上万的程序员开始诅咒了。

任何大面积流行的公共代码库或多或少都会被认为是不可变的。这种类型的 API 的任何变化都有可能破坏向后的兼容性,引起大量问题。方法声明的变化会导致客户端无法编译或是崩溃,方法行为的变化会导致无可预计的微妙错误。即使是修复 bug 也会有问题,因为会有些既有的代码是依赖于原始的有 bug 的行为的。

在动态链接库的世界中,客户端代码就更容易受到 API 变化的影响了。哪怕采用了版本机制,客户端代码又怎样判断库文件版本号的小小变化是否会影响到当前任何对 API 行为的假定呢?

即使 API 自身的变化会让框架变得更好,但是每一个用户也都要为升级付出一定的代价。当然,也有的变化不会引起客户端哪怕一行代码的改动,可就算这样,每一个客户端也不得不进行重新编译,以防止出现崩溃的情况,因为源码不变的情况下,二进制码也会发生变化。

一个API 设计者需要考虑很多事情,当然,大多数的决定都是根据过往经验做出的,不过还是有些比较优秀的想法应当推荐给大家。Joshua Bloch 在 Javapolis/InfoQ 上关于 API 的演讲中和听众分享了自己的心得,而在他所有的建议中,最根本的一条就是要让 API 尽可能地小(但绝不过分小),因为:

你可以随时往里面加东西,但是你永远不能删除些什么。

Joshua 讨论了几乎每一件应当注意的事项,从不可变性(保证类的不可变,除非有比较好的理由要求可变)等概念的重要性,到比较细微但也同样重要的细节问题,比如只要在可能的情况下,就宁可返回空的列表也不要返回 null 值(因为返回一个 null 值会导致调用者要做多余的检查)。

Michi Henning 同样在文中也给出了他的一些如何让 API 更加优秀的建议:

  • API 必须要提供充分的功能,以供调用者完成自己的任务。
  • API 应该是最精简的,不要为调用者带来多余的不便。
  • 如果没有理解 API 的使用环境的话,那也就不能去设计它。
  • 通用性的 API 应当是与具体使用场景无关的,而特定用途的 API 则要充分考虑使用策略。
  • API 应该从调用者的角度来进行设计。
  • 好的 API 绝不推卸责任,把自己该做的事情留给别人。
  • 在实现 API 之前,就应该把 API 文档化。
  • 好的 API 应当符合工效学(Ergonomic)。

因为大多数人都会承认这些建议的重要性,人们会觉得应该在学校里教授这些知识,但是看起来这还不属于当前的标准教学过程。Ed 描述了现在的课程中这些知识的欠缺,作为对 Michi 的文章的评论

我现在意识到了我所接受的教育是不完整的。我上的课只是教会了我们怎样寻找最好的算法,或是和旧的数据结构行为相似的新数据结构(还有人记得双向队列么?)。而且我们的作业也从来没有根据设计来评分:评分的依据纯粹是执行速度。

但是如果教育系统不教给学生如何进行 API 设计的话,那么资深的同事会教么?Michi 在这一点上不甚乐观:

经验的积累是需要时间的,而且还要经历过一点“吃一堑,长一智”,才能掌握如何把事情做得更好。但不幸的是,我们这个行业的趋势就是把经验最丰富的开发人员提升到远离编码的职位上,正好是在他们可以把多年积累的经验用到实处的时候。

这就让整个的学习过程变得很棘手了。那么,我们这整个行业该做出哪些改进呢?

查看英文原文: Why API design matters

2007-09-03 19:462112
用户头像

发布了 197 篇内容, 共 59.9 次阅读, 收获喜欢 21 次。

关注

评论

发布
暂无评论
发现更多内容

业务复杂度治理方法论--十年系统设计经验总结

京东科技开发者

电商评论数据自动化采集的六大核心策略(附避坑手册)

代码忍者

淘宝API接口

合合信息IntFinQ知识库体验:大模型加速器2.0,助力企业走向文档智能化

Damon小智

工作流 OCR AI‘’ DeepSeek coze

2025南京软博会

AIOTE智博会

WebSocket调试神器对决:Apipost凭何碾压Apifox?

数据追梦人

知识图谱与多模态推理在测试开发中的实践价值‌

测试人

人工智能

鸿蒙NEXT开发-Tabs组件

东林知识库

APP开发框架及其特点

北京木奇移动技术有限公司

APP开发 软件外包公司 APP外包公司

Gitea Enterprise 23.6.0 (Linux, macOS, Windows) - 本地部署的企业级 Git 服务

sysin

git

数据要素市场化 驱动数字金融

郑州埃文科技

数据要素

数据要素对企业营销会有什么影响?

郑州埃文科技

VMware Aria Automation 8.18.1 - 多云基础架构自动化平台

sysin

aria

自增主键去哪了?---一次开发过程中的思考

京东科技开发者

【转载】golang内存分配

京东科技开发者

智能制造:MES系统架构设计

积木链小链

数字化转型 制造业 智能制造

唤醒新质生产力,破解企业供应链“新”课题

科技热闻

WebGL开发框架及其特点

北京木奇移动技术有限公司

软件外包公司 webgl开发 3D软件开发

VMware ESXi 8.0U3d macOS Unlocker & OEM BIOS Dell (戴尔) 定制版

sysin

esxi

AI英语背单词APP开发

北京木奇移动技术有限公司

软件外包公司 AI口语练习 AI英语学习

区块链项目的开发框架及特点

北京木奇移动技术有限公司

区块链技术 软件外包公司 web3开发

《币圈不设防》第五期:市场热点、交易所生态与合约争议深度探讨

TechubNews

HarmonyOS:ComposeTitleBar 组件自学指南

李游Leo

鸿蒙

有关IP地址运算

郑州埃文科技

IP地址

互联网的“神经中枢”域名根服务器是如何演变的?

郑州埃文科技

根服务器

Shopify电商平台API接口对接及上线

北京木奇移动技术有限公司

跨境电商 软件外包公司 shopify开发

高效定位 Go 应用问题:Go 可观测性功能深度解析

阿里巴巴云原生

阿里云 云原生

VMware Aria Operations for Logs 8.18.3 - 集中式日志管理

sysin

aria

文档智能扫描,提升无纸化办公效率

HarmonyOS SDK

什么是IP地址子网划分,是如何应用的?

郑州埃文科技

IP地址

智能制造新引擎:铜丝生产行业MES系统核心功能

万界星空科技

制造业 mes 铜加工 铜拉丝生产 铜丝mes

VMware Aria Operations 8.18.3 - 多云 IT 运维管理

sysin

aria

API设计的“不可承受之轻”_架构_Niclas Nilsson_InfoQ精选文章