写API这件事:80%的程序员都踩过这些坑

2026-05-27 12 0

你以为你在设计API,其实你在制造灾难

入行这么多年,我见过最恐怖的事情不是线上数据库被删,而是某天产品经理兴奋地跑过来跟你说:「那个API再加三个字段吧,很快很简单。」然后你发现那个接口已经被87个客户端在用了。

今天咱们好好聊聊API设计——不是那种背书的玩意儿,是真的实战踩出来的血泪经验。读完了你可能会拍大腿:「卧槽,原来我一直在踩坑。」

坑一:REST不是你想怎么用就怎么用

很多人嘴里说「RESTful」,实际上做的是「RESWhatever」——把 GET 当 POST 用,把 POST 当 PUT 用,返回状态码永远只有200和500,中间地带?不存在的。

举个例子,我见过最离谱的一个接口是这样设计的:

POST /getUserInfo
Content-Type: application/json
{"userId": 12345}

兄弟,这不是REST,这是裸奔。你都 POST 了,body 里还带 userId 干嘛?不嫌别扭吗?

GET /users/12345

这才是正道。资源是名词,动作靠HTTP方法,后面的数字是路径参数,干干净净,清清楚楚。

坑二:状态码是门艺术,不是玄学

我见过太多项目所有接口返回值长这样:

{"code": 0, "message": "success", "data": {...}}

或者更离谱的:

{"status": "ok"}

哥们儿,HTTP协议给你提供了几十个状态码,你不用,非得自己发明一套?这是什么,重复造轮子俱乐部?

状态码是用来表达语义的:

  • 200 OK:成功了,数据在body里
  • 201 Created:资源刚被创建,地址在Header的Location里
  • 204 No Content:成功了但没内容,DELETE的时候常用
  • 400 Bad Request:客户端的参数有问题,别动不动500
  • 401 Unauthorized:没登录,别跟403混用
  • 403 Forbidden:登录了但没权限
  • 404 Not Found:资源不存在,不是「用户不存在」是404,但「用户存在但订单不存在」也该404
  • 422 Unprocessable Entity:格式对了但语义不对,比如邮箱格式正确但已被注册
  • 429 Too Many Requests:你被限流了,给个 Retry-After 头
  • 500 Internal Server Error:服务器炸了,这个真的只有服务器炸了才用

记住一个原则:能用标准状态码就绝不自己发明。你发明的那套code体系,新人来了得先看三天文档才能干活。

坑三:分页是刚需,但你可能做错了

最简单的分页是什么?

GET /users?page=1&size=20

这有什么问题?问题大了。你翻到第5页的时候,第3页的数据被删了——你刷新一下,用户从第3页掉到了第4页,数据错位了。这种问题在生产环境里巨难追查。

正确做法是用游标分页:

GET /users?cursor=eyJpZCI6MTIzfQ&size=20

或者基于创建时间的游标:

GET /orders?after_id=12345&limit=20

返回的时候带上下一页的cursor:

{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTYzfQ==",
    "has_more": true
  }
}

这样无论数据怎么变化,分页结果都是稳定的。用户在第5页刷新的时候,看到的还是那些数据,不会乱跳。

坑四:版本管理是个大学问

API是要版本化的,这点大家都有共识。但怎么做版本化?

常见的三种方式:

第一种:URL版本

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

这是最直观的方式,Netflix和Stripe都在用。好处是调试方便,坏处是多个版本要同时维护。

第二种:Header版本

GET /users HTTP/1.1
Accept: application/vnd.mycompany.v2+json

这很「标准」,但说实话,不直观。调试的时候你得记着这个Header,翻日志的时候眼睛要瞎了。

第三种:Query参数版本

/users?version=2

求你了,别这样。这玩意儿很容易跟业务参数混在一起,时间长了谁分得清哪个是版本哪个是业务参数?

我个人的建议是:URL版本优先,简单直观,利于API发现和管理。维护多个版本不丢人,丢人的是版本之间打架。

坑五:错误信息要厚道

很多API的错误返回是这样的:

{"error": "Invalid parameter"}

这个错误信息扔给前端,前端同学能骂你八辈祖宗。「什么参数?参数在哪儿?为什么Invalid?」

好的错误格式应该是这样的:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "请求参数验证失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      },
      {
        "field": "age",
        "message": "年龄必须大于0"
      }
    ]
  }
}

这样的错误信息,前端可以直接渲染到对应的输入框旁边,用户体验直接拉满。搞成这样,你的同事会感谢你的。

坑六:安全这事别糊弄

CORS这东西很多人糊弄过去了:

Access-Control-Allow-Origin: *

如果你在做一个公开API,这样写情有可原。但如果你的API有认证信息,还敢这样写,等着被人用JSONP抽走用户数据吧。

认证方面,Bearer Token 是标准做法:

Authorization: Bearer 

别把Token放在URL里,URL会被服务器日志、浏览器历史记录、Referer头一路传到天涯海角。Token晒出去了,比密码泄露还麻烦,因为用户往往会用一个密码打天下。

坑七:文档是API的门面

最后说文档。很多人API写完了,Postman测一下,能跑,就交付了。然后别人问他:「这个接口参数是什么?」「不知道,你跑一下试试就知道了。」

这叫什么?这叫「薛定谔的API」——你不测,永远不知道它能不能用。

建议用 OpenAPI Specification (以前叫Swagger) 来写文档。好处是:

  • 可以自动生成客户端SDK
  • 可以自动渲染出漂亮的交互式文档
  • 团队新人来了能快速上手,不需要你亲自讲解每个接口
  • 契约测试——你的实现必须跟文档对上,不对上CI就失败

你要是懒得写文档,那迟早你的同事也会懒得看你写的代码。出来混,都是要还的。

写到最后

API设计这件事,看起来是技术活,实际上是产品思维+技术能力的结合。你设计的每一个字段、每一种状态码、每一版返回格式,都在定义使用你API的人的体验。

好的API是沉默的仆人——调用者不需要说明书就能用明白,用的过程中遇到问题看一眼错误信息就知道怎么改,用久了会觉得这API还挺贴心。

差的API是暴躁的老板——你得猜它想要什么,它给错的时候你不知道是你错了还是它错了,它报错的时候你恨不得把它重写一遍。

愿你我都能多做前者,少做后者。毕竟,写代码已经够累了,API就别再给人添堵了。

共勉。

相关文章

为什么你的代码里藏着一万只魔鬼?——从if-else地狱到状态机的救赎之路
写API这件事:80%的程序员都踩过这些坑
不想折腾了?让别人帮你一键部署 AI 工具,不香吗?
从CRUD到每秒10万:你不知道的Redis骚操作
后端接口写烂被队友锤?我从血泪史里扒出了这10个致命毛病
懒得折腾?让小龙虾帮你一键部署 AI 神器,省心又省力!

发布评论