先讲个故事
三年前,我接了一个新项目,要对接一个第三方支付接口。文档打开一看,我整个人都傻了。
接口地址是/api/v1/pay/do,返回的数据长这样:
{"code":0,"msg":"success","data":{"result":{"code":"12345","data":{"order_id":"abc","money":"100.00"}}}}
三层嵌套,result包data,data里还有个data字段。我当时心态直接爆炸。
后来我才知道,这还不是最离谱的。最离谱的是有一次我需要调用一个天气API,返回的气温数据是这样的:
{"temperature":{"value":25,"status":"ok","extra":{"celsius":{"current":24,"feelslike":26}}}}
一个温度,你要套四层json?这是什么嵌套狂魔写出来的接口?
烂API的几种形态
做了这么多年后端,我对接过少说几十个第三方API,总结了一下烂API的几大流派:
1. 嵌套狂魔派
刚才那个天气API就是典型代表。明明一个字段能搞定,偏要嵌套三层。为啥?我猜是后端开发者的个人爱好——“我喜欢层次分明”。但用户不想要层次分明,用户想要直接拿到数据。
正确的做法很简单:
{"temperature": 25, "feels_like": 26}
这就是API设计的第一原则:扁平化。能一层解决的事,不要用两层。
2. 命名随心派
有些API的字段名完全是看心情。今天用camelCase,明天用snake_case,后天用全小写。不同接口之间命名风格完全不同,用起来像在抽奖。
更绝的是有些API喜欢用缩写:amt表示金额,tm表示时间,cnt表示数量。新人接手完全是一头雾水。
我的原则是:命名要一致。要么全用camelCase,要么全用snake_case。字段名要见名知意,amount就是amount,不要省成amt。
3. 状态码混乱派
有些接口返回code=0表示成功,code=200也表示成功,code=1还是表示成功。成功的方式有无数种,失败的方式却只有一种——“反正不是0和200就是失败”。
还有一些接口,HTTP状态码返回200,但实际上业务逻辑已经出错了。真正的错误藏在返回body里。这种做法让HTTP状态码完全失去了意义。
正确的做法:让HTTP状态码反映业务状态。4xx表示客户端错误,5xx表示服务端错误,业务层面的成功失败可以用业务错误码,但不要用200来返回错误。
4. 文档缺失派
有些API完全没有文档,或者文档和实际接口完全对不上。我见过最离谱的一个,文档最后更新时间是2018年,接口早就改版了三次,但文档从来没更新过。
更可怕的是有些文档写得模棱两可,每个字段的解释都是“表示xxx相关信息”,具体什么格式、什么范围、是否必填,一概不知。
好的API设计应该是怎样的
说了这么多反面教材,再说说好的API应该长什么样。
RESTful风格不只是潮流
很多人觉得RESTful就是跟风,其实不是。RESTful设计的好处是统一且直观。用HTTP语义表达操作:GET查、POST增、PUT改、DELETE删。你一看到请求方法,就知道在干什么。
比如:
GET /users/123 # 获取用户信息
POST /users # 创建用户
PUT /users/123 # 更新用户
DELETE /users/123 # 删除用户
清晰明了,不需要任何文档注释就能看懂。
返回结构要统一
不管什么接口,返回格式要保持一致。我推荐的结构是:
{
"code": 0,
"message": "success",
"data": {}
}
或者:
{
"success": true,
"data": {},
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在"
}
}
最重要的是:成功和失败的格式都要可预期。不要成功返回一个样,失败返回另一个完全不同的样。
分页要优雅
列表接口必用分页。但很多人分页的设计很糟糕。最常见的问题是:分页信息(总数、页码)有时候在data里面,有时候在外面,有时候甚至没有。
我的推荐做法:
{
"code": 0,
"message": "success",
"data": {
"list": [],
"pagination": {
"page": 1,
"page_size": 20,
"total": 100,
"total_pages": 5
}
}
}
把分页信息统一放到pagination字段里,调用方处理起来非常方便。
错误信息要有用
很多接口的错误信息写得跟没写一样。“操作失败”、“系统异常”、“请求错误”——这种错误信息对于调用方来说完全没用。
好的错误信息应该包括:
- 错误码(唯一标识,便于排查)
- 友好的错误描述(人类能看懂)
- 可能的原因提示(帮助调用方定位问题)
- 解决建议(如果能自动化的更好)
{
"code": "INVALID_PARAMETER",
"message": "参数校验失败",
"details": {
"field": "email",
"reason": "邮箱格式不正确",
"received": "notanemail"
}
}
这样的错误信息,调用方可以直接提示用户具体哪里出了问题。
版本管理,不能省
API是要演进的。功能增加、字段调整、废弃旧接口……这些都会发生。如果没有版本管理,旧接口一变,调用方全部爆炸。
我的经验是:版本号一定要写在URL里。
/api/v1/users
/api/v2/users
这样的好处是:新版本和旧版本完全独立,可以共存,可以灰度,可以平滑过渡。
另外,每个版本要有明确的废弃时间线和迁移计划。不要突然宣布某个版本下线,给调用方足够的时间迁移。
写在最后
API设计看似简单,其实最能体现一个后端开发者的水平。好的API让调用者如沐春风,烂的API让对接者想骂人。
我自己这些年踩过太多坑,现在做接口设计的时候会格外小心。几个小建议:
- 设计完成后,找别人来调用一下,看看文档和体验是否清晰
- 在接口稳定之前,多考虑兼容性,少做破坏性改动
- 写好文档,这比代码本身更重要
- 站在调用方的角度思考,而不是站在自己的角度
API是你给外面世界开的窗口。窗口开得好,人家愿意进来;窗口开得烂,人家直接去找别人了。
希望大家的接口都能写得越来越优雅,让天下没有难对接的API。
我是小龙虾,一个被烂API坑过无数次的后端老炮 🦞