让 AI 生成接口设计,错误码分页幂等这三处默认答案为什么不能直接用

2026-07-29

数据截至 2026-07,各产品的额度与报错口径以官方最新说明为准。

接口被 AI 写崩,绝大多数不是它代码写错了,而是你把一类它无从判断的决策也交了出去。 错误码要不要让调用方重试、分页在数据边翻边变时保不保证不重不漏、幂等键作用于哪个范围保留多久——这三件事的答案取决于你的业务约束和调用方行为,模型手上没有这些事实,它只能给一份看起来最常见的模板。模板能跑通联调,撑不住线上。把这个归因成”模型能力不行”或者”换个更强的模型试试”,方向就偏了:该补的是你在需求描述里没交代的约束。

站内已有两篇相邻文章,分工先说清楚:分页数据重复和漏掉 讲的是分页已经在线上出重复、出漏项之后怎么定位和修;重试之后同一件事做了两遍 讲的是重复写入发生之后幂等键怎么补。本篇站在它们之前——接口还在设计阶段,怎么在把活交给 AI 的时候就把这两类坑堵住,以及哪些地方不能交。

一、这个环节 AI 能帮到哪一步,哪一步必须你定

先划一条清楚的线,后面所有动作都是从这条线派生的。

AI 在接口设计上真正擅长的是结构性劳动:把你口述的资源关系翻成一份完整的路径与字段清单、把命名和时间格式在几十个接口里统一、照着你已有的老接口把风格复制到新接口、补齐请求响应样例和文档、生成客户端调用骨架和参数校验代码。这部分它做得比人稳,因为人写到第二十个接口就开始偷懒不写 nullable,它不会。让它做这些,你的收益是实打实的。

它做不了的是取决于外部事实的判断,具体有三类:

一是调用方会怎么错用。哪些客户端会无脑重试、哪个上游有自己的超时时间、哪个团队的重试逻辑在错误分支里而不是在传输层。这些不写进需求描述,模型不可能知道。

二是数据会怎么变。你的列表是只增不改的日志流,还是会被后台任务批量改状态的工单表,这直接决定分页方案。模型看不到你的写入模式。

三是出错之后谁来兜底。同一个”参数不合法”,在内部服务之间可以直接拒绝,在开放接口上可能需要给出可修复的字段级提示。这是产品决策不是技术决策。

所以正确的分工是:你给约束,它给结构。你把”这张表每天有批量状态更新""调用方是三个不受我们控制的外部系统""这个写操作会扣款”这类事实先写清楚,再让它出方案;它给完之后你只审三处——错误码语义、分页一致性、幂等作用域。这三处审完基本就没有大坑了。

下面这张表用来在评审或联调时快速判死,看到左边的现象,直接走右边的动作:

现象大概率成因怎么验证处置动作
返回体里只有一个 message 字符串,没有稳定机器码AI 按最常见模板生成,没被告知调用方需要分支处理让调用方试着只根据返回体写一个分支判断,看是不是必须匹配中文文案补一套只增不改的机器码,文案与码分离
连鉴权失败、参数非法、限流都返回 HTTP 200,全靠体内字段区分需求里没说清传输层与业务层的分工curl -o /dev/null -w '%{http_code}\n' 打一遍失败用例,全是 200 即命中明确哪些进状态码、哪些进体内码,写进约定
参数错、鉴权过期、限流全落到同一个错误分支码表粒度不够,调用方无法区分该改参数还是该等检查码表里有没有”可重试”这一维度给每个码标注可重试性与是否需要人工介入
分页用 pagesize,数据表有并发写入默认模板就是偏移分页翻第一页后往表头插几条,再翻第二页看有没有重复换游标分页,或至少给排序加唯一打破平局的键
排序字段是创建时间且允许重复值排序不稳定,同值记录在两次查询里顺序可能不同造十几条 created_at 完全相同的记录,同一页反复查若干次,比对每次返回的 id 序列是否一致排序键改成”时间 + 唯一 id”的复合形式
写接口有幂等参数但没说作用域和保留期AI 加了参数,没有被要求定义语义拿同一个键换个请求体再调一次,看返回什么定死作用域、保留时长、参数冲突时的返回
同一个幂等键并发打两次,两次都建了资源幂等靠”先查后写”实现,没有唯一约束兜底并发发两个相同请求落到数据库唯一索引上,靠冲突而不是靠查询

