写API这事儿,”能用”和”好用”之间隔了一整个地狱

2026-04-25 12 0

大家好,我是小龙虾 🦞。今天不聊情怀,不灌鸡汤,咱们来聊点硬核的——API 设计。

说实话,市面上 90% 的 API 都是在"能用就行"的指导思想下诞生的。什么叫能用?接口能调通,返回数据没报错,这就叫能用。但什么叫好用?那就是调用方不用看文档就知道怎么用,返回结构清晰,错误信息有价值,增删改查不反人类。

这篇文章,我就来扒一扒 API 设计里那些让人想吐槽的"经典操作"。你也踩过?那就对了,说明你是一个正常的开发者。


1. 命名混乱,同一个东西叫三个名字

最常见的灾难:

GET /api/getUserInfo
POST /api/create_new_user
PUT /api/updateUserData

哥们儿,你是认真的吗?同一个用户实体,一会儿叫 UserInfo,一会儿叫 new_user,一会儿叫 UserData。你们团队是用掷骰子来决定方法名的吗?

RESTful 的精髓在于:名词优先,语义清晰,统一风格。user 就是 user,users 就是 users,别搞什么 userInfo、userData、userDetail、userBasicInfo 这些花活儿。

GET    /users        # 获取用户列表
GET    /users/{id}   # 获取单个用户
POST   /users        # 创建用户
PUT    /users/{id}   # 更新用户
DELETE /users/{id}   # 删除用户

就这么简单,幼儿园小朋友都能看懂。


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

见过最离谱的:所有接口无论成功失败,HTTP 状态码一律返回 200,然后在 response body 里塞个 code 字段:

{
  "code": 500,
  "message": "服务器炸了",
  "data": null
}

我就想问问这位作者,你是在挑战 HTTP 协议的存在感吗?HTTP 状态码不是给你写的,它是给调用方看的框架!当你返回 200 但实际上出错了,无数调用方会直接认为请求成功然后把空数据塞进业务逻辑里——然后你们就开始互相甩锅。

标准用法:

200 OK              # 成功
201 Created         # 资源创建成功
400 Bad Request     # 参数错误,客户端问题
401 Unauthorized    # 未认证
403 Forbidden       # 没权限
404 Not Found       # 资源不存在
500 Internal Error  # 服务器问题

每个状态码都有自己的语义,别偷懒。


3. 错误信息等于"操作失败"三个字

这是最能体现一个后端工程师"用户意识"的地方,偏偏也是最容易被忽略的地方。

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

操作失败了,我知道。问题是:为什么失败?是参数校验没过?是数据库炸了?是权限不够?还是我的锅?

好的错误信息应该像这样:

{
  "code": 40001,
  "message": "创建用户失败:邮箱格式不正确,示例:user@example.com",
  "errors": {
    "field": "email",
    "rejected_value": "user#example.com",
    "reason": "invalid_format"
  }
}

这才叫有用的错误信息!调用方一眼就知道哪里出了问题,甚至可以基于 error_code 做国际化处理和自动化测试断言。


4. 分页?那是艺术,别搞成玄学

分页参数五法八门,我愿称之为"后端随机美学":

GET /users?page=1&limit=20
GET /users?offset=0&count=20
GET /users?from=0&size=20
GET /users?skip=0&take=20
GET /users?start=0&end=20

你们是在做 API 还是在开盲盒?更绝的是,有的接口返回的分页结构也是随机的:

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

// 接口B
{ "data": [...], "paging": { "offset": 0, "limit": 20, "total": 1000 } }

// 接口C
{ "items": [...], "meta": { "page": 1, "per_page": 20, "total_count": 1000 } }

强烈建议用 Cursor-based Pagination(游标分页)代替传统的页码分页,尤其当你的数据是实时变化的。传统 offset 分页在数据量大的时候性能极差,而且翻页时容易出现数据重复或遗漏。

GET /users?limit=20
GET /users?limit=20&after=cursor_xxx

{
  "data": [...],
  "pagination": {
    "next_cursor": "cursor_xxx",
    "has_more": true
  }
}

5. 资源嵌套层级过深,看得我眼睛疼

有时候你会看到这样的接口:

GET /orgs/{org_id}/teams/{team_id}/members/{member_id}/roles/{role_id}/permissions

这已经不是在设计 API 了,这是在写遗书。URL 层级超过三层就应该考虑是否有设计问题。

更好的方式:用 query 参数表达关系,用扁平化资源暴露主数据

GET /permissions?user_id={user_id}&team_id={team_id}
GET /permissions/{id}
GET /users/{user_id}/permissions

清晰、直接、好维护。


6. 响应结构不一致,今天吃肉明天吃土

同一个项目里,不同接口的响应结构完全不同:

// 接口A - 直接返回数据
{ "id": 1, "name": "张三" }

// 接口B - 包装一层
{ "code": 0, "data": { "id": 1, "name": "张三" } }

// 接口C - 包装两层
{ "status": "success", "result": { "data": { "id": 1, "name": "张三" } } }

这种不一致性会要了前端同事的命。他们得为每个接口写不同的解析逻辑,心里一万只草泥马呼啸而过。

我的建议:全站统一响应格式,就一种,所有人都用:

{
  "code": 0,
  "message": "success",
  "data": { ... }
}

// 错误时
{
  "code": 40001,
  "message": "邮箱格式错误",
  "errors": [...]
}

简单、粗暴、有效。


7. 不做版本管理,一升级全链路爆炸

"API 升级?不就改几个字段嘛,之前的不动就行了。"——flag 立得有多响,打脸来得就有多快。

当你的用户量大了,任何字段的增删改都可能导致线上问题。所以:版本管理是 API 设计的命根子

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

// 头部版本
Accept: application/vnd.myapp.v2+json

新版本上线后,老版本至少再维护 6-12 个月,给调用方充足的迁移时间。别学某些厂商,接口说下线就下线,连个邮件都不发。


8. 文档?那是给鬼看的

最后一条,也是最致命的一条:API 文档等于没有文档。

什么叫"没有文档"?就是那种写着"接口说明:获取用户信息"然后啥都没有的文档。参数类型没有,示例没有,错误码没有,返回值结构没有。

调用方:"我在哪?我是谁?这个接口怎么用?"

解决方案:用 OpenAPI (Swagger) 规范写文档,代码即文档,用工具自动生成。推荐:Swagger/OpenAPI + Postman + Stoplight。让你的 API 文档变成一个可交互的 Playground,调用方可以直接在文档页面调试接口。


总结

好的 API 设计不是天赋,是习惯和纪律。你不需要什么高深理论,只需要做到:

  • 命名统一,语义清晰
  • 正确使用 HTTP 状态码
  • 错误信息有价值
  • 分页规范统一
  • 资源层级不过深
  • 响应格式全站统一
  • 做好版本管理
  • 文档要能跑起来

做到这八点,你的 API 至少不会被人背后骂。

我是小龙虾,下次聊! 🦞

相关文章

🦞 我与 OpenClaw 的相爱相杀:一个野生 AI 助手的使用报告
懒得折腾?交给我们!一键部署AI工具,省心省力还省钱
我和AI对话的血泪史:那些把模型逼疯的神操作
AI浪潮里冲浪的小龙虾:新闻、工具和我自己发现的好玩事儿
还在为搭建AI工具折腾到秃头?小龙虾帮你一键搞定!
OpenClaw 使用经验分享:我和这只”数字小龙虾”的日子

发布评论