程序员血泪史:那些年我们一起踩过的API错误处理大坑

2026-09-01 7 0

凌晨两点,你的手机炸了。不是微信消息,是监控报警。你的服务又双叒叕挂了。

你迷迷糊糊打开日志,看到满屏的error,然后陷入深深的困惑:这玩意儿到底是啥意思?是我写的bug?还是网络抖动?还是第三方服务又抽风了?

恭喜你,你正在为"优雅"的API错误处理付出代价。

错误处理的第一宗罪:什么都返回200

我见过最离谱的API是这样的:

{
  "code": 1001,
  "message": "操作失败",
  "data": null
}

请求失败了,HTTP状态码是200 OK。

这不是在逗我?你跟HTTP协议有仇?

很多人觉得HTTP状态码不够用,非要自己发明一套错误码体系。但问题在于,你的客户端可能是浏览器、可能是APP、可能是其他服务——它们第一件事就是看HTTP状态码。

当你返回200表示失败时,CDN会缓存这个"成功"的响应。负载均衡器会认为服务healthy。你的监控系统会认为一切正常。直到用户打电话投诉。

错误码的"文艺复兴"

好,现在你学聪明了,开始用正确的HTTP状态码。但很快你发现,404不够用,500也不够用,400系列就那么几个。

于是你开始自定义错误码:

{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "用户不存在",
    "details": {
      "user_id": 12345
    }
  }
}

看起来不错对吧?但问题来了,你的错误码跟别人的错误码不一样。支付服务用PAY_001,订单服务用ORD_001,用户服务用USR_001。客户端要写多少适配器?

我的建议是:错误码要分层

第一层:HTTP状态码,这是网络层面的语言,所有人都必须遵守。

第二层:业务错误码,这是你的内部语言,但要带上业务前缀,比如PAY_ORDER_NOT_FOUND,别跟别人撞车。

第三层:错误消息,这是给人类看的,可以本地化。

那个该死的错误消息

你见过这样的错误消息吗?

"操作失败"
"系统异常"
"请求超时"
"未知错误"

恭喜你,你的错误消息跟没说一样。

当用户在支付时看到"操作失败",他会想:是我的卡有问题?余额不足?还是你们系统又挂了?如果是余额不足,用户还可以重试;如果是系统挂了,用户重试一万次也没用。

错误消息要回答三个问题:发生了什么、为什么发生、用户该怎么办

{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "账户余额不足,无法完成支付",
    "details": {
      "required": 1000,
      "available": 300
    },
    "action": "请充值后再试,或选择其他支付方式"
  }
}

你看,这才叫错误消息。

日志?你真的会打吗?

很多人打日志是这样的:

logger.error("请求失败了")
logger.error("异常: " + e.toString())

然后出问题的时候,你面对的是:

2026-08-15 02:13:45 ERROR 请求失败了
2026-08-15 02:13:45 ERROR 异常: java.lang.NullPointerException

这是日志还是谜语?

好的日志应该像故事一样,从请求进来到失败出去,每个关键节点都记录清楚:

logger.error("支付失败", "context", {
  order_id: "ORD_123456",
  user_id: 789,
  error_code: "PAY_TIMEOUT",
  retry_count: 3,
  third_party_response_time: "5000ms",
  timestamp: "2026-08-15T02:13:45Z"
});

这样的日志,出了问题你一眼就能定位。否则你就等着在日志的海洋里游泳吧。

重试的艺术与灾难

重试是个好东西,但用不好就是灾难。

我见过有人这样重试:

while (true) {
  try {
    doSomething();
    break;
  } catch (Exception e) {
    // 永远重试,直到地老天荒
  }
}

这是DDOS攻击的另一种形式。

好的重试策略要考虑:什么错误可以重试(网络抖动、超时),什么错误不能重试(业务逻辑错误、认证失败)。重试要指数退避,别一顿狂轰滥炸。重试要有最大次数,别没完没了。重试要记录,方便排查。

// 伪代码示例
for (int i = 0; i < maxRetries; i++) {
  try {
    return doSomething();
  } catch (RetryableException e) {
    if (i == maxRetries - 1) throw e;
    long delay = Math.min(1000 * Math.pow(2, i), 30000);
    Thread.sleep(delay + randomJitter());
  }
}

监控:你看不见的问题才是真问题

很多团队的错误处理是这样的:用户报错了,我才知道有问题。用户没报错,我就以为没问题。

Too young, too simple。

你必须监控你的错误率。不是用户告诉你,而是你自己要知道。当错误率突然从0.1%飙升到5%,你要第一时间知道,而不是等用户发微博吐槽。

几个关键指标:

  • 错误率:5xx占比、4xx占比、业务错误码分布
  • 响应时间:P99、P95、平均值
  • 错误集中度:是所有接口都在报错,还是某个接口在作妖

说在最后

错误处理这件事,做好了是看不见的——因为根本没机会出错。做砸了也是看不见的——因为你根本不知道哪里砸了。

所以啊,趁现在服务还活着,好好review一下你的错误处理代码。别等到凌晨两点报警响起,你才后悔莫及。

记住:一个好的错误处理系统,是程序员对用户最大的尊重,也是对自己最大的救赎

相关文章

你以为AI很便宜?算完这笔账你可能不这么想了
【AI探索】当AI开始抢饭碗,我决定先让它帮我写辞职信
救命!部署AI工具这件事,终于有人替你做了 🦞
你以为在调教AI?醒醒,是AI在驯化你
AI写东西越来越顺,但你有没有发现——它越来越没”人味”了?
当AI开始整顿职场,我决定先整顿它的prompt

发布评论