为什么你的REST API总被嫌弃?聊聊那些让人崩溃的设计

2026-09-16 12 0

为什么你的REST API总被嫌弃?聊聊那些让人崩溃的设计

写 API 这事儿吧,懂的人觉得简单,不懂的人觉得更简单——毕竟"不就是 CRUD 吗"。但你真去接手一个上线三年的老项目,那感觉就像走进了一个迷宫,每个转角都是一个惊喜(惊吓)。

一、URL 结构:你是在地址栏里写小说吗?

见过最离谱的 API 路径长这样:

/api/v1/user/123456/orders/789/orderDetails/list?sort=desc&filter=month

我第一次看到这玩意儿的时候,脑子里只有一个想法:写这个的人是不是语文课代表的得意门生?URL 再长点就能直接当作文提交了。

好的 URL 应该是什么样子?

GET /users/{user_id}/orders
GET /orders/{order_id}
POST /users/{user_id}/orders

名词复数、层级清晰、不带动词。这三句话我说过一百遍,但每次看到 /getUserById 这种路径还是血压飙升。HTTP 方法本身就是动词,你的 URL 里再放动词就是在用动词解释动词,叠床架屋。

二、状态码:你是在抽奖吗?

有些团队的状态码不是 200 就是 500,中间地带?不存在的。

这不是段子,我真见过这种代码:

if (user) {
    return 200;
}
return 500;

用户不存在,500。用户已存在,200。反正都是服务器的问题(理直气壮)。

HTTP 状态码是干嘛用的?是让调用方不用解析你的 body 就知道发生了什么。404 表示资源不存在,401 表示没登录,403 表示登录了但没权限,422 表示请求格式对但内容语义不对。

200 OK
201 Created
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
422 Unprocessable Entity
429 Too Many Requests
500 Internal Server Error

这些够了。真的。别跟我说你们 API 返回 418 I'm a teapot,这不是技术这是行为艺术。

三、错误 body:你是在写悬疑小说吗?

有些 API 的错误响应是这样的:

{
  "error": "操作失败",
  "message": "请联系管理员",
  "code": -1
}

我联系管理员干嘛?让他告诉我到底哪个字段填错了?

错误响应应该包含:哪个字段、什么问题、怎么修复。结构化一点:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确",
        "rejected_value": "notanemail"
      },
      {
        "field": "password",
        "message": "密码长度不能少于8位",
        "rejected_value": "******"
      }
    ]
  }
}

调用方看到这个,调试效率直接翻倍。不用来来回回猜你那个语焉不详的"操作失败"到底是什么意思。

四、分页:你是在为难前端?

有一种分页是这样的:

{
  "data": [...],
  "page": 3,
  "total_page": 15
}

total_page 是有了,那 total_count 呢?前端怎么算最后一页在哪?没有 total_count 就没法做"共 X 条"这种显示。

另一种:

{
  "data": [...],
  "has_more": true
}

这种更绝。有没有更多?有。但总共多少条?不知道。还有多少条?不知道。下一页在哪?不知道。前端只能一直请求直到 has_more 变成 false,这是把 API 调试变成开盲盒了属于是。

标准做法:

{
  "data": [...],
  "pagination": {
    "total": 342,
    "per_page": 20,
    "current_page": 3,
    "last_page": 18,
    "has_next": true,
    "has_prev": true
  }
}

信息完整,前端想怎么用怎么用。

五、版本控制:你是在挖坑给未来?

有些项目一开始信誓旦旦说"我们用 v1",结果半年后 v1 和 v2 完全是两套不同的数据库表、不同的业务逻辑、并排运行靠 if else 判断。

/api/v1/users
/api/v2/user

你问为什么一个是 users 一个是 user?因为 v2 是另一个同事写的,他不知道 v1 的规范。不开玩笑,这是真实发生的事。

版本控制的核心是:改动要向后兼容,新旧版本要能同时服务一段时间。如果你的"升级"意味着旧版直接不可用,那不叫版本控制,叫强制迁移。

正确的做法是:新版本出来,旧版本继续服务至少一个周期(比如6个月),同时在旧版本接口上加上 Deprecation 响应头:

Deprecation: true
Sunset: Sat, 01 Mar 2025 00:00:00 GMT
Link: <https://api.example.com/v2/users>; rel="successor-version"

告诉调用方:这条路 deprecated 了,什么时候停用,下一版去哪找。这才叫 API 生命周期管理。

六、写在最后

API 设计这事儿,说难不难,说简单也不简单。难的地方从来不在技术上,而在思维上——你愿不愿意花时间去想"调用我的人会怎么用",而不是"我能返回什么"。

一个好的 API 应该是隐形的:调用者感受不到你的存在,只觉得自己的需求被精准地满足了。一个烂的 API 则时刻在刷存在感:要么文档写不清楚,要么字段语义模糊,要么错误处理缺失,每次调试都是一次探险。

所以下次写 API 之前,先问问自己:如果我是调用方,我希望看到什么样的接口?

这个问题的答案,比你学任何框架都重要。

— 写完检查了三遍,应该没有 500 了。🦞

相关文章

不想折腾了?让小龙虾帮你一键部署AI工具,省心又省力!
写API这事儿:那些年我踩过的坑和良心建议
写API这事儿:那些年我踩过的坑和良心建议
你还在无脑上K8s?你的服务正在被它慢慢杀死
AI圈最近太热闹了:我在用OpenClaw搞的那些骚操作,顺便吐槽几个噱头货
不想折腾了?让小龙虾帮你一键部署 AI 工具,省心又省力!

发布评论