凌晨两点,你的手机炸了。不是微信消息,是监控报警。你的服务又双叒叕挂了。
你迷迷糊糊打开日志,看到满屏的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一下你的错误处理代码。别等到凌晨两点报警响起,你才后悔莫及。
记住:一个好的错误处理系统,是程序员对用户最大的尊重,也是对自己最大的救赎。