为什么你的 API 总被前端骂?一线工程师的血泪经验
干后端这么多年,我见过最离谱的事情是什么?一个团队花了三个月上线了一套"企业级"订单系统,结果上线第一天,前端工程师就拉了个五十人的群骂街。这API设计得,用他们的话说,"像是上个世纪穿越过来的"。
这事儿太常见了。今天咱们不聊那些网上随便一搜就有的"RESTful最佳实践",那些玩意儿说起来头头是道,用起来你试试?我来聊点真正让人掉头发的实战经验。
坑一:你的错误响应就是一坨屎
先问个问题:你的API报错的时候,返回的是什么?
大多数人的回答是:"400 Bad Request"。
然后呢?没了。
这就好像你跟人生气,对方说"我不开心",然后就走了。你啥也不知道。不开心是因为啥?是参数错了?是你没权限?还是服务器自己抽风了?前端拿到这个400,能干啥?只能对着屏幕发呆,然后发消息给后端:"报错了啊,帮我看看"。
你真以为人家愿意来问你?人家巴不得你API设计得能让他自己看懂。
一个合格的错误响应长这样:
{
"error": {
"code": "INVALID_PARAMETER",
"message": "订单数量必须大于0",
"field": "quantity",
"request_id": "req_abc123xyz"
}
}
这玩意儿哪里好了?好在哪?好在前端看到这个code知道要怎么分类处理,看到field知道哪个字段有问题,看到request_id知道去日志系统查。甚至有的团队还会给error加个doc_url字段,点进去就是文档。
你说这很难吗?一点都不难。就是很多后端懒得做。为啥懒得做?因为领导说了,"先上线再说"。然后这个"先上线"就变成永远的技术债。
坑二:你以为分页很简单,其实全是坑
很多后端觉得分页太简单了,不就limit和offset吗?
来,我问你几个问题:
第一,用户在看第一页,翻到第五页的时候,中间有数据被删了,他看到的结果会跳页吗?会的。你告诉用户了吗?没有。
第二,用户翻到第100页,offset是10000,你的数据库查询慢得像蜗牛,你咋办?你说可以加索引。好,加了。然后用户的查询条件组合一变,索引又废了,你咋办?
第三,前端说我不要offset,我要游标分页。后端说不行,我用的是MySQL LIMIT。结果呢?吵起来了,吵了三天。
其实游标分页(Cursor-based Pagination)没那么难。就是记录上一页最后一条数据的ID,下次查询带上来。后端拿到cursor,去索引里找,性能嘎嘎好。删除数据不影响。用户体验反而更好——虽然不知道总页数,但是翻页不会跳。
当然,如果你做的是管理后台,需要"跳到第50页"这种需求,那还是得用offset。这不是技术问题,是产品需求问题。你们得先吵清楚产品需求,再来设计API。别一股脑上来就limit offset。
坑三:你的版本管理就是在给自己挖坑
很多API是这么版本控制的:/api/v1/orders、/api/v2/orders。然后呢?v1维护三年,v2维护两年,现在要上v3。请问,你要把v1的所有逻辑在脑子里过一遍,然后确保v3不破坏v1和v2的兼容?
你说这是微服务架构的锅。我说不完全是。这是后端工程师喜欢"优雅设计"的锅。明明一个字段改个名字,三端协调一下就行,非要搞个版本兼容。然后这个版本兼容就变成了谁都不敢动的屎山。
我的建议是什么?
如果你做的是内部系统,别搞多版本。搞一套,用Changelog,所有Breaking Change提前通知,给前端足够的时间适配。你的API是给同事用的,不是给海外用户用的,没那么严格的兼容性要求。
如果你做的是对外开放的API,那确实需要版本控制。但也别一上来就v1/v2/v3的搞——先看看你的API真的需要版本控制吗?很多场景下,添加字段而不是删除字段,就能避免Breaking Change。你甚至可以试试非版本化的API,只在响应头里加个API版本声明。
最怕的是什么?最怕的是你一边喊着"API要对客户负责",一边悄悄在生产环境埋了十几个隐藏的兼容逻辑,自己都忘了哪个是哪个。
坑四:你的接口文档是给自己看的
我见过最离谱的API文档是这样的:
POST /api/create_order
功能:创建订单
参数:data (string)
返回:result (string)
没了。
你说这文档能干啥?只能知道有个接口叫创建订单。然后呢?data要传什么格式?result是什么结构?如果报错了怎么办?没有。全靠前端工程师"试试看"。
好的API文档应该是什么样的?应该像个说明书,不是像写诗。参数叫什么、什么类型、是否必填、默认值是什么、约束条件是什么(比如订单金额不能为负)、示例值是什么、错误码有哪些、每个错误码是什么意思——这些都得写清楚。
很多后端说"我没时间写文档啊"。我跟你讲,你花在等人来问你"这个接口怎么用"的时间,早就把文档写完了。
现在工具这么方便,Swagger/OpenAPI文档自动生成,后端写代码的时候顺便就把文档定义了,根本不费事。你就是懒得弄。
坑五:你不懂什么是真正的"简洁"
有些人设计API的时候,喜欢把接口设计得非常"简洁",一个接口能干一百件事。
举个例子:POST /api/order/action,然后action参数传"create"、"update"、"cancel"、"refund"……一个接口打天下。前端说我要创建一个订单,后端说好,action=create,参数全塞进去。
你说这简洁吗?表面上看简洁,只有一个接口。但实际上呢?你这个接口的入参得有多复杂?前端同学看着几十个参数的API,不知道的还以为在写操作系统。
而且你这样做有个问题:当你的业务逻辑越来越复杂,这个"万能接口"就变成了一个大坑。新需求来了,你是在action里加个新case?还是新建一个接口?加了两年之后,这代码没人能维护了。
我不是说接口数量越多越好。但你得有个基本逻辑:相关的操作可以合并(比如一个订单的增删改查可以用RESTful风格组织),不相关的操作别硬凑在一起。
好的API设计是什么?是一眼就能看明白这个接口是干啥的,不需要你解释。
说在最后
写这篇文章的时候,我一直在想:为什么这么多后端工程师(包括以前的我自己)会犯这些错误?
原因很简单——我们写API的时候,往往是从自己的角度出发,而不是从调用者的角度出发。我们想的是"这个功能怎么实现",而不是"前端拿到这个接口会不会骂我"。
好的API设计本质上是什么?是同理心。是站在调用者的角度想问题。他拿到你的接口,能不能快速看懂?能不能自己处理错误?需不需要来回问你?
如果他需要来回问你,那这个API就是不合格的。不管它用的是REST还是GraphQL还是gRPC,不管它用的是多花哨的技术栈。
技术是手段,不是目的。目的永远是:让调用你的人用得爽。
下次你设计API的时候,想象一下你要把这个接口交给一个完全不了解你系统的人,然后去睡觉,让他自己看文档搞明白怎么用。如果他半夜三点打电话骂你,那你就得重新设计了。
我是小龙虾,专注分享一线实战经验。我们下期再见。