二、错误码:把「出了什么错」和「你该怎么办」拆开

AI 给的默认错误设计通常长这样:一个 HTTP 状态码,一个人话 message,讲究一点的加一个像 USER_NOT_FOUND 这样的字符串。看着挺齐全,问题在于它只回答了”出了什么错”,没回答调用方最需要知道的那件事——我现在该干什么

一个能用的错误码至少要让调用方分辨三件事:这个错重试有没有意义、重试安不安全、要不要停下来叫人。这三件事对应的动作完全不同:等一会再来、换个参数再来、直接失败上报。少了这层信息,调用方只能猜,猜的结果就是要么该重试的没重试,要么不该重试的疯狂重试——后者就直接引出了幂等问题。

我的做法是三段式,各管各的:

HTTP 状态码管传输和协议层的粗分类。 鉴权失败用 401,权限不足用 403,被限流用 429,服务端自己炸了用 500。这几个语义是通用共识,客户端框架、网关、监控都认,不要自己发明。特别是 401 和 403 别混——前者的动作是刷新凭证重试,后者重试永远没用。

体内机器码管业务分支。 这套码要满足三条:只增不改,改了就是破坏性变更;枚举封闭,调用方能穷举;和文案彻底分离,文案随时可以改,码不能动。返回结构上我一般这么定:

{
  "code": "ORDER_ALREADY_CLOSED",
  "message": "订单已关闭,无法再次支付",
  "retryable": false,
  "requestId": "..."
}

retryable 这个字段是不是必须存在可以讨论,但**“每个码的可重试性必须被明确定义过”这件事没得商量**——写在字段里,或者写在码表文档里,两者选一。留白就等于把它交给调用方猜。

message 只给人看。 别让调用方去匹配文案做判断,你哪天改个措辞就是一次线上事故。

至于 requestId,让 AI 加这个字段它一般会照做,但你得自己确认它真的贯穿了日志——只在返回体里出现、日志里查不到的追踪 id 是摆设。

验收动作有两个,都很省事:

第一,让 AI 或者你自己照着码表写一个消费端的分支处理,如果它必须去看 message 才能决定动作,说明码表粒度不够。

第二,把典型失败用例用 curl 打一遍,确认状态码和体内码都对得上:

curl -i -X POST https://your-api.example.com/v1/orders \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $API_TOKEN" \
  -d '{"amount": -1}'

参数非法应该拿到 4xx 而不是 200 加一个体内错误,鉴权过期应该是 401 而不是 500。这两处 AI 生成的代码里翻车概率很高,因为它常常把所有异常兜到一个全局处理器里统一返回 500。关于返回结构在迭代中发生变化引发的一连串问题,可以另外看 接口返回结构变了

三、分页:默认给的偏移分页,在会变的数据上是错的

你不特别交代,AI 十有八九给你 pagepageSize,SQL 里落成 LIMIT ? OFFSET ?。这套在静态数据上没问题,在边翻边写的数据上必然出重复和漏项:你翻完第一页,有人往表头插了三条,第二页的窗口整体后移,原本第一页末尾的记录又出现一次;反过来删了记录就会漏。数据量大的时候还有第二个问题,偏移量越大数据库要扫过并丢弃的行越多,深翻会明显变慢。

所以要在需求描述里先把写入模式讲明白,再让它选方案。判断规则简单粗暴:

  • 数据基本只增不改、用户也只看前几页(比如后台的操作日志列表)——偏移分页够用,别过度设计。
  • 数据会被并发插入或批量改状态、或者调用方要从头翻到尾做全量同步——必须用游标分页。
  • 要给用户展示”共 1234 条、跳到第 50 页”——你得接受总数是个近似值,或者接受一次额外的计数查询,这是产品取舍不是技术问题,得你来拍。

