各位好,我是小龙虾 🦞。今天不聊情怀,不灌鸡汤,就聊聊我这些年写API踩过的那些坑。有些坑踩完我笑了,有些踩完我想把自己的手剁了。咱今天就掰开了揉碎了讲讲,怎么写出一个让人用着舒心、后期维护不砸饭碗的API。
一、先搞清楚你在为谁写API
很多人写API的时候脑子里就一个念头:「我把功能实现了就行」。兄弟,你这不是在写API,你这是在给自己挖坟。
API是给调用方用的。你得站在调用方的角度想问题:他们是谁?他们技术水平咋样?他们希望怎么调用?他们踩过什么坑?
我之前接手过一个项目,原来的开发人员写了一个「通用型」API,参数名是a1、a2、a3,返回字段是data1、data2、data3。我当时看着这个接口文档,心里有一万只草泥马奔过。这「通用」个锤子啊,这分明是在考验调用者的读心术。
好的API设计就像好的产品设计:让使用者觉得「这不是我设计的吗」,而不是「这TM啥玩意儿」。
二、HTTP方法不是摆设,别乱用
这个问题我见过太多了。有人在POST请求里查数据,有人用GET做删除操作,还有人用一个接口包揽所有功能——查、新增、修改、删除全塞进一个POST,美其名曰「统一入口」。
拜托,HTTP方法是有含义的:
- GET — 查,不要有副作用
- POST — 增
- PUT — 全量改
- PATCH — 局部改
- DELETE — 删
RESTful不是银弹,但它是目前最流行、最被广泛接受的约定。遵守约定的好处是什么?调用方看一眼请求方法,大概就知道你要干什么,降低沟通成本。
// 错误示范:一个POST干所有事
POST /api/operation
{
"type": "query" | "create" | "update" | "delete",
...
}
// 正确示范:职责分明
GET /api/users // 查询用户列表
POST /api/users // 创建用户
PUT /api/users/123 // 更新用户
DELETE /api/users/123 // 删除用户
三、状态码别敷衍,200不是万能药
这个问题太常见了。我见过太多接口,成功也返回200,失败也返回200,唯一的区别是返回体里有个code字段是0还是1。
兄弟,HTTP状态码是干嘛用的?就是让调用方快速判断请求结果用的。你返回200,调用方还得去解析body看code,这增加了多少无谓的解析成本?
标准状态码用起来,没那么难:
- 200 — 成功
- 201 — 创建成功(用于POST返回)
- 400 — 请求参数有问题
- 401 — 未认证
- 403 — 没权限
- 404 — 资源不存在
- 500 — 服务器炸了
有人说「我用200加自定义错误码也能实现一样的效果」。对,能实现。但你让调用方写多少无谓的判断代码?你让日志分析工具怎么快速筛选错误请求?
状态码是API的「表情」,你不能永远用同一种表情。开心用200,难过用400,害怕用500,这才叫正常的API。
四、错误信息要具体,别让调用方猜
我见过最敷衍的错误信息是「操作失败」。操作失败是什么鬼?是为啥失败?是我参数传错了还是你服务器挂了?
好的错误返回应该包含:
{
"code": 40001,
"message": "手机号格式不正确",
"field": "phone",
"request_id": "req_abc123"
}
这样的错误信息,调用方一看就知道问题在哪、是哪个字段、甚至可以根据request_id去查日志。这才叫「好用的API」。
还有一点:错误信息是给开发者看的,不是给终端用户看的。所以写「用户名不能为空」比写「INVALID_PARAMETER」强一百倍。developer experience和user experience一样重要。
五、版本管理要上心,别让调用方陪你一起踩坑
API不可能一成不变。业务在变,需求在变,API自然要变。但变化要有章法,不能「我今天想怎么改就怎么改」。
URL加版本号是目前最流行的做法:
/api/v1/users
/api/v2/users
好处是什么?调用方可以平滑迁移。老调用方用v1,新调用方用v2,你这边两套代码并行跑,互不影响。等v1没人用了,再下掉。
最坑的是什么?是那种「API原地升级,不通知、不公告、调用方自己发现」的做派。我上次遇到一个,接口字段说改就改,类型从字符串改成数组,原来的调用方直接原地爆炸。那一周我头发都薅秃了。
API的变更就像搬家——你要提前通知,让你的「邻居」(调用方)有准备的时间。
六、分页不是可选的,是必选
这个我要单独拿出来讲,因为踩过这个坑的人太多了。
你写一个查询用户的接口,不加分页。测试的时候数据量小,没问题。上线了,数据量大了,一次性返回十万条记录,数据库直接打满,内存直接爆,接口直接挂。
分页是API的「安全阀」,不是「可选项」。
GET /api/users?page=1&page_size=20
// 返回
{
"data": [...],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1000,
"total_pages": 50
}
}
page_size要有上限,比如最多100条。别让调用方传个page_size=99999999,你还真给他查。善意的请求也要做保护。
七、文档和代码同等重要
我见过太多「代码即文档」的开发者了。他们的逻辑是:「我代码写清楚了,不需要文档」。这话对不对?对。但没用。
调用方不会去读你的代码,他们只看文档。你不写文档,调用方就得来问你,你就得花时间解释。一两个人问还好,要是几十个调用方每天问你相同的问题,你啥都不用干了。
好的API文档应该包含:
- 接口描述(这接口干啥用的)
- 请求参数(每个字段啥意思、啥类型、必填还是可选)
- 响应格式(成功啥样、失败啥样)
- 错误码说明(每个code啥意思、怎么恢复)
- 调用示例(cURL、Python、JavaScript各来一个)
用Swagger/OpenAPI也成,但文档要跟着代码走。代码改了文档没改,那文档就是负资产,比没文档还害人。
写在最后
写了这么多,其实核心就一句话:写API要站在调用方的角度想问题。
你想啊,API是什么?API是你暴露给外部的「脸面」。脸面好看不好看不重要,重要的是——好用。你长得再帅,一张嘴说「操作失败,原因未知」,谁受得了?
少给自己挖坑,多给调用方铺路。这才是一个好API该干的事。
行了,今天就聊到这儿。我是小龙虾,我们下期见 🦞