为人类设计 API:错误消息

2025-06-04

为人类设计 API:错误消息

好的错误消息,坏的错误消息

错误信息就像税务机关的信件。你宁愿不收到它们,但当你收到时,你希望他们清楚地告诉你下一步该怎么做。

在集成新的 API 时,难免会遇到错误。即使您严格遵循文档并复制粘贴代码示例,也难免会遇到问题——尤其是在您不再依赖示例代码,而是根据自己的用例进行调整的情况下。

良好的错误信息是 API 中被低估和低估的一部分。我认为,在教导开发人员如何使用 API 方面,它们与文档或示例一样重要。

举个例子,很多人喜欢动觉学习,或者说边做边学。他们舍弃官方文档,只喜欢借助 IDE 和API 参考,自己动手实现集成

让我们首先展示一个我在野外看到的真实错误消息的示例:



{
  status: 200,
  body: {
    message: "Error"
  }
}


Enter fullscreen mode Exit fullscreen mode

如果这看起来没什么吸引力,那是因为它确实如此。有很多因素导致这个错误信息毫无帮助;让我们一一分析一下。

发送正确的代码

上面是一个错误,真的吗?消息体显示是错误,但状态码却是200,这说明一切正常。这不仅令人困惑,而且非常危险。大多数错误监控系统会先根据状态码进行过滤,然后再尝试解析消息体。这个错误很可能会被归入“一切正常”的范畴,从而被完全忽略。只有添加一些自然语言处理功能,才能自动检测到这实际上是一个错误,而这却是一个针对简单问题而提出的荒谬的过度设计的解决方案。

状态码是给机器用的,而错误消息是给人类用的。虽然对状态码有深入的理解总是好的,但你不需要了解所有状态码,尤其是有些状态码比较深奥。实际上,你的 API 用户只需要了解下表:

代码 信息
200 - 299 一切都好
400 - 499 你搞砸了
500 - 599 我们搞砸了

当然,您可以而且应该更具体地了解错误代码(例如,当您对某人在短时间内发送过多请求进行速率限制时,应该发送 429 )。

重点是,HTTP 响应状态代码出于某种原因而成为规范的一部分,并且您应该始终确保发回正确的代码。

这看起来很明显,但很容易意外忘记状态代码,就像这个使用 Express.js 的 Node 示例一样:



// ❌ Don't forget the error status code
app.post('/your-api-route', async (req, res) => {      
  try {
    // ... your server logic
  } catch (error) {    
    return res.send({ error: { message: error.message } });
  }  

  return res.send('ok');
});

// ✅ Do set the status correctly
app.post('/your-api-route', async (req, res) => {      
  try {
    // ... your server logic
  } catch (error) {    
    return res.status(400).send({ error: { message: error.message } });
  }  

  return res.send('ok');
});


Enter fullscreen mode Exit fullscreen mode

在上面的片段中,无论是否发生错误,我们都会发送 200 状态码。在下面的片段中,我们通过确保将相应的状态码与错误消息一起发送来解决这个问题。请注意,在生产代码中,我们希望区分 a400500error,而不是一概而论地400涵盖所有错误。

描述性

接下来是错误消息本身。我想大多数人都会同意,“Error”和没有消息一样有用。响应的状态码应该已经告诉你是否发生了错误,而消息需要详细说明,以便你能够真正解决问题。

故意使用晦涩难懂的消息来掩盖系统内部细节,让最终用户难以理解,这或许很诱人;但是,请记住你的目标受众是谁。API 是为开发人员服务的,他们想知道究竟出了什么问题。这些开发人员有责任向最终用户显示错误消息(如果有)。如果你是最终用户,收到“发生错误”的消息是可以接受的,因为你不是调试问题的人(尽管这仍然令人沮丧)。作为一名开发人员,没有什么比出现问题而 API 却没有提供基本的提示更令人沮丧的

让我们以前面那个错误错误消息的例子为例,并对其进行改进:



{
  status: 404,
  body: {
    error: {
      message: "Customer not found"
    }    
  }
}


Enter fullscreen mode Exit fullscreen mode

