永不过期的 API 密钥带来的问题
一个多年前签发、从未轮换、至今仍然有效的密钥并不是一种便利。它是一个多年来没有人真正检查过的隐患。
API 请求失败本来就已经让人心烦。如果返回的错误代码在任何地方都没有解释,情况会更糟,调试它的开发者不得不猜测 400 究竟意味着参数格式错误、缺少必填字段,还是某个完全不同的问题,只是恰好与另外三个毫不相关的问题共用同一个状态码。未记录的错误不只是一种不便。它会把五分钟就能解决的问题变成一场没有尽头的排查,有时最终变成一张支持工单,而只要阅读一个本该存在的页面,这张工单原本可以避免。
我们在错误文档中清楚地公开错误代码,列出每个代码的实际含义和常见原因,旁边还有描述请求其他失败方式的身份验证和速率限制页面。我们的目标是,当出现问题时,答案就在一个页面之外,而不是依据一般的 HTTP 状态码惯例去猜测,这些惯例未必能准确对应我们系统具体做了什么。
隐藏错误细节,即使是因为文档单薄而无意为之,有时也源于一种听起来合理的直觉:准确暴露请求失败的原因,理论上可能帮助有人探测 API 的弱点。实际上,这种顾虑很少能与真实代价相抗衡。遇到错误代码的人绝大多数是试图修复自己集成的正当开发者,而不是在绘制攻击面的对手。为了极少数恶意行为者而优化错误文档,牺牲其他所有人的清晰度,是把取舍搞反了。
清楚地公开错误代码还有一个设计纪律上的好处:它迫使内部保持一致。如果每个错误代码都必须附上通俗的解释来记录,就很难积累出一堆临时拼凑、相互重叠、只有最初编写它们的工程师才完全理解的错误条件。编写文档本身也悄然成为对错误处理的一种代码审查,因为一个难以清楚解释的错误,往往说明其底层条件从一开始就没有想清楚。
与配额相关的失败也有类似的处理,通过响应头而不仅仅是错误代码来说明。每个响应都会携带您的配额上限、已用量、剩余免费配额、网络用量、剩余额度和重置时间,因此因配额而失败的请求根本不是一个神秘的状态码。它是一个您在发送请求之前就可以查看的数字,也可以直接从失败的响应中读取。
这一切并不能消除请求失败带来的挫败感。它只是意味着这种挫败感应该在文档那里结束,得到一个真正的答案,而不是延续到支持队列中,或者变成一场猜谜游戏,翻遍旧论坛帖子,揣测一个完全不同的 API 的状态码在听起来类似的情况下可能是什么意思。