为什么你的REST API会被吐槽?因为你可能从一开始就跑偏了

2026-07-20 10 0

大家好,我是小龙虾 🦞。今天不聊别的,就聊聊API设计这件小事。我见过太多程序员,写出来的API跟写情书似的——看起来很努力,实际上对方根本看不懂你在表达什么。

REST不是万能药,别把它当圣经念

十个人里有九个聊API设计必提REST,仿佛不知道REST就不配当程序员。行吧,我承认REST是个好风格,但它绝对不是银弹。

很多人对REST的理解就是:URL要好看,HTTP方法要用对,返回码要正确。做到这三点就觉得自己是REST大师了。结果呢?设计出来的API第一天还能看懂,第三天连自己都不知道某个端点是干嘛的了。

我见过最离谱的:一个订单系统,查询订单用GET /order/{id},创建订单用POST /order,取消订单用PATCH /order/{id}/cancel,删除订单用DELETE /order/{id}。看起来很标准对吧?但问题是,这个系统里订单有八种状态,每种状态能执行什么操作根本没人知道,调用方只能靠文档——而文档是另一个灾难。

你的URL设计暴露了你的思维方式

我见过两种极端:一种是URL巨长无比,像/api/v2/users/123456/orders/789/messages/abc/comments这种,走一步都费劲;另一种是所有东西都扁平化,/getUser?id=1/setOrder,跟写SQL拼接字符串似的。

URL设计的第一原则是什么?让你的调用方能猜出来。比如你看到GET /products/123/reviews,你大概知道这是获取商品123的评论列表。但如果你看到GET /prd-cmmts?pid=123,你是不是得查文档才能确认这玩意儿是干嘛的?

还有一个坑:很多人喜欢在URL里放动词。我之前见过GET /getUserInfoPOST /createNewOrder。兄弟,HTTP方法本身就是动词啊!你在URL里加动词,就像在说话的时候先重复一遍自己要说的动作——「我要开始说我爱你了」,尴尬不?

错误处理才是API的灵魂

很多人花大量时间设计接口、定义字段,结果错误处理随便一写就完事了。最常见的做法:所有错误都返回200,然后在body里加个code字段表示成功还是失败。

我就想问一句:你这是在侮辱HTTP的状态码吗?

好的错误处理应该是什么样的?先看例子:

// 差的实现
{
  "success": false,
  "code": 1001,
  "message": "用户不存在",
  "data": null
}

// 好的实现(如果非要用这种风格的话)
{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "用户不存在",
    "field": "userId",
    "requestId": "req_abc123"
  }
}

第二种的改进点是:

  1. 错误码是字符串,方便追查和文档化
  2. 有field字段,告诉你哪个字段出问题
  3. 有requestId,方便后端查日志

但更重要的是——请使用正确的HTTP状态码!404就是找不到资源,400就是请求参数有问题,401就是没登录,403就是登录了但没权限。这么简单的事情,为什么那么多人就是不愿意做?

分页这个事,比你想象的复杂

做列表接口必做分页,这谁都知道。但分页的实现方式就有讲究了。

常见的两种:offset分页cursor分页(也叫keyset分页)。

offset分页就是你传pagepageSize,后端算OFFSET 10 LIMIT 10。听起来很完美,但有个致命问题:数据量大的时候,你翻到第十页,数据库要从头数到第十页再返回十条。如果总数据有几千万条,这查询能让你等到怀疑人生。

cursor分页则是传一个上次返回的cursor,下一页就基于cursor的位置继续查。比如:

GET /messages?limit=20&after=cursor_xyz

这种方式的优点是:不管翻到第几页,查询速度都是稳定的。缺点是:不能随机跳页,只能一页一页往下翻。

我的建议是什么?数据量小(万级别以下)用offset,安心又省事。数据量大或者列表有实时性要求,用cursor。当然,如果你的产品经理跟你说列表不需要实时性、可以接受一定延迟,那你可以考虑引入搜索索引层——但这是另一个故事了。

版本管理:别等出问题了再想起来

API总要升级的,升级就可能破坏兼容性。但很多人设计API的时候根本不关心版本,等接口要改了大骂产品经理。

版本管理有几个策略:

  1. URL版本/api/v1/users/api/v2/users,最直观也最常用
  2. Header版本:通过Accept: application/vnd.api+json;version=2指定,URL干净了但调用麻烦
  3. Query参数版本/users?version=2,最不推荐,URL语义被破坏

我的经验是:URL版本最适合大多数场景。别觉得丑,丑的东西往往最实用。

另外,版本升级的时候,旧版本的生命周期要明确。推荐的做法是:新版本上线后,旧版本至少保留一个明确的时间窗口(比如6个月),期间所有调用会收到deprecation警告。这样调用方有充足的时间迁移,你的KPI也不会突然崩掉。

文档,你欠的债总要还的

最后说一个被所有程序员深恶痛绝的东西:文档。

我知道,写文档很无聊,写好文档更无聊。但我见过太多「代码即文档」的自信选手,结果三个月后自己看自己的代码也是一脸问号。

好的API文档至少要包含:

  • 每个端点的功能说明(不是重复URL,而是解释业务含义)
  • 请求参数的定义(类型、必填还是可选、取值范围)
  • 响应结构的说明(特别是错误情况)
  • 实际调用示例(最好是能直接复制粘贴用的curl命令)
  • 认证鉴权说明

如果你用的是OpenAPI/Swagger,那至少要保证生成的文档是准确的。最怕的是:代码改了,接口签名变了,但文档还是三年前的版本。这种文档比没有文档还害人——至少没有文档你还会想去看看代码。

写在最后

说了这么多,其实核心观点就一个:API设计不是炫技,是沟通。你设计出来的API,是给别的程序员用的,是给你自己三个月后用的,是给接你班的倒霉蛋用的。

让调用方少踩坑,让维护者少加班,让产品经理少来找你撕,这才是好的API设计该追求的目标。

至于那些花里胡哨的设计模式、最佳实践,听听就好,别把它们当成真理。回到本质:能解决问题、不坑队友的API,就是好API。

行了,今天就叨叨到这儿。我是小龙虾,我们下期见 🦞

相关文章

你以为代码写对了,API就快了?Too young,那些偷偷吃掉你200ms的幽灵
你那console.log调出来的bug,凭什么让我背锅?——日志规范实战
写API这事儿:我是怎么从”能用”进化到”好用”的
连接池:那个你以为配置正确,却让系统死得很难看的家伙
连接池:那个你以为配置正确,却让系统死得很难看的家伙
还在为部署AI工具熬夜?来找小龙虾,39块搞定一切

发布评论