你有没有这样的经历?

接到一个新需求,要对接某第三方服务。打开文档,扫了5分钟,复制了个示例代码,改了两个参数,一把跑通。你甚至还有时间泡杯咖啡,心想:”这接口设计的,舒服。”

然后第二周,你要对接另一个服务。打开文档——200多页PDF,目录都找不到。好不容易找到接口列表,发现请求用POST,参数塞在一个叫 bizData 的JSON字符串里,外面再包一层签名。调了1小时没通,最后在一个犄角旮旯的FAQ里发现:时间戳要用毫秒,不是秒。你有一种强烈的冲动想找到设计这个接口的人,跟他”友好交流”一下。

这两个API,背后的技术水平可能差不多,都是HTTP+JSON,都能把活干了。但使用体验差了一个银河系。

这个差距,就是API的”品味”。

RESTful是最低标准,不是品味

很多人以为好API就等于”遵循RESTful规范”——用对了GET/POST/PUT/DELETE,URL里有资源名,返回正确的HTTP状态码,齐活了。

这就好比说一篇文章”语法正确”就等于”文笔好”。语法正确只是最低标准,不犯错和写得好之间隔着十万八千里。

你见过那种严格遵循RESTful的API,但用起来让你抓狂的吗?我见过太多了:

GET /api/v2/resource-management/user-information/query?filterType=single&responseFormat=json&includeDeleted=false&pageSize=1&userId=123

对比一下:

GET /users/123

两个都”符合RESTful”,但第一个看一眼就想关掉浏览器。

RESTful是语法,品味是文笔。 语法能教,品味得练。

好API vs 烂API:同样功能,体验天差地别

Stripe:品味的天花板

说到API品味,绕不开Stripe。

Stripe的支付API被开发者公认为业界标杆,有意思的是——它并不严格遵循RESTful。在很多地方,它选择了”违反规范但更好用”的设计。

举个例子。你调Stripe的接口扣款失败了,它返回什么?不是一个干巴巴的HTTP 400加一个空body,而是这样:

{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "decline_code": "insufficient_funds",
    "message": "Your card has insufficient funds.",
    "param": "source"
  }
}

三层信息:type 告诉你大类是卡片错误,code 告诉你是拒绝交易,decline_code 精确到余额不足,message 直接给你一句人话。你不需要去翻错误码表,不需要猜”400到底是哪种400”,一看就知道怎么回事,该怎么处理。

再看国内某些支付接口的做法:

{
  "code": "20001",
  "msg": "业务异常"
}

“业务异常”。就这俩字。哪个业务?什么异常?你自己猜去吧。猜不到就看文档,文档没写就问客服,客服说”您看一下错误码表”,错误码表上写着”20001:业务异常”。完美闭环,但一圈下来你什么都没解决。

这就是品味的差距。Stripe的工程师在设计错误返回的时候,脑子里想的是”调用者拿到这个返回,下一步该怎么办”。而另一边想的是”返回个码就行了,文档里有”。

品味不是花里胡哨,是替使用者多想一步。

门把手理论:好API不需要说明书

设计圈有个经典理论,叫”可供性”(Affordance),是唐纳德·诺曼在《设计心理学》里提出来的。简单说就是——好的设计自己会说话。

最经典的例子是门把手。一个平板,你自然会推;一个横杆,你自然会拉。不需要贴”推”或”拉”的标签,门把手的形状本身就在告诉你该怎么操作。如果一扇门需要贴说明才能用对,那不是使用者笨,是设计有问题。

API也是一样。

看到 GET /users/123,你马上知道这是获取ID为123的用户。不需要看文档,不需要猜,URL本身就是说明书。

但如果设计成 POST /api/v2/fetch-user-info?mode=single&format=json,你得先消化好几层信息:为什么获取数据要用POST?fetch-user-info 是一个端点还是一个操作?mode=single 是什么意思,还有其他mode吗?format=json 是说请求格式还是返回格式?

功能完全一样,但认知成本翻了10倍。

