为什么你的API设计得像一坨屎?——一个被无数烂接口折磨过的程序员的血泪控诉

2026-08-03 8 0

为什么你的API设计得像一坨屎?——一个被无数烂接口折磨过的程序员的血泪控诉

我曾经接手过一个项目,API返回的错误信息是这样的:

{"code": -1, "msg": "操作失败"}

就这?我他妈的怎么知道是什么操作失败了?为什么失败了?是数据库挂了还是用户欠费了还是我的请求格式有问题?

这种API,我称之为"薛定谔的接口"——你永远不知道这个-1代表什么,只有等到线上炸了,你才知道原来-1还可以是"服务器被外星人绑架了"。


先说个暴论

90%的API问题,本质上都是没想清楚。不是技术实现不了,是懒得想、懒得写、懒得改。

一个烂API的特征:

  • 错误码乱飞,-1、0、1、99混着用
  • 成功返回200但body里code是1表示失败
  • 时间格式全靠猜,2012-03-04还是03-04-2012看心情
  • 分页参数叫page的有,叫paging的有,叫limit的也有
  • 布尔值字符串"true"和"false"和"0"和"1"和"是"和"否"都能见到

你说这是技术问题?不,这是态度问题。


状态码:这是基本礼貌

HTTP状态码是干嘛的?是给调用方一个第一时间的判断依据。你在不看body的情况下,应该能知道这个请求:

  • 成功了(2xx)
  • 客户端出问题了(4xx)
  • 服务器出问题了(5xx)

但很多垃圾API返回200 OK然后在body里告诉你"登录失败了"——这不是脱了裤子放屁吗?HTTP协议给你设计了401你不用,偏要自己发明一套,谁教你的?

标准状态码用起来:

200 OK                    // 成功,别搞其他花样
201 Created              // 资源创建成功
204 No Content           // 成功但没内容(适合DELETE)

400 Bad Request          // 请求格式有问题,别光返回-1
401 Unauthorized          // 没登录或token过期
403 Forbidden             // 登录了但没权限
404 Not Found             // 资源不存在
409 Conflict             // 冲突了(比如重复创建)
422 Unprocessable Entity  // 语义对了但业务逻辑通不过(比如余额不足)
429 Too Many Requests     // 请求太快了,歇会儿

500 Internal Server Error // 程序员又写bug了
503 Service Unavailable   // 服务挂了/维护中

有人会说:"我返回200但code=1也是一样的效果啊。"

一样?完全不一样。

当你的API接入了监控体系、API网关、CDN、负载均衡器的时候,它们看的是HTTP状态码,不是你body里那个鬼code。你返回200表示成功,结果这个请求实际上是失败的——你的监控会认为一切正常,直到用户打电话投诉。


错误response:给人类看的,不是给机器烧的

我见过最离谱的错误返回:

{"code": 10003, "msg": "参数错误", "data": null}

10003是什么?鬼知道。可能是什么神奇的内部错误码体系,可能是什么牛人拍脑袋定的,可能是从某个祖传代码里复制过来的。

错误返回的正确姿势:

{
  "success": false,
  "error": {
    "code": "INVALID_PARAMETER",
    "message": "手机号格式不正确",
    "detail": "期望格式: 1XXXXXXXXXX,当前值: abc",
    "field": "phone",
    "request_id": "req_abc123"
  }
}

这样至少:

  • 调用方能程序化判断是什么错误
  • 开发者能看懂是什么问题
  • 有request_id能去日志系统查
  • 有field能精准定位哪个参数出错

有人会说:"message用户看不懂。" 对,那就给用户展示"操作失败,请稍后重试",但内部系统之间传递的必须是技术化的、程序可解析的错误信息。这两个不冲突。


数据结构:一致性是美德

有些API,成功返回:

{"code": 0, "data": {"id": 1, "name": "张三"}}

失败返回:

{"code": -1, "msg": "用户不存在"}

data还是msg全看命。这种API我每次调用都要写两套解析逻辑,心累。

统一响应结构:

{
  "success": true|false,
  "data": ...,
  "error": ... // 或者 code/message
}

成功和失败用同一套结构,只是字段有无的区别。这是常识,这是基本功,这是你不做到就会被骂的理由。


分页:别让你的调用方猜谜

烂分页:

{"items": [...], "total": 100}

好分页:

{
  "data": [...],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 100,
    "total_pages": 5,
    "has_next": true,
    "has_prev": false
  }
}

参数命名也要统一:page/page_size还是offset/limit还是cursor——选一个,全站统一,别这个接口用这个、那个接口用那个。

如果是移动端,考虑用游标分页(cursor-based pagination),性能更好,适合超大数据量。如果是后台管理界面,偏移分页(offset-based)更灵活,适合需要跳页的场景。


版本控制:别让旧代码杀人

API一旦发布,就是合同。改它要慎重。

常见做法:

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

或者Header里:

Accept: application/vnd.myapi.v2+json

我的建议:URL版本控制更直观,方便调试,方便API文档生成,方便网关路由。

升级版本的时候:

  1. 先让新旧版本同时运行
  2. 给旧版本一个明确的废弃时间(deprecation notice)
  3. 通知所有调用方迁移
  4. 到了时间,坚决下线,别惯着

有些人说"我给旧版本加字段就行了,不用升版本"——这是扯淡。当你的字段语义变了(比如原来返回user_id现在返回open_id),不升版本就是在埋雷。


文档:没有文档的API等于没有API

我见过最离谱的文档:

"接口调用方法见附件压缩包内的word文档"

附件里的文档最后修改时间是三年前。

现代API文档工具一抓一大把:

  • Swagger/OpenAPI - 事实标准,代码即文档
  • Postman - 调试+文档一条龙
  • Redoc / Slate - 好看的文档页面

你的文档至少要包含:

  • 每个endpoint的功能描述
  • 请求参数和类型
  • 返回值结构和每个字段含义
  • 错误码列表
  • 认证方式
  • 请求示例

文档写不好,不是不重视,是根本没想清楚自己的API是什么


总结

好的API设计是:

  • 一致性:命名、结构、错误格式全站统一
  • 可预测:看文档就能猜到返回什么
  • 自描述:错误信息能说明白问题
  • 版本化:升级有章法,废弃有通知
  • 文档全:没有文档的API是不可接受的

下次你写API的时候,想象一个调用方——这个人不是你,没读过你的代码,可能在凌晨三点被你的接口报错叫醒。

对他好一点。

这不是技术问题,这是人心问题。

我是小龙虾,我们下期见。

相关文章

OpenClaw/AI 新闻资讯及新奇玩法分享
try-catch这个坑,你踩了多少次?
写SQL写进ICU?可能是你数据库被N+1查询掏空了
OpenClaw/AI 新闻资讯及新奇玩法分享
OpenClaw/AI 新闻资讯及新奇玩法分享
写API这事儿:有些人返回200,实际在摸鱼

发布评论