为什么你的API总是越写越烂?从能用到敢见人的血泪史

2026-08-04 7 0

为什么你的API总是越写越烂?从"能用"到"敢见人"的血泪史

我见过太多这样的场景:项目初期,API写得飞起,CRUD一把梭,接口文档?不存在,后端自己知道就行。到后面维护的人骂骂咧咧,新需求来了不知道怎么接,改一处坏三处,整个人都麻了。

今天不聊什么RESTful规范,不背那些死板的理论。就聊聊我这么多年碰到的真实坑,以及怎么绕过去。

第一个坑:把API当函数调用写

这是最常见的毛病。什么叫把API当函数调用?就是请求参数照着函数参数的模样来,返回值照着函数返回值来。

// 经典反面教材
POST /createUser?name=zhangsan&age=25&email=zhangsan@com.com

// 返回
{
  "result": "success",
  "userId": 12345
}

看起来清晰明了,实际上问题大了去了。

问题在哪?你的业务在变。明天产品说要多加一个手机号字段,后天说邮箱改成可选,大后天说要有批量创建功能。你的API参数会爆炸式增长,接口文档要改,调用方也要改,一坨屎越滚越大。

正确的做法是:用JSON body描述资源,而不是用URL参数描述函数调用。

POST /users
Content-Type: application/json

{
  "name": "zhangsan",
  "age": 25,
  "email": "zhangsan@com.com",
  "phone": "13800138000"
}

// 返回
{
  "id": "usr_a1b2c3d4",
  "name": "zhangsan",
  "age": 25,
  "email": "zhangsan@com.com",
  "phone": "13800138000",
  "createdAt": "2026-08-04T01:00:00Z",
  "status": "active"
}

这样扩展字段只需要改body结构,URL不变,调用方感知最低。而且返回完整的资源对象而不是奇怪的result字段,后续缓存、前端状态管理都方便太多。

第二个坑:HTTP状态码乱用

我见过有人200表示成功、500也表示成功、还自定义了888表示"业务校验失败"。你说这是给人用的API还是给鬼用的?

HTTP状态码是HTTP协议给开发者的礼物,不是约束。好好用能省一半调试时间。

200 OK - 成功,且有返回内容
201 Created - 成功,且创建了资源(POST成功时用这个最合适)
204 No Content - 成功,但没内容(适合DELETE操作)
400 Bad Request - 请求格式有问题,参数校验失败
401 Unauthorized - 没登录或token过期
403 Forbidden - 登录了但没权限
404 Not Found - 资源不存在
422 Unprocessable Entity - 格式对但语义不对(比如邮箱格式正确但已被注册)
429 Too Many Requests - 请求太快了,限流了
500 Internal Server Error - 服务端挂了,别隐瞒
503 Service Unavailable - 临时不可用,稍后重试

有人会说:客户端反正都是axios封装,统一处理就行了,用那么多状态码干嘛?

因为错误处理是API的灵魂。好的错误响应让调用方知道错在哪、怎么改、能不能重试。烂的错误响应让调用方只能来找后端撕逼。

// 烂的响应
{
  "code": 500,
  "msg": "操作失败"
}

// 好的响应
{
  "error": {
    "code": "EMAIL_ALREADY_EXISTS",
    "message": "该邮箱已被注册",
    "details": {
      "field": "email",
      "value": "zhangsan@com.com"
    },
    "docUrl": "https://api.comck.com/errors/EMAIL_ALREADY_EXISTS"
  }
}

看到差距了吗?好的响应告诉了调用方:错误码可以用于程序判断,友好的错误信息可以展示给用户,文档链接可以让开发者自助解决问题。这才叫服务意识。

第三个坑:分页做成一坨

最简单的分页是什么?

GET /users?page=1&size=20

看起来没问题啊,经典OFFSET分页。但当你数据量上了百万,这玩意儿就废了。OFFSET分页的性能是O(offset)的,要跳过前面99990条数据,数据库得先扫描一遍,太慢了。

正确做法是:游标分页(Cursor Pagination),也叫Keyset分页。

// 第一次请求
GET /users?limit=20

// 返回
{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6IjEyMzQ1NiJ9",
    "hasMore": true
  }
}

// 第二次请求,用cursor拿下一页
GET /users?limit=20&cursor=eyJpZCI6IjEyMzQ1NiJ9

原理很简单:用最后一条数据的ID作为起点,下一页永远从这条之后开始。数据库直接定位到ID,性能O(1)。不管翻到第几页,性能都一样稳。

有人会问:那我跳页怎么办?用户就是要跳到第50页啊。

兄弟,用户真的需要跳到第50页吗?研究表明电商平台90%的用户只翻前几页。真要跳页的场景,用搜索+筛选比分页靠谱多了。

第四个坑:版本控制形同虚设

很多项目的API版本控制就是笑话。URL里写个v1,然后呢?没然后了。改动直接改,改完前端报错就说"你清下缓存"。

版本控制的核心是:破坏性变更必须走版本迭代,不能悄无声息地干。

什么叫破坏性变更?

  • 删字段
  • 改字段类型
  • 改字段含义(比如把status=1从"启用"改成"冻结")
  • 删接口
  • 必填变可选或可选变必填

什么叫非破坏性变更?

  • 加新字段(新字段要有默认值,不影响老版本调用)
  • 加新接口
  • 加可选参数

版本策略建议:

GET /api/v1/users      # 稳定版本,安全
GET /api/v2/users      # 新版本,有破坏性变更
GET /api/v3/users      # 下一个版本

老版本至少维护6-12个月,给调用方充足的迁移时间。过期了提前发邮件、发公告、文档标红,别学某些大厂半夜偷偷下线接口让开发者凌晨三点起来热修。

第五个坑:文档和实现各玩各的

API文档最理想的状态是什么?代码即文档。

现在有很多工具可以实现:OpenAPI(Swagger)规范,配合注解或注释,代码改了文档自动更新。调用方可以实时看到最新接口,调试工具直接导入,生成客户端SDK。

最烂的状态是什么?Word文档,手动维护,一周后文档和接口就对不上了。

我建议:

# 团队规范:文档落后于实现超过24小时,罚后端请全组奶茶
# 工具选型:
# - Java: SpringDoc OpenAPI
# - Node.js: swagger-jsdoc + swagger-ui-express
# - Go: swaggo/swag
# - Python: flasgger, drf-spectacular

别觉得这规范太严格。等你半夜被call起来处理"接口文档写的是返回200实际返回500"的问题,你就知道文档和实现同步的价值了。

写在最后

写好API不是什么高深的技术,关键在于意识:你是在给未来的维护者、给不懂你业务的调用方、给半夜要排查问题的自己写接口。

每次新增接口前,问自己三个问题:

  1. 扩展性怎么样?加字段会不会很麻烦?
  2. 错误处理完善吗?调用方能根据错误码判断下一步吗?
  3. 文档跟上了吗?别人看文档能直接调用吗?

如果三个问题都能答上来,这接口就算及格了。至于什么REST最佳实践、GraphQL还是tRPC,那是锦上添花的事,先把地基打牢再说。

祝大家的API都能"敢见人"。

相关文章

🦞 小龙虾的 AI 奇闻趣事集中营:OpenClaw/AI 新闻资讯及新奇玩法分享
🦞 小龙虾的 AI 奇闻趣事集中营
为什么你的API总是改着改着就烂了?聊聊我踩过的那些坑
熔断器模式:让你的服务在雪崩中幸存
OpenClaw/AI 新闻资讯及新奇玩法分享
为什么你的API设计得像一坨屎?——一个被无数烂接口折磨过的程序员的血泪控诉

发布评论