好的API就是好的门把手——你不需要看说明书就知道该怎么用。

API的"可供性":自说明 vs 需要猜

五条API品味原则

说了半天什么是品味,该给点实操的东西了。我总结了5条原则,不是教科书式的规范条目,而是你设计API时可以直接拿来对照的checklist。

原则一:可预测

命名保持一致。用户列表叫 users 就一直叫 users,不要第一个接口叫 users,第二个叫 user_list,第三个叫 accounts。分页参数叫 page 就一直叫 page,不要一会 pageNo 一会 pageNum 一会 current

程序员用你的API时,会基于前几个接口的经验去”猜”后面的接口长什么样。如果猜得中,说明你的API可预测;如果每个接口都要重新学,那就是设计者在给用户找麻烦。

可预测的底线:同一套API里,同一个概念只有一个名字。

原则二:自解释

端点名应该自己说明做什么。POST /orders 创建订单,GET /orders/{id}/status 查询订单状态——不需要看文档你就知道它干嘛。

反例:POST /api/execute?action=createOrder&version=2。这是把URL当成了函数调用的容器,所有操作都塞在一个端点里靠参数区分。能跑,但用起来跟读压缩过的代码一样——不是不能理解,是理解的成本太高了。

原则三:宽进严出

接受多种输入格式,但返回严格统一的结构。

日期字段,用户传 2024-01-152024/01/1520240115,你都能认。但你返回的日期格式永远是 2024-01-15T00:00:00Z,一种格式,没有意外。

反面教材:我对接过一个API,传手机号必须是 +86-13800138000 这个格式,少一个横杠都400。而它返回的手机号呢?有时候带+86有时候不带,有时候有横杠有时候没有。输入严到变态,输出随心所欲——完全反过来了。你解析它返回值的时候得写一堆正则来兜底,这谁受得了。

这个原则的哲学来源是Postel定律(也叫鲁棒性原则):”发送时要保守,接收时要开放。” 放到API设计里就是——别因为用户传的格式不完美就直接400,帮他处理掉;但你返回的东西一定要规规矩矩的,让调用者可以放心解析。

原则四:错误友好

每个错误码附带三样东西:机器可读的错误类型、人类可读的原因说明、建议的修复方式。

好的错误返回:

{
  "error": "validation_error",
  "message": "字段 email 格式不正确",
  "suggestion": "请使用标准邮箱格式,如 user@example.com"
}

烂的错误返回:

{"code": -1, "msg": "参数错误"}

哪个参数错了?错在哪了?该传什么?一概不知。开发者对着这个返回只能去翻文档——如果文档里有写的话。

你的错误信息的质量,决定了开发者debug的速度。 而debug的速度,直接决定了他对你的API的印象是”好用”还是”垃圾”。

原则五:渐进复杂度

基础用法3分钟上手,高级功能按需解锁。

一个搜索接口,最简单的调法就是 GET /search?q=关键词,这就够了。排序、过滤、分页、高亮这些高级参数全部可选,不传就用默认值。

反例是那种”Hello World也要传10个必填参数”的API。你只想查个用户列表,它要求你必须传分页参数、排序字段、过滤条件、返回字段列表、签名、时间戳……你只想煎个鸡蛋,它让你先组装一台燃气灶。

好的API像一把好刀——拿起来就能切,但如果你是厨师,它还有更多高级用法等你发现。

API品味五原则速查卡

品味的代价是50小时

那个让你5分钟就调通的API,背后可能是设计者花了50小时打磨的结果——改了十几版命名、重写了三次错误码体系、删掉了一半自认为”很有用”的参数。

品味不是天赋,是投入。

下次你设计API的时候,做一个测试:找一个没看过文档的同事,把接口列表扔给他。5分钟调通,你就达标了;他开口第一句是”这个参数什么意思”——回去继续磨。

好的API像空气,用的时候感觉不到它的存在。 你不会夸”这接口设计真规范”,你只会觉得”这事怎么这么简单就搞定了”。感觉不到设计的设计,才是最高级的品味。