为什么你的REST API总被吐槽?我总结了10条血泪经验
做后端开发这些年,我写过的、接过的、骂过的API没有一百也有八十。说实话,见过太多「能用但恶心」的接口设计——功能没问题,但用起来让人想把键盘砸了。今天不聊什么高大上的架构理论,就聊点实际的血泪经验,都是我踩过坑之后总结出来的。
1. URL设计:名词对名词,别跟动词较劲
这是最容易翻车的地方。HTTP方法本身就是动作,URL就应该是资源名词。很多人喜欢在URL里塞动词:
❌ GET /getUserById?id=123
❌ POST /deleteOrder
❌ GET /queryProductList
✅ GET /users/123
✅ DELETE /orders/456
✅ GET /products?category=electronics
URL是名词,HTTP方法是动词,各司其职。如果你发现自己想在URL里用动词,先停下来问问自己:是不是把这个操作拆成不同的资源会更好?
2. 状态码不是装饰品,别什么都返回200
这是国内开发者最容易犯的毛病。接口出错了,body里塞个errorMessage,HTTP状态码还是200。你这是在侮辱调用方智商。
❌ HTTP/1.1 200 OK
{"code": 500, "message": "服务器炸了"}
✅ HTTP/1.1 500 Internal Server Error
{"error": "internal_error", "message": "服务器内部错误,请稍后重试"}
标准HTTP状态码就那么些,但足够覆盖大部分场景了:
- 400:客户端的错(参数不对、格式错误)
- 401:没登录
- 403:登录了但没权限
- 404:资源不存在
- 429:请求太频繁了
- 500:服务端炸了
别嫌麻烦,这比在body里塞各种奇怪的code强一百倍。调用方可以统一处理,你也可以少写很多文档。
3. 统一响应格式是基本礼仪
如果你的接口有时返回{"data": {...}},有时返回数组,有时直接返回对象,调用方会疯掉的。我见过一个项目,同一个项目里十几种响应格式,看代码像做阅读理解。
{
"success": true,
"data": { ... },
"message": "操作成功"
}
或者更规范一点,用这种结构:
{
"code": 0,
"message": "success",
"data": { ... }
}
统一的格式让调用方可以统一封装、统一解析、统一错误处理。这不是过度设计,这是基本的人文关怀。
4. 分页不是奢侈品,是必需品
如果你的列表接口不支持分页,等数据量上来就等着背锅吧。返回几百条数据还好,等你返回几万条的时候,数据库、内存、网络带宽全都得炸。
GET /users?page=1&page_size=20
响应里记得带上总数和分页信息:
{
"data": [...],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1523,
"total_pages": 77
}
}
还有,别用offset+limit做深度分页。等你翻到第10000页的时候,数据库得跳着数一万条记录才能给你返回。用游标分页(cursor-based pagination)体验会好很多。
5. 版本控制不是矫情,是生存必需
很多人觉得版本号挂在URL里很丑:
❌ /api/v1/users
❌ /api/v2/users
但等你需要做不兼容的接口变更时,就会知道版本控制是多救命的东西。你让所有调用方一夜之间升级?做梦吧,总有人躺着不动。
推荐的做法是:大版本放URL里(如/v1/),小版本用header控制。这样可以在不破坏现有客户端的情况下,逐步迭代。
6. 字段命名:别放飞自我
我见过最离谱的命名是这样的:
{
"userName": "张三",
"user_name": "李四",
"userName2": "王五",
"NAME": "赵六"
}
这是在写代码还是在开盲盒?选一种命名风格(推荐snake_case或者camelCase),从一而终。团队协作最大的噩梦就是命名混乱——你用这个,我用那个,谁也看不懂谁的代码。
另外,字段名要语义化。userName比uName好,createdAt比ct好。别贪图那一两个字母的简短,三个月后你再看自己写的代码,能认出算我输。
7. 敏感数据:能别返回就别返回
这条简直是血泪教训。曾经有个同事,登录接口把密码的md5值返回给前端了。前端说「反正加密了没事」,结果某天数据库被拖,密码明文都能通过md5碰撞反推出来。
永远不要返回密码相关内容,包括加密后的密码。用户ID、token、最后一次登录时间这些可以,但密码——无论是明文还是加密——都给我删掉。
8. 请求参数校验:别把压力全丢给数据库
很多人觉得参数校验是数据库的事情——只要数据库不报错就行。这是非常危险的想法。
一方面,不校验的参数会让你的业务代码充满各种防御性判断,丑陋无比;另一方面,等无效数据进了数据库再报错,成本可比提前校验高多了。
// 请求参数校验
if (!email || !email.includes("@")) {
return 400 Bad Request;
}
if (age < 0 || age > 150) {
return 400 Bad Request;
}
用validator库(大多数语言都有)做统一校验,比散落在各处的if判断优雅得多。
9. 幂等性:这是个容易被忽略的大问题
什么是幂等性?就是同一个请求执行一次和执行一百次,效果是一样的。对于GET、PUT、DELETE这类操作,理论上都应该是幂等的。
举个非幂等的反例:扣款接口没有防重复提交。用户手抖点了两下,扣了两次钱。这个锅谁背?
解决方案:用唯一请求ID + 服务端去重。用户发起请求时带上client_id,服务端用这个ID做幂等校验。重复请求直接返回第一次的结果,不做任何实际处理。
10. 文档:没有文档的API等于没有API
这条放到最后,但重要程度排前三。代码写完没文档,其他工程师怎么接入?总不能让他们一行行读你晦涩的代码吧?
推荐用Swagger/OpenAPI来写文档。你在写代码的同时顺手就把接口文档完善了,而且还能自动生成客户端SDK。懒是科技进步的第一推动力,这个工具就是让你懒得有价值。
以上十条,说白了就一个核心原则:做API要有同理心。你写的API是给别人用的,不是给自己炫技的。调用方用起来顺畅,你后期维护也省心。
写代码这件事,技术只是底线,品味才是上限。共勉。