API 设计原则

引章

小时候没见过电梯,第一次到大城市见到电梯的时候,摆在我面前的就两个按钮:🔼 和 🔽 。我当时猜想的使用姿势是:

  1. 观察现在电梯停在几楼
  2. 如果电梯在我楼下就摁 🔼 先喊它上来(接我),如果电梯在我楼上就摁 🔽 喊它下来(接我)
  3. 进电梯后再摁我想要去的具体的楼层

但实际上电梯的正确使用方法是:

  1. (不用关心现在电梯停在哪)如果想下楼,那就摁 🔽 ,如果想上楼就摁 🔼
  2. 进电梯后再按想要到达的具体的楼层

显然,现实的使用方式比我自己想象的方案要简单, 它不会给调用者带来使用上的压力,因为前一种方案需要用户思考很多细节:

我现在在 4 楼,但我想上到 8 楼:

  1. 如果现在电梯停在 7 楼,那我就按 🔽 先让它来 4 楼接我,最后再送我到 8 楼
  2. 如果现在电梯停在 2 楼,那我就按 🔼 先让它来 4 楼接我,最后再送我到 8 楼

而后一种方案则简单很多:

我不用关心现在电梯停在几楼,只要我现在想上楼我就先按 🔼 ,接到我后,我再最后告诉电梯我具体要上到几楼

原则

那么怎样才能减少调用者的使用压力呢?之前已有 文章 其实已经介绍了很多方法。这里通过如何设计一套优雅的 SDK 来举例归纳一下(按优先级由高到低排序):

少的接口数量

之前做 iOS 开发的时候,为了做一些统计需求,对接了一个 SDK,使用非常的简单,就只需要在 app 的启动函数里调用一下他们的 init 函数,将自己的 appkey 传进去即可。这样就能轻松统计到一些完全满足需求的信息:页面流量、crash 日志上报、设备 id、用户 ip 等等。

接口少,使用者就不需要花时间去阅读繁多的 API 列表,使用起来清爽简单。

再聊一个我们前端同学前段时间遇到的一个案例:

两个需求分别对应两个场景: 1. 从记录用户名的 input 输入框里获取用户输入的值,无论用户是否使用大小写,最后吐出的值都转换成小写 2. 从记录用户 ETH 地址的 input 输入框里获取用户输入的值,这个时候就不能全转成小写了,需要从 input 里原样取出。因为如果用户输入的是大小写混杂的 addr,就需要检查其 checksum,如果不是,再走后续正常流程

实现: 前端库里实现了 A 、B 两个从 input 输入框里取值的函数,分别对应上述的两个需求。

问题: 一个熟悉前端库的老手或许清楚从 input 里取值有两个函数,分别对应了两种不同的场景,那新手呢?当然可以要求他先熟读一遍库里的所有 API,但万一后来又忘了呢?况且老手也会有不小心的时候,也用错了函数怎么办?

如果真出了问题的时候,就以“常在河边走,哪有不湿鞋的道理”来安慰自己?这显然不是正确的方法论

解法:

这样库里外显的函数少了 A 和 B ,多了一个 C,总共减少了一个函数,无论是新手还是老手再次遇到需要从 input 里取值的场景时,他有且只能使用的就只有 C 函数,并且由于 C 函数有一个必填参数,这就会迫使使用者不得不去关注一下当前处于什么使用场景

简单的函数签名

衡量函数签名 简单 的标准(按优先级由高到低排序):

  1. 入参数量少且类型简单
  2. 返回字段少且类型简单
  3. 函数名一定要避免词不达意

详细的说明看 这篇文章

做更多的事

全面的入参检查

之前用过某个库函数,参数有一个是指针,然后我不小心传了一个空指针进去,结果库函数不仅不报错还给我返回了一个看起来格式正确的结果。显然这个流程继续走下去迟早会报错,后来报错之后我调试了很长时间才发现问题的根源。

最后我不得不在调用这个库函数之前自己先判断一下参数是否为空指针,后面为了避免再次发生类似的问题,所有的关于这个库的函数我都需要额外再做一下保护措施,因为我不再相信这个库的作者的水平了。

实际上,判断参数是否为空指针应该要放到库函数里去做的。因为你无法预料使用你的库函数的用户会是一个怎样的人,他如果有谨慎的习惯还好,那万一要是不小心呢?所以如果你正在编写一些公共函数,请为你的使用者尽量多做一些事。如果没有入参检查,不会有更多的人使用你的代码。

代码只有在被人使用的时候才会产生价值

详细的错误描述

早期版本的 GCC 很多错误描述让人摸不着头脑,所以这也是大家觉得 C/C++ 很难的原因之一。既然大家都已经感同身受了,那就应该己所不欲勿施于人,自己写函数的时候就要多写点错误处理,正常情况下 60% 的代码都应该是错误处理,真正处理业务逻辑的代码应该是占少部分的。虽然很难理解,但确实是事实。

提供申明式函数

申明式 指你只需要告诉函数你的目的就行了,函数会帮你处理好一切;与之相对应的是 命令式 ,指你要求函数按照你的说的办,一步一步达到最终目的。

引章里举的电梯的例子就很典型,我幻想的使用方式其实是 命令式 的,而电梯真正的使用方式是 申明式 的,仔细想想是不是这样的 😃

申明式函数的好处就是大大减轻了使用者的压力,但牺牲了一些可组合性

说白了,申明式的函数就是把一堆命令式函数为满足特定的需求组合而成的新的函数

  • 从系统架构师角度来讲,会更喜欢写一堆基础的命令式的接口,然后让使用者去搭建乐高一样任意组合这些接口拿到他们想要的结果。
  • 从产品开发角度来讲,我们需要提供更优质的服务,那么就要求在命令式接口基础上事先组合好申明式的接口,这样使用者无需思考太多,直接拿来用就好了。

而 SDK 则更偏向于服务,因为 SDK 是更接近真实用户的那一层,它是需要带有服务意识的。