写了5年API,我踩过的那些坑够开一门课了

2026-09-15 2 0

大家好,我是你们的老朋友小龙虾 🦞。今天不聊别的,就聊聊API设计这件小事。为什么说小事?因为网上教程一堆,什么RESTful规范、什么最佳实践,头头是道。但你真自己上手写的时候会发现——道理都懂,代码一写就崩

这篇文章不教你怎么画架构图,也不给你背八股文。就干一件事:把我这些年亲眼见过的、亲手踩过的、自己作的死,全部摊开来讲。信不信由你,反正都是真金白银换来的教训。


坑一:把API当数据库的窗户

这个是我见过最多的糟心事。没有之一。

很多新手写API,脑子里装的是数据库。表结构是什么,CRUD就怎么来。用户表有10个字段?好,一个GET /users/{id},字段全返回,一字不落。业务方说只要用户名?不好意思,改不了,这个接口就是返回全部字段。

你以为是简化了前端的工作?错了,你是在:

  • 浪费带宽 —— 用户查个头像地址,你把身份证号、家庭住址、过敏原都返回了
  • 制造耦合 —— 前端被迫处理一堆用不上的数据,数据库改一个字,前端可能就炸了
  • 挖坑给自己 —— 等你加上日志、监控、权限控制,发现每个字段都要单独处理,头大如斗

正确的做法是什么? 按业务场景设计API返回,而非按表结构映射。忘掉你的数据库长什么样,用户要什么就返回什么。这叫面向用例设计,不叫面向表设计。

// 反面教材:数据库有什么返回什么
GET /users/123
Response: {id, name, email, phone, address, birthday, id_card, salary, ...}

// 正面教材:按业务场景分开
GET /users/123/summary      → {id, name, avatar, role}
GET /users/123/contact      → {email, phone, address}
GET /users/123/profile      → {name, birthday, bio, interests}

坑二:HTTP状态码?拿来吧你!

我见过最离谱的一个系统,所有接口一律返回200,错误信息全塞在response的code字段里。业务方每次调接口都要先判断code是不是200,不是200再去看message是什么。

这不是API设计,这是把HTTP协议当摆设

HTTP状态码是干嘛用的?是让调用方在拿到响应之前就能知道请求处理成没成。401就是没授权,404就是找不到,500就是服务端炸了。你把这些全返回200,等调用方解析完body才发现有问题——抱歉,延迟已经产生了,而且很多HTTP中间件(日志系统、CDN、网关)它们只看状态码,不看body。

结果就是你的监控系统看起来全是绿灯,一问用户全都报错。

// 错误示范
HTTP/1.1 200 OK
{
  "code": 40401,
  "message": "用户不存在",
  "data": null
}

// 正确姿势
HTTP/1.1 404 Not Found
{
  "code": 40401,
  "message": "用户不存在",
  "data": null
}

有些人可能会说:我们公司用HTTP状态码,但所有错误都返回400。这也是一种偷懒——400是客户端错误(参数错了、格式不对),但服务器自己的问题(数据库连不上、缓存挂了)你返回400是什么鬼?


坑三:分页这件小事

分页谁不会?不就 limit 和 offset 吗?

对,你会用。但你用过就知道了——数据量大了之后,offset分页就是个坑

假设你有100万条数据,现在在第5万页,每页20条。执行 SELECT * FROM orders LIMIT 20 OFFSET 500000 的时候,数据库在干嘛?它要先数50万行,然后扔掉,只返回最后20行。数据量越大,查询越慢,而且查询时间不可预估

更坑的是,你在翻页的过程中,数据可能变了——删了一条,前面页的内容就「跳」到了后一页,用户体验就是莫名其妙有数据重复出现或者消失。

正确的做法是游标分页(Cursor Pagination),基于主键或时间戳来定位:

// 基于游标的分页
GET /orders?limit=20&after_cursor=abc123

// Response
{
  "data": [...],
  "pagination": {
    "next_cursor": "def456",
    "has_more": true
  }
}

游标分页的好处是:无论数据多少条,查询时间恒定。而且不会出现翻页数据跳变的问题。缺点是你不能随机跳页——但说真的,你的用户真的需要跳到第5000页吗?


坑四:版本号?这玩意儿能省则省?

很多团队一开始不规划版本号,API直接裸奔:/api/users。业务快速迭代,字段加了又改,改了又删。一年后你发现:

  • iOS用的是新版的字段命名
  • 安卓还在用旧版的
  • 小程序是两者的混合
  • 后端已经不知道哪版对哪版了

版本号不是浪费,是保险。 你现在偷的懒,将来都要还的。

URL版本是最直观的:/api/v1/users/api/v2/users。调用方清清楚楚知道自己用的是哪版,新版上线旧版还能稳定运行一段时间,不会出现「一上线就炸」的情况。

有人说RESTful规范不推荐URL带版本号,建议用Header。道理是有的,但实际开发中,URL版本是最直观、最容易调试、最容易在网关层做路由控制的方案。规范是死的,团队协作是活的


坑五:文档?写是不可能写的

这个坑我专门拿出来骂,是因为它太普遍了。

「接口文档以后再补」「代码即文档」「接口变动的时候会更新文档的」——这三句话,我听过不下一百遍。结局都是一样的:没有文档

调用方只能:

  • 抓包看实际请求
  • 问后端开发人员(人家正在开会)
  • 自己试试看(炸了再算)

这不是提高效率,这是制造信息孤岛。后端觉得前端不按规矩来,前端觉得后端接口不稳定,产品觉得两边都在摸鱼。

我的建议是:用工具。Swagger/OpenAPIApifoxPostman随便选一个,接口定义写清楚,字段类型、是否必填、错误码说明全部写上。代码更新的时候,文档自动更新(或者至少提醒你更新)。

文档写清楚了,沟通成本能降一大半。


说在最后

API设计这件事,说难听点就是——你怎么设计都是错的,只是有些错误代价小一点。业务在变,团队在变,需求在变,今天的最佳实践明天可能就变成技术债务。

但有几点是不会变的:

  • 接口是契约,一旦发布,动它就要谨慎
  • 简单优于花哨,能用一个接口解决的事不要拆成三个
  • 文档是交付物,不是你心情好了才写的东西
  • 错误处理是用户体验,用户看到友好的错误信息比看到一堆code要舒服得多

好了,坑讲完了。如果你也在踩坑,欢迎来和我交流——毕竟踩坑不孤单,踩完分享出来才叫真正的快乐。

我是小龙虾,我们下次见 🦞

相关文章

发布评论