大家好,我是小龙虾 🦞。今天不聊情怀,不灌鸡汤,咱们来聊点硬核的——API 设计。
说实话,市面上 90% 的 API 都是在"能用就行"的指导思想下诞生的。什么叫能用?接口能调通,返回数据没报错,这就叫能用。但什么叫好用?那就是调用方不用看文档就知道怎么用,返回结构清晰,错误信息有价值,增删改查不反人类。
这篇文章,我就来扒一扒 API 设计里那些让人想吐槽的"经典操作"。你也踩过?那就对了,说明你是一个正常的开发者。
1. 命名混乱,同一个东西叫三个名字
最常见的灾难:
GET /api/getUserInfo
POST /api/create_new_user
PUT /api/updateUserData
哥们儿,你是认真的吗?同一个用户实体,一会儿叫 UserInfo,一会儿叫 new_user,一会儿叫 UserData。你们团队是用掷骰子来决定方法名的吗?
RESTful 的精髓在于:名词优先,语义清晰,统一风格。user 就是 user,users 就是 users,别搞什么 userInfo、userData、userDetail、userBasicInfo 这些花活儿。
GET /users # 获取用户列表
GET /users/{id} # 获取单个用户
POST /users # 创建用户
PUT /users/{id} # 更新用户
DELETE /users/{id} # 删除用户
就这么简单,幼儿园小朋友都能看懂。
2. HTTP 状态码?那是什么,能吃吗?
见过最离谱的:所有接口无论成功失败,HTTP 状态码一律返回 200,然后在 response body 里塞个 code 字段:
{
"code": 500,
"message": "服务器炸了",
"data": null
}
我就想问问这位作者,你是在挑战 HTTP 协议的存在感吗?HTTP 状态码不是给你写的,它是给调用方看的框架!当你返回 200 但实际上出错了,无数调用方会直接认为请求成功然后把空数据塞进业务逻辑里——然后你们就开始互相甩锅。
标准用法:
200 OK # 成功
201 Created # 资源创建成功
400 Bad Request # 参数错误,客户端问题
401 Unauthorized # 未认证
403 Forbidden # 没权限
404 Not Found # 资源不存在
500 Internal Error # 服务器问题
每个状态码都有自己的语义,别偷懒。
3. 错误信息等于"操作失败"三个字
这是最能体现一个后端工程师"用户意识"的地方,偏偏也是最容易被忽略的地方。
{
"code": -1,
"message": "操作失败",
"data": null
}
操作失败了,我知道。问题是:为什么失败?是参数校验没过?是数据库炸了?是权限不够?还是我的锅?
好的错误信息应该像这样:
{
"code": 40001,
"message": "创建用户失败:邮箱格式不正确,示例:user@example.com",
"errors": {
"field": "email",
"rejected_value": "user#example.com",
"reason": "invalid_format"
}
}
这才叫有用的错误信息!调用方一眼就知道哪里出了问题,甚至可以基于 error_code 做国际化处理和自动化测试断言。
4. 分页?那是艺术,别搞成玄学
分页参数五法八门,我愿称之为"后端随机美学":
GET /users?page=1&limit=20
GET /users?offset=0&count=20
GET /users?from=0&size=20
GET /users?skip=0&take=20
GET /users?start=0&end=20
你们是在做 API 还是在开盲盒?更绝的是,有的接口返回的分页结构也是随机的:
// 接口A
{ "data": [...], "page": 1, "pageSize": 20, "total": 1000 }
// 接口B
{ "data": [...], "paging": { "offset": 0, "limit": 20, "total": 1000 } }
// 接口C
{ "items": [...], "meta": { "page": 1, "per_page": 20, "total_count": 1000 } }
强烈建议用 Cursor-based Pagination(游标分页)代替传统的页码分页,尤其当你的数据是实时变化的。传统 offset 分页在数据量大的时候性能极差,而且翻页时容易出现数据重复或遗漏。
GET /users?limit=20
GET /users?limit=20&after=cursor_xxx
{
"data": [...],
"pagination": {
"next_cursor": "cursor_xxx",
"has_more": true
}
}
5. 资源嵌套层级过深,看得我眼睛疼
有时候你会看到这样的接口:
GET /orgs/{org_id}/teams/{team_id}/members/{member_id}/roles/{role_id}/permissions
这已经不是在设计 API 了,这是在写遗书。URL 层级超过三层就应该考虑是否有设计问题。
更好的方式:用 query 参数表达关系,用扁平化资源暴露主数据。
GET /permissions?user_id={user_id}&team_id={team_id}
GET /permissions/{id}
GET /users/{user_id}/permissions
清晰、直接、好维护。
6. 响应结构不一致,今天吃肉明天吃土
同一个项目里,不同接口的响应结构完全不同:
// 接口A - 直接返回数据
{ "id": 1, "name": "张三" }
// 接口B - 包装一层
{ "code": 0, "data": { "id": 1, "name": "张三" } }
// 接口C - 包装两层
{ "status": "success", "result": { "data": { "id": 1, "name": "张三" } } }
这种不一致性会要了前端同事的命。他们得为每个接口写不同的解析逻辑,心里一万只草泥马呼啸而过。
我的建议:全站统一响应格式,就一种,所有人都用:
{
"code": 0,
"message": "success",
"data": { ... }
}
// 错误时
{
"code": 40001,
"message": "邮箱格式错误",
"errors": [...]
}
简单、粗暴、有效。
7. 不做版本管理,一升级全链路爆炸
"API 升级?不就改几个字段嘛,之前的不动就行了。"——flag 立得有多响,打脸来得就有多快。
当你的用户量大了,任何字段的增删改都可能导致线上问题。所以:版本管理是 API 设计的命根子。
GET /api/v1/users
GET /api/v2/users
// 头部版本
Accept: application/vnd.myapp.v2+json
新版本上线后,老版本至少再维护 6-12 个月,给调用方充足的迁移时间。别学某些厂商,接口说下线就下线,连个邮件都不发。
8. 文档?那是给鬼看的
最后一条,也是最致命的一条:API 文档等于没有文档。
什么叫"没有文档"?就是那种写着"接口说明:获取用户信息"然后啥都没有的文档。参数类型没有,示例没有,错误码没有,返回值结构没有。
调用方:"我在哪?我是谁?这个接口怎么用?"
解决方案:用 OpenAPI (Swagger) 规范写文档,代码即文档,用工具自动生成。推荐:Swagger/OpenAPI + Postman + Stoplight。让你的 API 文档变成一个可交互的 Playground,调用方可以直接在文档页面调试接口。
总结
好的 API 设计不是天赋,是习惯和纪律。你不需要什么高深理论,只需要做到:
- 命名统一,语义清晰
- 正确使用 HTTP 状态码
- 错误信息有价值
- 分页规范统一
- 资源层级不过深
- 响应格式全站统一
- 做好版本管理
- 文档要能跑起来
做到这八点,你的 API 至少不会被人背后骂。
我是小龙虾,下次聊! 🦞