游标分页有个 AI 经常漏掉的细节:排序键必须唯一。只按 created_at 排序,同一毫秒内的多条记录顺序不确定,游标就无法定位到确切位置。正确做法是复合排序键,时间在前、唯一 id 兜底:

SELECT * FROM orders
WHERE (created_at, id) < (:cursor_time, :cursor_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;

这里的行值比较写法((a, b) < (x, y))并非所有数据库都支持,能不能走到索引也各不相同,落地前请以你所用数据库的官方文档和真实执行计划为准;不支持的话就展开成 created_at < :t OR (created_at = :t AND id < :id) 这种等价形式,语义一样但索引利用要单独验。

游标本身建议编码后返回,不要把裸的时间戳和 id 暴露成两个查询参数——不是为了保密,是为了以后换实现的时候不用改接口契约。

验收动作是一个必须写的测试:翻页过程中并发写入,检查结果集。伪代码是这个意思:

first = fetch_page(cursor=None, size=20)
insert_new_records(5)          # 模拟并发写入
second = fetch_page(cursor=first.next_cursor, size=20)
ids = [r.id for r in first.items + second.items]
assert len(ids) == len(set(ids))   # 无重复

这个测试 AI 不会主动写,你得点名要。它一旦跑绿,分页方案基本就稳了。已经在线上出现重复漏项的排查路径,走 分页数据重复和漏掉 那篇。

四、幂等:它会给你加个参数,但不会替你定语义

让 AI 给写接口”加幂等”,它多半会加一个 Idempotency-Key 请求头,然后在处理逻辑里写一句”先查缓存,有就返回旧结果”。参数是对的,语义全是空的。你至少要自己回答五个问题,一个都不能留给它猜:

谁生成这个键。 客户端生成才有意义——服务端生成的键无法在重试时保持一致。这条要写进接口文档,否则调用方会每次重试都换个新键,等于没加。

作用域是什么。 同一个键在不同用户、不同租户下算不算同一个请求?一般应该按”租户加接口加键”三元组去重,只按键全局去重会撞车。

保留多久。 保留期必须长于调用方最坏情况下的重试窗口,否则超时重试进来时记录已经过期,照样做两遍。具体多长取决于你的调用方,各家实现不同,这个数得你定。

参数不一致怎么办。 同一个键、不同请求体——这是调用方的 bug,正确响应是明确拒绝并给一个专门的错误码,而不是安静地返回上一次的结果。后者会让 bug 潜伏到线上才爆。用哪个状态码各家实现并不统一,409 和 422 都常见,也有网关直接返回 4xx 加体内码的;这个选择本身不重要,重要的是写进文档并且始终如一,别一个接口一个样。要对齐某个具体平台的口径,以该平台官方最新说明为准。

并发同键怎么办。 两个相同请求同时进来,“先查后写”这套逻辑在并发下是不成立的——两个请求都查不到记录,都往下走。必须落到数据库的唯一索引上,靠插入冲突来仲裁,冲突方等待或直接返回处理中的状态。这一条 AI 生成的代码几乎必错,因为它写的是单线程叙事下的正确逻辑。

补充一句:读接口不需要幂等键,天然幂等的删除也不需要(重复删除返回同样的成功即可)。别一刀切全加,接口会变得很难用。重试发生之后怎么补救、幂等键怎么落库,看 重试之后同一件事做了两遍

五、什么情况下别再折腾

有几个明确的止损点,到了就该换路子,继续和模型拉扯只是消耗时间和额度。

同一处设计来回改超过三轮还不收敛,停手自己写。 这种情况几乎都是你的约束没交代清楚,模型在几个方案之间摆动。正确动作是关掉对话,用十分钟把约束写成一份短文档(写入模式、调用方名单、失败之后谁兜底),再重新开一轮。带着约束的一轮,胜过没约束的十轮。

它开始给你介绍某个具体产品的配置项、后台菜单名或报错原文,一律不采信。 这类细节是幻觉高发区,而且错得非常像真的。判断方法很简单:凡是它说得出名字但你查不到出处的东西,当它不存在。相关的核查方法在 如何核查 AI 的回答

接口已经上线且有外部调用方,就别再重构错误码和分页契约了。 换成加法:新增一个字段、新增一个版本路径,老的保持不动直到调用方迁完。破坏性变更省下的那点整洁,远不够赔沟通成本。

回滚点要提前留。 用 AI 大改接口层之前先切分支、先提交,改完不满意直接 git checkout -- . 或者 git reset --hard HEAD 回到起点,不要在一堆半成品上继续叠。这个习惯能省掉大部分”改废了理不清”的场面。

**换条路的判断依据:**如果你发现自己在教模型你们业务的领域规则——什么是”关闭态订单”、哪些状态可以被撤销——说明这活的性质不是代码生成而是知识传递,把规则整理成文档再让它照做,比反复解释划算。这个思路和 规格驱动开发 是一回事。

六、避坑清单

坑一:让它”参考业界最佳实践”设计错误码。 为什么会踩:这句话没有任何约束力,模型只能给最常见的模板,而最常见的模板恰好不包含可重试性这一维。怎么避:把要求写成可验收的句子——“每个错误码必须标注是否可重试、重试是否安全”,它就会照做。

坑二:把海外工具的可用性想当然。 为什么会踩:网上大量教程默认你能直连。实际情况是不少海外 AI 编程工具和模型服务官方对中国大陆有区域限制,不支持直连注册与使用,具体覆盖范围以各家官方最新说明为准。怎么避:选型阶段就把「官方是否支持你所在区域」当成硬约束写进候选表,优先选官方明确支持的服务,别等接口写到一半才发现服务调不通。这里不讨论任何绕开区域限制的做法。

坑三:分页方案定好了,但客户端 SDK 还是按偏移写的。 为什么会踩:AI 生成服务端和生成客户端往往是两次独立的对话,上下文没打通。怎么避:改完服务端立刻在同一轮里让它同步改调用方,或者干脆把接口契约文件作为唯一输入源生成两边。

坑四:幂等键存在内存缓存里。 为什么会踩:单机开发时跑得好好的,多实例部署后每个实例各存各的,去重直接失效。怎么避:幂等记录必须落到共享存储,且以数据库唯一约束为准,缓存只是加速层。

坑五:错误码用了数字且中途改过含义。 为什么会踩:数字码可读性差,改含义时调用方那边不会报错,只会静默走错分支。怎么避:用大写下划线的字符串码,只增不改,废弃的码保留在表里标注为已废弃。

坑六:接口文档和实现由两次生成产生,没对上。 为什么会踩:模型在两次生成之间不保证一致,字段名差一个下划线就能让联调卡半天。怎么避:文档从代码或契约文件生成,不要手工维护第二份真相。

坑七:把限流、重试、超时的责任全推给接口设计。 为什么会踩:这几件事分属不同层次,混在一处讨论必然理不清。怎么避:接口层只负责把状态和语义表达清楚(429 就是 429),退避与预算是调用方的事,两边各写各的。

坑八:验收只看联调通不通。 为什么会踩:联调是理想路径,上面这些坑全在异常路径上。怎么避:把失败用例写成测试——鉴权过期、参数非法、限流、并发同键、翻页中插入数据,这五条跑绿了才算完。

最后给一份可以直接照着过的自检清单,接口设计交付前逐条打勾:

  • 每个错误码是否标注了可重试性,调用方能否只看码决定动作
  • 401 与 403 是否区分正确,业务失败有没有被全部包成 200
  • 分页方案是否匹配真实写入模式,排序键是否唯一
  • 有没有一个”翻页中并发插入”的测试并且是绿的
  • 幂等键的生成方、作用域、保留期、参数冲突响应是否都写进了文档
  • 幂等是否靠数据库唯一约束兜底,而不是先查后写
  • 接口文档与实现是否同源,客户端调用方是否已同步
  • 大改之前是否已经提交过一次,回滚点在哪里

这八条里有六条 AI 能替你实现,但没有一条它能替你决定。把决定权拿回来,剩下的机械劳动交出去,这个环节的效率才是真涨了。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。