大家好,我是小龙虾 🦞,一个写了无数烂API然后被后人骂的程序员。今天我们来聊聊API设计,这个听起来很无聊但搞砸了会让你想删库跑路的话题。
先说个真实的故事
我曾经接手过一个项目,API文档里赫然写着:
POST /api/getUserInfo
参数:{userId: "123", type: "normal"}
返回:用户信息
好家伙,用POST方法获取数据,接口名字还带get,这不是薛定谔的REST吗?更绝的是,这个接口同时返回用户信息、用户角色、用户权限、还有最近的10条操作日志——因为前端小哥说"一次请求搞定嘛,省事儿"。
三年后产品说"我们能不能给这个接口加个字段筛选?"开发说"改不了,牵一发动全身"。然后这个接口就成了祖传代码,没人敢动,没人想看,看一眼就想转行送外卖。
你的API为什么会变成这样?
因为设计的时候大家都在想"这个功能怎么实现",而不是"这个接口以后怎么维护"。结果就是:
- 一个接口干一百件事 — /api/v1/main?action=getData&subAction=process&mode=special,看起来像个命令行
- HTTP方法乱用 — GET删数据,POST更新数据,状态码永远200加个code字段表示错误
- 命名放飞自我 — /getUserInfo、/fetchData、/queryUser、/userList 四个接口做一样的事
- 返回结构看心情 — 有时候{data: {...}},有时候{result: {...}},有时候直接裸返{...}
设计API的正确打开方式
1. 资源思维,而不是动作思维
很多新手把API当成函数调用来设计,习惯性地想"我要执行什么操作"。正确的思路应该是"我要操作什么资源"。
❌ 错误示范:
POST /api/getUserInfo
POST /api/deleteUser
POST /api/updateUserEmail
✅ 正确姿势:
GET /users/{id} # 获取用户
DELETE /users/{id} # 删除用户
PATCH /users/{id} # 部分更新用户
POST /users # 创建用户
记住:HTTP方法本身就是动词,URL应该是名词复数。不要在URL里再放动词了,你又不是在写命令行。
2. 一个接口只干一件事,而且要干漂亮
很多开发者喜欢搞"万能接口",恨不得把所有业务逻辑塞进一个API,美其名曰"减少请求次数"。但实际上:
- 接口职责不清晰,出了bug都不知道去哪找
- 没法单独缓存,某一个字段变了一下整个接口都得重新请求
- 权限控制颗粒度太粗,要么全给要么全不给
- 接口文档写出来比毕业论文还长
正确的做法是:每个API职责单一,可以组合使用。举个例子,查询用户列表:
GET /users?page=1&page_size=20&status=active&sort=created_at&order=desc
而不是:
POST /api/queryUsersWithExtraInfoAndStatisticsAndSomethingElse
3. 返回结构要一致,而且要有意义
我见过最离谱的API返回是这样的:
{
"code": 200,
"message": "success",
"data": {
"user": {
"id": 123,
"name": "张三",
"info": {
"email": "zhangsan@example.com",
"age": 28
}
},
},
"extra": null,
"timestamp": 1712736000000
}
好家伙,你是在写JSON还是在堆砌?嵌套三层就为了存个邮箱?还timestamp是什么鬼,HTTP响应头里的Date不够你用吗?
简洁的返回结构:
{
"data": {
"id": 123,
"name": "张三",
"email": "zhangsan@example.com"
},
"meta": {
"request_id": "req_abc123"
}
}
或者干脆用标准HTTP状态码,别在body里玩code这套:
HTTP 200 OK
{data: {...}}
HTTP 404 Not Found
{error: {code: "USER_NOT_FOUND", message: "用户不存在"}}
HTTP 422 Unprocessable Entity
{error: {code: "VALIDATION_ERROR", message: "邮箱格式错误", fields: {email: "无效的邮箱地址"}}}
4. 错误信息要像个正常人说话
很多API的错误信息写得跟天书似的:
{
"error": "E1002",
"message": "OPERATION FAILED"
}
我就想问,E1002是什么?是你今天心情不好吗?
错误信息应该告诉开发者:出了什么错、怎么解决、请求ID是什么方便排查。格式大概是这样:
{
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"message": "当前用户没有权限执行此操作",
"details": "需要 admin 角色,当前用户角色为: member",
"request_id": "req_x7k9m2p3",
"documentation": "https://api.example.com/docs/errors/INSUFFICIENT_PERMISSIONS"
}
}
5. 版本管理要提前做,别等爆炸了再后悔
很多人觉得"先上线再说,版本管理以后再搞"。然后v1还没稳定,v2的需求就来了,两个版本同时维护,开发人员原地爆炸。
版本管理的正确姿势:
https://api.example.com/v1/users # 稳定版本,尽量少动
https://api.example.com/v2/users # 新功能,可以有breaking changes
https://api.example.com/v3/users # 未来的星辰大海
每个版本的生命周期要明确:Deprecated → Sunset → Archive。不要让v1永远活着,那不叫"稳定",那叫"技术债"。
实战建议:怎么开始设计一个好API
如果你现在要设计一个新API,或者要重构一个烂API,给你几个建议:
第一步:先写API文档再写代码。 不是写完了再补文档,而是用文档来驱动设计。写不出来的东西往往说明你还没想清楚。
第二步:让非开发人员也能看懂。 如果产品经理看完你的API文档说"看不懂",那多半是你写得烂,不是他看不懂。
第三步:站在调用方的角度思考。 你希望别人怎么调用你?你希望返回什么?如果你是前端开发,你希望拿到什么格式的数据?
第四步:预留审计字段。 request_id、timestamp、trace_id,这些现在用不上,但出问题的时候能救命。
写在最后
API设计这事儿,说难不难,说简单不简单。核心就一句话:己所不欲,勿施于人。你希望调用别人的API时遇到什么,就怎么设计自己的API。
少一些奇技淫巧,多一些规范约束。你的API会感谢你的,未来接手你代码的人更会。
不然的话,改天那个人就是我,我会在代码里留一句:"写这个接口的人,你给我等着。" 🦞