我们已经可以看到:

  • 我们有一个相关的状态代码:404,未找到资源
  • 信息很明确:这是一个尝试检索客户的请求,但由于找不到客户而失败
  • 错误消息被包装在一个错误对象中,使得处理错误更加容易。如果不依赖状态码,你可以简单地检查是否存在body.error错误。

这样就好多了,但还有改进的空间。错误信息虽然有用,但没什么帮助

乐于助人

我认为,优秀的 API 之所以能区别于那些“还行”的 API,就在于此。告知错误是最低要求,但开发者真正想知道的是如何修复它。一个“有用”的 API 会与开发者协作,消除解决问题过程中的任何障碍。

“未找到客户”这条消息给了我们一些关于错误原因的线索,但作为 API 设计人员,我们知道这里可以包含更多信息。首先,让我们明确一下未找到的是哪个客户:



{
  status: 404,
  body: {
    error: {
      message: "Customer cus_Jop8JpEFz1lsCL not found"
    }    
  }
}


Enter fullscreen mode Exit fullscreen mode

现在,我们不仅知道发生了错误,而且还会收到错误的 ID。这在查看一系列错误日志时尤其有用,因为它可以告诉我们问题出在某个特定的 ID 上,还是多个 ID 上。这为我们判断问题出在单个客户身上,还是发出请求的代码提供了线索。此外,由于ID 带有前缀,因此我们可以立即判断是否使用了错误的 ID 类型。

我们可以更进一步地提供帮助。在 API 方面,我们可以获取有助于解决错误的信息。我们可以等待开发人员自己尝试解决问题,或者直接向他们提供我们认为有用的额外信息。

例如,在我们的“未找到客户”示例中,未找到客户的原因可能是提供的客户 ID 在实时模式下存在,但我们使用的是测试模式密钥。使用错误的 API 密钥是一个很容易犯的错误,一旦您知道问题所在,就很容易解决。如果我们在 API 端快速查找该 ID 所指向的客户对象是否存在于实时模式下,我们就可以立即提供该信息:



{
  status: 404,
  body: {
    error: {
      message: "Customer cus_Jop8JpEFz1lsCL not found; a similar object exists in live mode, but a test mode key was used to make this request."
    }    
  }
}


Enter fullscreen mode Exit fullscreen mode

这比我们之前的方案有用得多。它能立即识别问题,并提供解决问题的线索。其他一些示例如下:

  • 如果类型不匹配,请说明预期的内容和收到的内容(“预期为字符串,收到的是整数”)
  • 请求缺少权限吗?告诉他们如何获取权限(“使用此 URL 在控制面板上激活此付款方式”)
  • 请求中缺少某个字段吗?请准确说明缺少的是哪个字段,或许可以链接到文档或 API 参考中的相关页面。

注意:在类似最后一条的情况下,请务必谨慎提供信息,因为信息泄露可能会带来安全风险。例如,在身份验证 API 中,如果您在请求中提供了用户名和密码,返回“密码错误”错误会让潜在攻击者知道,虽然密码不正确,但用户名是正确的。

提供更多拼图碎片

我们可以而且应该尽力提供帮助,但有时这还不够。您可能遇到过这样的情况:您认为自己在 API 请求中传递了正确的字段,但 API 却不同意。找到解决方案的最简单方法是回顾原始请求以及您传递的具体内容。如果开发人员没有设置日志记录,那么这很难做到,但是 API 服务应该始终保留请求和响应的日志,那么为什么不与开发人员共享这些日志呢?

在 Stripe,我们为每个响应都提供了一个请求 ID,由于它总是以 开头,因此很容易识别req_。输入此 ID 并在控制面板上查找,您将看到一个包含请求和响应详细信息的页面,其中包含一些额外的详细信息,方便您进行调试。

Stripe 控制面板上的有用信息

请注意,仪表板还提供时间戳、API 版本甚至源代码(在本例中为stripe-node的 8.165 版本)。

作为额外的奖励,提供请求 ID 可以使我们Discord 服务器中的 Stripe 工程师非常轻松地查找您的请求,并通过在 Stripe 端查找请求来帮助您进行调试。

