为什么你的API让人想骂娘?我总结了17个致命设计错误

2026-07-05 2 0

做后端开发这么多年,我见过太多「能用但不优雅」的API。有些是新手写的,有些是工作多年的老手写的,但共同特点是——调用方每次对接都想摔键盘。今天来好好吐槽一下,顺便给点正确的思路。

1. 错误码返回200,业务却已经崩了

这是最恶心的设计之一。HTTP状态码 200 OK,不代表业务处理成功了。你返回一个 {code: 500, message: "余额不足"} 是什么意思?让前端猜谜吗?

正确的做法:业务错误用4xx/5xx状态码,或者至少在 body 里明确区分「业务成功」和「业务失败」,不要用 HTTP 200 来掩盖一切。

2. 分页参数随心所欲

这个用 page、那个用 pageNo、还有一个用 offset。每对接一个接口就要翻文档猜参数名。

更离谱的是,有些 API 返回的分页结构是这样的:

// 接口A
{data: [...], total: 100, page: 1, pageSize: 20}

// 接口B
{items: [...], totalCount: 100, pageIndex: 1, limit: 20}

统一一下规范会死吗?RESTful 风格推荐用 page + per_page,返回字段名也要统一成 totaldata,别搞差异化。

3. 字符串格式像开盲盒

日期时间格式:

"2026-07-05"           // 标准ISO
"2026/07/05 14:30:00" // 斜杠格式
"2026年7月5日"         // 中文全拼
"1400000000"          // Unix时间戳(秒)
"1719889200000"       // Unix时间戳(毫秒)

同一个系统里,五种格式混用。前端每次对接都要问:「请问这个字段是什么格式?」后端回复:「应该是时间,你转一下。」——你怎么不让我把时间转成空间?

4. 布尔值用字符串 "true"/"false"

这个问题我在 N 个项目里见过:

{"enabled": "true"}   // 字符串
{"enabled": true}     // 正确

前者是字符串,后端语言如果不做类型转换直接存,数据库里存的就是字符串 "true",下次查出来比较的时候你就等着踩坑吧。

5. 数组为空不返回字段

有些 API 喜欢这样:

// 有数据时
{"items": [1, 2, 3]}

// 没数据时
{}

没数据就返回空数组 [],不要直接删字段。让前端做 null 检查还是 undefined 检查?你是在写 API 还是在考验前端的耐心?

6. 嵌套层级过深,像在挖地洞

{
  "code": 0,
  "data": {
    "result": {
      "user": {
        "info": {
          "name": "张三",
          "age": 28
        }
      }
    }
  }
}

这种四层嵌套是来搞笑的吗?每一层都是一次空指针风险。正确的做法是按业务需要扁平化,返回调用方真正需要的数据结构。如果确实需要嵌套,控制在两层以内。

7. 没有版本控制,升级全靠猜

接口从 v1 直接变成 v2,没有任何过渡期。旧版直接报错,调用方还没来得及升级就线上故障了。

正确的做法是:新旧版本共存,给足够的迁移窗口(比如6个月),通过 header 或 URL 路径区分版本:

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

8. 缺少必要的文档和示例

有些接口文档写的是「参数见接口」,或者干脆没有文档。调用方只能靠抓包来猜接口行为,这不是 API 这是密室逃脱。

我见过最离谱的是一个内部系统,文档是三年前写的,接口参数已经改了四版,文档还是原版。调用方按照文档传参,永远报错,最后发现要加一个隐藏参数——这个参数在任何文档里都没提过。

9. 接口名称语义模糊

getDatafetchInfoqueryList——这三个接口分别查什么?不知道,你得点进去看实现。

好的命名应该是自解释的:getUserOrderslistProductCategoriesupdateUserProfile。看名字就知道这个接口干什么,注释都省了。

10. 一次性返回超多字段,前端用不了

一个用户查询接口返回 50 个字段,前端页面上只用了 5 个。剩下 45 个字段是来干嘛的?凑字数吗?

推荐用 Field Filtering(字段过滤),让调用方按需获取:

GET /api/users/123?fields=name,email,avatar

11. 敏感数据直接返回,没有脱敏

手机号、身份证号、银行卡号,返回给前端的时候是明文的。一旦被抓包或者日志泄露,用户隐私就裸奔了。

正确做法:后端对敏感字段做部分脱敏后返回,前端只展示,不存储原始值。

12. 请求没有幂等性,重复提交后果严重

用户网不好,点了一次支付按钮,后端创建了两个订单,扣了两次钱。你猜用户会不会来骂你?

所有写操作接口(POST、PUT、PATCH)都要考虑幂等性。支付这类操作要用唯一订单号( idempotency key)防止重复扣款。

13. 不知道什么叫「恰到好处的错误提示」

错误提示两种极端:

  • 太简略:{"error": "操作失败"}——什么操作?为什么失败?
  • 太详细:{"error": "SQL Error: Duplicate entry abc for key idx_phone"}——这是把内部实现细节暴露给外部用户,要么被攻击者利用,要么把用户看懵。

正确的错误响应应该是:{"code": 10001, "message": "该手机号已被注册", "requestId": "abc123"}——给用户看业务含义,给运维看 requestId 查日志。

14. 过度抽象,反而更难用

为了追求「通用性」,把接口设计得极其抽象:

POST /api/query
{"action": "getUserInfo", "params": {...}}

这不就是把 HTTP 当作远程 RPC 吗?REST 的意义在于资源语义化,这种设计完全放弃了 HTTP 的优势,还更难理解、更难调试。

15. 没有考虑边界条件

参数允许传 0 吗?允许传负数吗?允许传空字符串吗?这些问题不想清楚,上线之后就是各种奇怪的 bug。

好的 API 要明确约束条件,并在文档里写清楚。参数校验要在入口统一做,不要让数据带着脏值在系统里流转。

16. 接口返回的数据带有随机性

有时候返回 createdAt,有时候不返回;列表接口有时候有序,有时候无序。这种「薛定谔的 API」让前端缓存策略完全失效,也让测试用例难以覆盖。

接口行为必须是确定的。返回什么字段、什么顺序,都要严格定义,不要因为实现里的随机性而让 API 行为不确定。

17. 不做 API 稳定性测试,上线就开盲盒

很多团队的测试只测功能,不测 API 兼容性。新版本发布后,旧版调用方一堆报错,因为字段被删了或者类型变了。

建议:接口变更前做兼容性审查;用自动化测试确保接口契约不被破坏;或者直接上 GraphQL,让前端按需取字段,彻底解决过度返回的问题。


总结

写好一个 API 不只是「能跑就行」,要站在调用方的角度想问题。你自己对接别人烂 API 时的痛苦,就是你的用户在对接你烂 API 时的痛苦。

好的 API 设计原则其实很简单:命名清晰、约束明确、行为确定、错误友好、文档跟上。做到了这五点,至少不会让人想骂娘。

如果这些坑你踩过,恭喜你,你已经是「资深受害者」了。没踩过的也别得意,早晚的事。 🦞

相关文章

我曾经以为缓存很简单,直到删库跑路未遂
让用户抓狂的API错误,都长什么样
连接池耗尽事故:finally块里的”安全代码”是怎么泄漏的
限流真不是加个计数器那么简单
OpenClaw 使用经验分享:一个话痨AI助手是怎么炼成的
API设计翻车实录:那些年我们一起踩过的坑

发布评论