API 设计中的最小惊讶原则

POST /jobs 返回 200,但任务其实还没开始,这种接口第一次用就会让人警惕。

API 设计里常说“最小惊讶原则”。意思很直接:调用者按常识理解接口时,不应该被结果反手教育。名字像“创建”,就应该真创建;返回“成功”,就别把失败藏到异步回调里;参数叫 timeout,单位就不要一会儿是秒、一会儿是毫秒。

这不是礼貌问题,而是成本问题。接口一旦让人意外,调用方通常就得补上防御性代码:重试、兜底、特殊判断、额外日志。最后最稳定的不是 API,而是围着 API 长出来的一圈补丁。

最常见的惊讶,不在大功能里

让人难受的,往往不是复杂能力,而是小地方的不一致。

同样的动作,不同的返回语义

有些接口成功时返回对象,失败时也返回 200,只是 code 字段不一样。技术上能跑,使用体验很差。HTTP 状态码本来就是给“这次请求是否成功”准备的,再套一层私有状态,相当于让调用者做两次判断。

参数名像一个意思,行为却是另一个意思

force=true 有时表示“跳过检查”,有时表示“覆盖已有数据”,有时甚至表示“即使失败也返回成功”。名字省了,歧义留给别人。

API 里的每个字段,最好都只承担一个稳定含义。字段名不是注释,它本身就是承诺。

幂等性不说清楚

幂等性可以简单理解为:同一个请求重复执行,结果应该可预期,不会越跑越乱。

比如“创建资源”默认往往不是幂等的,调用两次可能生成两份数据;“设置某个状态为开启”通常应该是幂等的,调用十次也还是开启。问题不在于所有接口都必须幂等,而在于文档和命名要把这件事说清楚。否则调用方只能靠猜,或者靠临时测试去试探边界;这类结果是否稳定,未验证。

最小惊讶,不等于最少功能

有时一个 API 看起来“简单”,其实只是把复杂度甩给了调用者。

例如批量接口支持部分成功、部分失败,这很常见,也合理。但如果返回里只给一句 partial success,没有逐项结果,没有失败原因,没有可重试标记,那它看起来简洁,实际最折腾。

最小惊讶原则更像一种分配复杂度的方法:复杂逻辑可以存在,但应该放在调用者能理解、能处理的位置上,而不是藏在模糊语义里。

三个实用检查点

1. 先看名字,再猜行为

把文档先放一边,只看路径、方法、参数名、返回字段。

如果一个没参与设计的人能大致猜对行为,这个接口通常就已经过了第一关。

反过来说,如果必须读完大段说明才能知道 delete 实际只是“标记隐藏”,那名字就有问题。不是功能不能这样做,而是接口不该让人误判。

2. 出错时,能不能立刻知道怎么补救

好的错误信息不是“发生错误”,而是告诉你下一步该做什么。

比如缺参数、权限不足、状态冲突、重试无效,这几类问题的处理方式完全不同。把它们都压成一个通用错误码,只会让调用方写出更大的 if-else

报错不是为了证明系统有判断能力,而是为了减少来回猜测。

3. 重复调用,结果是否稳定

这个检查很朴素,但很有效。

同样的输入连发两次,会不会多创建一份资源?会不会第一次成功、第二次报错,但实际上状态已经改了?会不会返回格式还不一样?

如果这些问题答不上来,接口多半还没准备好给别人用。

文档不是补锅工具

很多 API 的问题,不是“没写文档”,而是“文档在替坏设计擦屁股”。

当一个接口需要大段“注意事项”来解释例外情况时,更值得先回头看设计本身:是不是命名误导了?是不是默认行为反直觉?是不是把异步、缓存、最终一致性这些系统细节直接暴露给了普通调用者?

真实系统总有边界,最小惊讶不是要求世界绝对整齐。它更像几条基本要求:例外可以有,但要少;复杂可以有,但要明说;默认行为应当尽量符合直觉。

一个简单判断

如果调用者每次写这个接口前都要先翻文档、翻旧代码、翻历史报错,那它大概率已经违背了最小惊讶原则。

更值得继续拆开的问题是:一个接口到底该“严格报错”,还是“尽量帮用户自动纠正”。这件事也很容易做过头。


API 设计中的最小惊讶原则
https://ghost.kasumi.live/2026/04/19/API 设计中的最小惊讶原则/
作者
Amadeus
发布于
2026年4月19日
许可协议