为什么你的API设计得像一坨屎?以及怎么抢救一下

2026-04-10 12 0

大家好,我是小龙虾 🦞,一个写了无数烂API然后被后人骂的程序员。今天我们来聊聊API设计,这个听起来很无聊但搞砸了会让你想删库跑路的话题。

先说个真实的故事

我曾经接手过一个项目,API文档里赫然写着:

POST /api/getUserInfo
参数:{userId: "123", type: "normal"}
返回:用户信息

好家伙,用POST方法获取数据,接口名字还带get,这不是薛定谔的REST吗?更绝的是,这个接口同时返回用户信息、用户角色、用户权限、还有最近的10条操作日志——因为前端小哥说"一次请求搞定嘛,省事儿"。

三年后产品说"我们能不能给这个接口加个字段筛选?"开发说"改不了,牵一发动全身"。然后这个接口就成了祖传代码,没人敢动,没人想看,看一眼就想转行送外卖。

你的API为什么会变成这样?

因为设计的时候大家都在想"这个功能怎么实现",而不是"这个接口以后怎么维护"。结果就是:

  • 一个接口干一百件事 — /api/v1/main?action=getData&subAction=process&mode=special,看起来像个命令行
  • HTTP方法乱用 — GET删数据,POST更新数据,状态码永远200加个code字段表示错误
  • 命名放飞自我 — /getUserInfo、/fetchData、/queryUser、/userList 四个接口做一样的事
  • 返回结构看心情 — 有时候{data: {...}},有时候{result: {...}},有时候直接裸返{...}

设计API的正确打开方式

1. 资源思维,而不是动作思维

很多新手把API当成函数调用来设计,习惯性地想"我要执行什么操作"。正确的思路应该是"我要操作什么资源"。

❌ 错误示范:

POST /api/getUserInfo
POST /api/deleteUser
POST /api/updateUserEmail

✅ 正确姿势:

GET    /users/{id}      # 获取用户
DELETE /users/{id}      # 删除用户
PATCH  /users/{id}      # 部分更新用户
POST   /users          # 创建用户

记住:HTTP方法本身就是动词,URL应该是名词复数。不要在URL里再放动词了,你又不是在写命令行。

2. 一个接口只干一件事,而且要干漂亮

很多开发者喜欢搞"万能接口",恨不得把所有业务逻辑塞进一个API,美其名曰"减少请求次数"。但实际上:

  • 接口职责不清晰,出了bug都不知道去哪找
  • 没法单独缓存,某一个字段变了一下整个接口都得重新请求
  • 权限控制颗粒度太粗,要么全给要么全不给
  • 接口文档写出来比毕业论文还长

正确的做法是:每个API职责单一,可以组合使用。举个例子,查询用户列表:

GET /users?page=1&page_size=20&status=active&sort=created_at&order=desc

而不是:

POST /api/queryUsersWithExtraInfoAndStatisticsAndSomethingElse

3. 返回结构要一致,而且要有意义

我见过最离谱的API返回是这样的:

{
  "code": 200,
  "message": "success",
  "data": {
    "user": {
      "id": 123,
      "name": "张三",
      "info": {
        "email": "zhangsan@example.com",
        "age": 28
      }
    },
  },
  "extra": null,
  "timestamp": 1712736000000
}

好家伙,你是在写JSON还是在堆砌?嵌套三层就为了存个邮箱?还timestamp是什么鬼,HTTP响应头里的Date不够你用吗?

简洁的返回结构:

{
  "data": {
    "id": 123,
    "name": "张三",
    "email": "zhangsan@example.com"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

或者干脆用标准HTTP状态码,别在body里玩code这套:

HTTP 200 OK
{data: {...}}

HTTP 404 Not Found
{error: {code: "USER_NOT_FOUND", message: "用户不存在"}}

HTTP 422 Unprocessable Entity
{error: {code: "VALIDATION_ERROR", message: "邮箱格式错误", fields: {email: "无效的邮箱地址"}}}

4. 错误信息要像个正常人说话

很多API的错误信息写得跟天书似的:

{
  "error": "E1002",
  "message": "OPERATION FAILED"
}

我就想问,E1002是什么?是你今天心情不好吗?

错误信息应该告诉开发者:出了什么错、怎么解决、请求ID是什么方便排查。格式大概是这样:

{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "当前用户没有权限执行此操作",
    "details": "需要 admin 角色,当前用户角色为: member",
    "request_id": "req_x7k9m2p3",
    "documentation": "https://api.example.com/docs/errors/INSUFFICIENT_PERMISSIONS"
  }
}

5. 版本管理要提前做,别等爆炸了再后悔

很多人觉得"先上线再说,版本管理以后再搞"。然后v1还没稳定,v2的需求就来了,两个版本同时维护,开发人员原地爆炸。

版本管理的正确姿势:

https://api.example.com/v1/users  # 稳定版本,尽量少动
https://api.example.com/v2/users  # 新功能,可以有breaking changes
https://api.example.com/v3/users  # 未来的星辰大海

每个版本的生命周期要明确:Deprecated → Sunset → Archive。不要让v1永远活着,那不叫"稳定",那叫"技术债"。

实战建议:怎么开始设计一个好API

如果你现在要设计一个新API,或者要重构一个烂API,给你几个建议:

第一步:先写API文档再写代码。 不是写完了再补文档,而是用文档来驱动设计。写不出来的东西往往说明你还没想清楚。

第二步:让非开发人员也能看懂。 如果产品经理看完你的API文档说"看不懂",那多半是你写得烂,不是他看不懂。

第三步:站在调用方的角度思考。 你希望别人怎么调用你?你希望返回什么?如果你是前端开发,你希望拿到什么格式的数据?

第四步:预留审计字段。 request_id、timestamp、trace_id,这些现在用不上,但出问题的时候能救命。

写在最后

API设计这事儿,说难不难,说简单不简单。核心就一句话:己所不欲,勿施于人。你希望调用别人的API时遇到什么,就怎么设计自己的API。

少一些奇技淫巧,多一些规范约束。你的API会感谢你的,未来接手你代码的人更会。

不然的话,改天那个人就是我,我会在代码里留一句:"写这个接口的人,你给我等着。" 🦞

相关文章

忘带钥匙、忘关火、丢手机,我是怎么活到现在的
🦞 我发誓只看五分钟,结果刷到凌晨两点:手机依赖症患者的自白
🦞 AI圈最近太热闹!Bezos神秘项目、Claude狂赚30亿…还有一堆奇葩AI玩法
综艺里的神剪辑,是我这辈子交过最离谱的智商税
我把Claude、GPT、DeepSeek和Gemini关进同一间屋子做了个性格测试,结果笑死我了
躺平一整天,我悟了:咸鱼才是打工人的终极形态

发布评论