富有同理心

最令人沮丧的错误是 500 错误。这意味着 API 端出了问题,因此不是开发人员的错。这类错误可能是 API 提供商端的暂时故障或潜在中断,而您当时无法真正了解具体情况。如果最终用户依赖您的 API 来实现关键业务路径,那么遇到这类错误会非常令人担忧,尤其是在您开始快速连续地遇到这些错误的情况下。

与其他错误不同,这里并不需要完全透明。您不应该将导致 500 错误的任何内部错误直接转储到响应中,因为这会泄露有关系统内部运作的敏感信息。您应该完全透明地告知用户导致错误的操作,但您需要谨慎处理导致错误时共享的内容

就像上面第一个示例一样,一个乏味的“500: error”错误信息和根本没有信息一样没用。相反,你可以表达你的同理心,确保开发人员知道错误已被确认,并且有人正在处理,这样他们就安心了。以下是一些示例:

  • “发生错误,团队已收到通知。如果此问题持续发生,请联系我们{URL}
  • “出了点问题,{URL}如果这种情况持续发生,请查看我们的状态页面”
  • “出了点问题,我们的工程师已收到通知。请稍后重试。”

它不能解决根本问题,但它确实有助于减轻打击,因为它可以让用户知道您正在处理这个问题,并且如果错误仍然存​​在,他们可以选择跟进。

整合起来

总而言之,有价值的错误消息应该:

  • 使用正确的状态代码
  • 描述性
  • 乐于助人
  • 提供详尽的信息
  • 富有同理心

以下是尝试使用错误的 API 密钥检索客户后 Stripe API 错误响应的示例:



{
  status: 404,
  body: {
    error: {
      code: "resource_missing",
      doc_url: "https://stripe.com/docs/error-codes/resource-missing",
      message: "No such customer: 'cus_Jop8JpEFz1lsCL'; a similar object exists in live mode, but a test mode key was used to make this request.",
      param: "id",
      type: "invalid_request_error"
    }
  },
  headers: {    
    'request-id': 'req_su1OkwzKIeEoCy',
    'stripe-version': '2020-08-27',    
  }  
}


Enter fullscreen mode Exit fullscreen mode

(为简洁起见省略了一些标题)

我们在这里:

  1. 使用正确的 HTTP 状态代码
  2. 将错误包装在“错误”对象中
  3. 通过提供以下帮助:
    1. 错误代码
    2. 错误类型
    3. 相关文档的链接
    4. 本次请求使用的API版本
    5. 关于如何解决这个问题的建议
  4. 提供请求 ID 来查找请求和响应配对

结果是错误消息中充满了有用的信息,即使是最初级的开发人员也能够解决问题并发现如何使用可用的工具自己调试代码。

为人类设计 API

通过将所有这些部分整合在一起,我们不仅为开发者提供了一种纠正错误的方法,而且还确保了一种有效的方式来指导开发者如何使用我们的 API。在设计 API 时始终以人类开发者为中心,意味着我们会采取措施确保我们的 API 不仅直观易用,而且易于使用。

我们在这里涵盖了很多内容,实施其中一些缓解措施似乎令人难以承受,但幸运的是,有一些资源可以帮助您使您的 API 更加人性化:

有没有你觉得很棒的错误信息示例(或者觉得很糟糕,因为更有趣)?我很想看看!请在下方留言或在Twitter上联系我。

关于作者

保罗·阿斯杰斯

Paul Asjes是 Stripe 的开发倡导者,负责编写代码、编写代码,并主持每月一次的开发者问答系列活动。工作之余,他喜欢酿造啤酒、制作肉干,以及在马里奥赛车游戏中与儿子较量。

文章来源:https://dev.to/stripe/designing-apis-for- humans-error-messages-94p
PREV
在 React 中构建无限滚动组件简介 App 组件无限滚动与我联系🚀 结论
NEXT
Kubernetes for Dummies 让我们开始使用 K8s 以下命令很有用,但您应该小心不要删除任何重要内容 结论