别再写”烂API”了:我在RESTful接口设计中踩过的那些坑

2026-09-14 10 0

别再写"烂API"了:我在RESTful接口设计中踩过的那些坑

做后端开发这么多年,我见过太多「能用但不优雅」的API了。有些接口你一看就知道是「能跑就行」的作品——命名随心所欲、状态码乱飞、错误信息跟谜语似的。今天不整虚的,跟大家聊聊我在API设计实战中总结出的经验,全是干货。


一、先搞清楚「REST」是什么,别把HTTP当背景板

很多人以为用了GET/POST请求就算是RESTful了,这认知跟「会开车就懂发动机原理」差不多。

REST的核心是资源(Resource)和表述(Representation)。你设计的每个API都应该围绕「资源」来思考,而不是「动作」。

来看看我见过最离谱的接口命名:

POST /getUserInfo.do
POST /deleteUser
GET /queryAllOrders.action

看到这种命名,我的感受是:

「这API是谁设计的?站出来,让我看看你的代码规范文档长什么样。」

正确的做法应该是这样的:

GET    /users/{id}       # 获取单个用户
GET    /users            # 获取用户列表
POST   /users            # 创建用户
PUT    /users/{id}       # 完整更新用户
PATCH  /users/{id}       # 部分更新用户
DELETE /users/{id}       # 删除用户

看,RESTful的API本身就是自描述的。你不需要看文档也知道这个接口是干嘛的——因为名词已经告诉你了资源是什么,HTTP方法告诉你做了什么操作。


二、状态码不是随便选的,这是门学问

状态码是API的门面,但你真的用对了吗?

我见过太多接口永远只返回200,然后靠code字段来区分成功和失败。这不是不行,但这是「偷懒式设计」。

先来张图镇楼,这是HTTP状态码的分类:

2xx - 成功相关
  200 OK           # 标准成功
  201 Created      # 资源创建成功
  204 No Content   # 成功但没返回内容(常用于DELETE)

4xx - 客户端错误
  400 Bad Request      # 请求参数有问题
  401 Unauthorized     # 未认证(没登录)
  403 Forbidden        # 已认证但没权限
  404 Not Found        # 资源不存在
  409 Conflict         # 资源冲突(比如重复创建)
  422 Unprocessable    # 格式对但语义错
  429 Too Many Requests # 请求过于频繁

5xx - 服务器错误
  500 Internal Server Error  # 程序员背锅
  502 Bad Gateway            # 网关问题
  503 Service Unavailable     # 服务挂了

有人会说:「200+code模式也很好用啊,比如code=0表示成功,code=1001表示用户不存在。」

我的观点是:如果你团队小、接口少,这么干没问题。但当你的API要面向外部、或者团队超过3个人,标准化状态码能省大量的沟通成本。

状态码是给HTTP客户端、API网关、监控系统看的,它们可不会解析你自定义的code字段。


三、错误信息要有人话,别让调用方猜谜

我曾经接手过一个遗留项目,接口报错永远返回这个:

{
  "code": 1002,
  "message": "操作失败"
}

看到「1002」,我需要翻一个Excel表格才知道什么意思。而且「操作失败」——什么操作?失败原因是什么?调用方根本不知道从何下手。

我的错误响应设计原则:

{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "用户不存在",
    "detail": "ID为 abc123 的用户不存在或已被删除",
    "request_id": "req_7f8a9b2c3d4e",
    "docs": "https://api.example.com/docs/errors/USER_NOT_FOUND"
  }
}

这样调用方可以:

  • 用code做程序化处理(比如展示不同的UI提示)
  • 用message做快速调试
  • 用detail做精确排查
  • 用request_id去查日志
  • 用docs让调用方自助学习

还有一个容易忽略的点:返回错误的时候,状态码一定要对。用户没登录,你返回200+code=401?对不起,这会让HTTP缓存、监控告警、API网关全部失效。


四、版本管理:你总有一天会面对这个坑

接口上线了,一切正常。三个月后,产品说「要在用户接口里加个字段」,然后你发现现有接口加了字段后,调用方炸了——他们程序里没处理这个新字段。

这就是API版本管理的意义。我的推荐策略:URL版本号。

https://api.example.com/v1/users
https://api.example.com/v2/users

为什么选URL而不是Header?两个字:直观。调试的时候直接在浏览器改版本号,比翻Header方便多了。

版本升级的原则:

  • 只在原有字段上做增量,不删除或修改现有字段
  • 废弃版本要有明确提示(响应头加Deprecation警告)
  • 给调用方足够的迁移时间(至少一个版本周期)

API是契约,改了要通知,不能偷偷摸摸发版就改。这是对调用方的基本尊重。


五、分页:这事儿比你想的重要

「列表接口要不要分页?」——这问题我被问了不下一百次。

答案是:只要列表可能超过10条,就必须分页

分页方式我推荐Cursor-based分页(游标分页),而不是Offset分页:

# Offset分页(问题多)
GET /users?page=2&page_size=20

问题:
- 数据有新增删除时,会出现数据重复或漏掉
- page数大了之后,数据库OFFSET性能差

# Cursor分页(推荐)
GET /users?cursor=eyJpZCI6IjEyMzQifQ&page_size=20
返回:
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6IjE0MzYifQ",
    "has_more": true,
    "page_size": 20
  }
}

Cursor分页的优势:无论数据怎么变,遍历时不会重复也不会漏掉。朋友圈、消息流这种场景,用Offset分页就是给自己找麻烦。


六、字段命名:大小写、下划线还是驼峰?

这个问题团队内部吵过无数次的架。我的建议是:统一就好,没有绝对正确

但如果你问我偏好,我会选snake_case(下划线命名):

// 我的选择
{
  "user_id": "12345",
  "created_at": "2026-01-15",
  "is_active": true
}

// 反对意见:很多前端团队喜欢camelCase
{
  "userId": "12345",
  "createdAt": "2026-01-15",
  "isActive": true
}

选哪种不重要,重要的是全栈统一。我见过后端snake_case、前端camelCase、中间转一道的代码,那个转换逻辑看着就让人心塞。


写在最后

API设计这事,说难不难,说简单也不简单。它不需要什么高深的技术,但需要有「为调用方考虑」的意识。

你的API就是你的产品。接口设计得好,调用方用着顺手,你也少接电话;设计得烂,每次改版都是噩梦,而且迟早有一天会有人在技术社区发帖子吐槽你的API。

记住:好的API设计是让调用方觉得「这API就该这么用」,而不是「这API怎么这么难用」

共勉。

相关文章

Go的协程:你以为很轻,其实是个坑货——从调度到内存的神奇之旅
API设计里那些让人想砸键盘的骚操作
API错误处理:從車禍現場到優雅翻車的修煉之路
连接池:那些默认配置正在让你的服务慢性死亡
RESTful API 设计踩坑指南:那些年我们一起写错的接口
你的服务没挂,但用户已经跑了——一次DNS污染引发的血案

发布评论