写了5年代码,我发现API设计才是程序员的分水岭

2026-05-16 15 0






写了5年代码,我发现API设计才是程序员的分水岭

写了5年代码,我发现API设计才是程序员的分水岭

干这行这么多年,我见过太多「能用就行」的API。有些系统代码写得稀烂,但API设计得优雅,后期维护起来像散步;有些同学代码写得飞起,API却一塌糊涂,对接方骂骂咧咧,后面接手的人恨不得顺着网线过来打人。

API设计这事,说大不大,说小不小。但你一旦踩过那些坑,就会明白:一个烂API可以让一个团队痛苦三年。今天咱们就聊聊那些最常见的API设计错误,以及怎么避坑。

一、URL里塞动词,REST看了会流泪

先问个问题:下面这两个API,哪个更「REST」?

GET /getUser?id=10086
GET /users/10086

对,第二个。这就是REST的核心思想之一:用名词,不用动词。资源是名词,动作交给HTTP方法。

但我见过更离谱的:

POST /deleteUser
POST /updateUserInfo
GET /queryAllOrders

哥们,POST是POST,DELETE是DELETE,你搁这套娃呢?这种设计不叫REST,叫「假装REST」。时间长了,自己都忘了哪个接口是干啥的。

正确姿势:

DELETE /users/10086       # 删除用户
PATCH  /users/10086        # 部分更新用户
GET    /users/10086        # 获取用户
GET    /orders?user_id=10086  # 查某用户的订单

PATCH很多人不用,但它就是给「只改用户头像」这种场景准备的。记住,PUT是全量替换,PATCH是部分更新,别用反了。

二、状态码乱用,前端同学半夜报警

HTTP状态码是API的语言。你跟前端说「200」,他就知道「成了」;你说「500」,他就知道「服务器炸了,你等着」。但如果你乱用状态码,前端会陷入无尽的困惑。

最常见的问题:所有错误都返回200,然后在body里塞个code: 500

// 这个API实际上出错了,但前端看到200以为一切正常
HTTP/1.1 200 OK
{"code": 500, "message": "数据库连接失败"}

拜托,HTTP状态码就是用来干这个的!你这样搞,前端监控报警都配置不好,因为他看到200就认为没问题。

标准状态码清单(按场景用):

  • 200 — 成功(GET、PATCH、PUT成功)
  • 201 — 资源创建成功(POST成功)
  • 204 — 成功但无返回内容(DELETE成功)
  • 400 — 前端传参有问题,比如格式不对、缺字段
  • 401 — 未认证,没登录或Token过期
  • 403 — 已认证但没权限
  • 404 — 资源不存在
  • 409 — 冲突,比如重复创建
  • 422 — 参数校验失败
  • 500 — 服务器自己出问题了

别偷懒用200+code的方式,状态码是给整个HTTP生态看的,CDN、网关、监控都靠它判断。

三、分页随心所欲,每个接口都不一样

有的接口这样分页:

GET /users?page=1&page_size=20

另一个接口这样:

GET /orders?offset=0&limit=20

还有一个这样:

GET /products?start=0&count=20

前端同学:???你们后端是不是有KPI,接口数量越多越好?

统一分页方式,不仅是风格问题,更是契约。前端封装SDK、接口文档、联调测试,全都依赖这套约定。

推荐方案,Cursor分页(游标分页)vs 偏移分页都要支持:

# 偏移分页,适合数据量可控的场景
GET /orders?page=2&page_size=20

# 响应
{
  "data": [...],
  "pagination": {
    "page": 2,
    "page_size": 20,
    "total": 300,
    "total_pages": 15
  }
}

如果数据量大、增长快,用游标分页避免深分页性能问题:

GET /orders?cursor=eyJpZCI6MTAwMH0&limit=20
# 返回
{
  "data": [...],
  "next_cursor": "eyJpZCI6MTAyMH0",
  "has_more": true
}

四、错误信息等于没信息

最让人崩溃的API错误是这样:

{"error": "操作失败"}

操作失败?什么操作?为啥失败?是我的问题还是服务器的问题?前端同学看着这个错误,只能给你回一句「接口报错了」。

好的错误响应应该长这样:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      },
      {
        "field": "age",
        "message": "年龄必须在18-120之间"
      }
    ],
    "request_id": "req_abc123xyz"  
  }
}

code是给程序看的,方便前端做分支处理;message是给用户看的,可以直接展示也可以做国际化;details列出具体哪个字段出了什么问题;request_id是排查问题的钥匙,出了事让用户报这个ID,一查就知道是哪次请求。

很多团队不屑于做这些,觉得麻烦。但你想想,每次联调省下的十分钟,最后都会在生产环境以小时为单位还回来。

五、版本管理形同虚设

「没事,我们接口稳定,不用做版本管理。」

说这话的团队,一般在三个月后会有一堆/api/v1/api/v2/api/v3/api/v4在代码库里并存,然后没人知道哪些接口在用、哪些已经废弃了。

API版本不是过度设计,是给自己留后路。当你要改一个字段名、加一个必填参数、改一个返回结构时,没有版本隔离,你就是在谋杀下游。

GET /api/v1/users/10086
GET /api/v2/users/10086  # v2改了返回结构,加了 avatar 字段

版本放URL里是最直观的方式,也可以放Header里(Accept: application/vnd.api+json; version=2),但URL方式更容易做网关路由和文档,也方便前端调试。

记得,发布新版本时,老版本至少再维护6个月再废弃。别学某些大厂,说停就停,恨不得让全球开发者第二天就跟着改。

六、不做幂等性设计,交付那天就是噩梦

什么是幂等?就是你调用一次和调用一百次,效果一样。比如删除操作,删第一次删掉了,删第二次还是删掉了(没东西可删,但也没报错),这叫幂等。但如果是这样的接口:

POST /orders/10086/pay   # 扣款接口
POST /refund             # 退款接口

这两个操作如果没做幂等控制,用户网络抖动点了两下,钱就扣了两次,或者退了两笔。这种问题在支付场景是致命的。

解决方案:用幂等Key。客户端生成一个全局唯一ID,放进Header,服务器根据这个ID做去重:

POST /orders/10086/pay
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

# 服务器先查这个key是否处理过:
# - 没处理过:执行业务逻辑,返回结果
# - 处理过:直接返回上次的结果,不重复执行

金融类的接口,幂等是基本素养,不是可选项。

写在最后

API设计不是什么高大上的架构设计,它就是天天要打的交道。但正因为天天见,那些细节才重要。一个好的API,用起来像呼吸一样自然;一个烂API,用起来像便秘。

代码可以重构,API一旦对外发布,动起来就难了。所以在一开始就想清楚,花20%的时间设计,省下往后200%的时间填坑。

如果你现在手头有个「能用就行」的API,建议抽个空 review 一下。不用一次性改完,先从最影响使用的地方开始。慢慢来,别指望一口吃成胖子。

毕竟,程序员最远的路,就是「下次一定重构」。


相关文章

为什么同样配置的服务器,别人能跑你却炸了?一位被连接池坑了三年的工程师的血泪复盘
从“这接口谁写的”到“真香”:RESTful API设计实战复盘
AI探索|当AI开始卷起来,我们都成了吃瓜群众
AI工具部署太麻烦?小龙虾帮你一键搞定,省心又省力!
AI工具部署太麻烦?小龙虾帮你一键搞定,省心又省力!
别人用Redis只會cache,我用Redis做了六件你絕對想不到的事

发布评论