我见过最离谱的10个API设计,看完血压都高了

2026-06-29 9 0

干了这么多年后端,我见过各种奇葩的API设计。有些让人拍案叫绝,有些让人怀疑人生。今天咱们就来聊聊那些让人血压飙升的API反模式,顺便看看怎么避坑。

1. 枚举值用字符串,状态码用魔法数字

这个真的太常见了。我见过一个订单状态字段,存储的是"pending""processing""shiped"(拼写错误)、"completed"。你问为什么不用数字?开发说:"数字不直观啊。"

好家伙,那为什么有些地方又用数字1、2、3、4表示同样的状态?然后前端if else写了一堆,还要对着文档猜每个数字什么意思。这就是传说中的"双协议并行策略",文档写不来,代码也读不懂。

最佳实践:枚举就用枚举。数据库可以是数字,API响应里建议用字符串,但必须严格定义枚举值范围。别让你的调用方玩猜谜游戏。

2. HTTP状态码是什么,能吃吗?

见过最离谱的:一个POST接口,创建成功了,返回200,body里写着{"code": 0, "message": "success"}。创建失败了,还是返回200,body里写着{"code": 500, "message": "服务器冒烟了"}

我当时就震惊了。200 OK,OK你个头啊!这是对HTTP协议最大的侮辱。建议把这种设计写进"如何激怒一个前端工程师"的教科书第一章。

// 正确示范
HTTP 201 Created
Location: /orders/12345
{"id": 12345, "status": "created"}

// 错误示范(我见过的真实案例)
HTTP 200 OK
{"code": 201, "msg": "创建成功", "data": null}
// 或者
HTTP 200 OK
{"success": false, "error": "余额不足"}

3. 分页参数随心所欲

这个真的是重灾区。page、pageNo、page_num、pn、pagenum、offset、start、startIndex、skip、cursor、page_token、next_cursor……我怀疑每个后端开发都觉得自己是第一个做分页的人。

更绝的是,有些接口page从0开始,有些从1开始。有些返回total,有些不返回。有些用cursor分页但token是base64编码的数组[10, 20],前端解析出来是乱码。

我曾经花了一整下午,就为了搞清楚"到底这个接口是怎么分页的"。结论是:没人知道,最初写这个的人已经离职了。

4. 错误的嵌套层级

有时候API结构能反映出设计者的精神状态。比如:

{
  "data": {
    "result": {
      "info": {
        "user": {
          "name": "张三",
          "user_info": {
            "age": 25,
            "userAge": 25
          }
        }
      }
    }
  }
}

你问我为什么user_info里还有userAge字段?哦,因为user对象里没有age字段。等等,user对象里不是有name和user_info吗?那个age去哪儿了?

这种"套娃式"设计,一般是经历了多人接手、每次需求都是往现有结构上加字段的结果。最终变成一个俄罗斯套娃,每一层的字段命名还tm不一致。

5. 命名玄学,同一个东西有n种叫法

userId、user_id、userIdStr、uid、user_ident、accountId、memberId、ownerId……

在一个系统里,这些可能都指同一个东西。为什么要这样?因为每个开发都觉得自己的命名"更规范"。最后调用方只能写一个巨大的映射表:

const idMapping = {
  userId: user_id,
  uid: user_id,
  userIdent: user_id,
  ownerId: user_id,
  // ... 200行
}

6. 文档?那是啥玩意儿

有些接口,文档写的是:

获取用户信息

参数:user_id (int) - 用户ID

返回:用户信息

没了。返回的字段有哪些?类型是什么?枚举值有哪些可选?有没有隐藏字段?这文档比我的前女友还神秘。

更可怕的是,文档是文档,代码是代码,线上跑的是另一套。你永远不知道文档里写的"已废弃"字段为什么还在生产环境疯狂返回数据。

7. 批量接口?不存在的

产品经理:"这个列表页要展示100个用户的头像。"

初级后端:"好,我写个for循环调100次getUserInfo。"

这个for循环,直接把数据库和服务器CPU送走。前端加载一个页面,发了100个请求,后端数据库被冲垮,老板把后端叫过去骂了30分钟。

批量接口不是"优化",是基本常识。id=1,2,3,4或者直接POST body传数组,都比循环调用强100倍。

8. 字段类型随心所欲

price字段,有时候返回字符串"29.99",有时候返回数字29.99,有时候返回整数2999(分)。created_at有时候是时间戳,有时候是ISO字符串,有时候是北京时间字符串,有时候是UTC但没标注时区。

前端拿到数据,先做一轮类型转换。转换失败就崩,转换成功也不一定对,因为不知道到底是元还是分。

9. 错误信息是最大的谜语

{
  "code": -1,
  "message": "操作失败",
  "data": null
}

操作失败?什么操作?为什么要失败?是服务器炸了还是我的参数有问题?code -1是什么?和code -2有什么区别?

好的错误响应应该长这样:

{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "账户余额不足,需要100元,当前余额50元",
    "field": "balance",
    "request_id": "req_abc123"
  }
}

让调用方知道错在哪、为什么错、怎么解决。这不是过度设计,这是基本的工程素养。

10. API版本管理?不存在的

最后这个堪称经典:没有任何版本管理。/api/getUser、/api/user/info、/v1/getUser、/v2/getUser、/v3/getUserInfo同时存在于一个系统里,每个版本行为还不太一样。

调用方:我到底该调哪个?

后端:都行,都没废弃。

调用方:那我调了出问题了算谁的?

后端:看脸。

总结

好的API设计其实没那么难,难的是"多想一步"的意识和"保持一致"的纪律。以下是我总结的几个核心原则:

  • 状态码要对:201就是201,400就是400,别什么都返回200然后在body里糊弄
  • 命名要统一:制定规范,遵循 camelCase 还是 snake_case 全链路一致
  • 错误要有意义:让调用方能定位问题,而不是在错误信息里写"服务器异常"
  • 文档要同步:代码即文档,文档即代码,要么就用OpenAPI/Swagger老老实实生成
  • 版本要管理:破坏性变更必须走版本迭代,老接口给足过渡期

API设计是给调用方看的。你写的每一行代码,吐出的每一个字段,都可能是某个前端半夜加班排查问题的线索。善待你的合作方,就是善待未来的自己。

祝大家的接口都能平稳运行,少一些玄学,多一些确定。

相关文章

这条SQL差点让公司数据库原地升天——一个真实的性能调优血案
从笨拙到默契:我与 OpenClaw 的相爱相杀
RESTful API 设计路上踩过的那些坑,今天全部交代
从MySQL迁移到PostgreSQL:那些没人告诉你的血泪避坑指南
SQL写得丑,数据库背锅:七个让查询变慢的作死操作
OpenClaw 使用经验分享:这只小龙虾是如何炼成的

发布评论