做后端开发这么多年,我见过太多项目在错误处理上栽跟头。不是返回格式不统一,就是错误信息跟放烟花似的——满天飞却不知道哪颗是哪颗。今天就来聊聊API错误处理这事儿,干货不少,建议先收藏。
先说个真实的笑话
我之前接手过一个老项目,某个接口的异常处理逻辑是这样的:
try {
doSomething();
} catch (Exception e) {
return 500;
}
就一行return 500,连个日志都没有。你问用户看到什么?一个字:「服务器开小差啦」。然后就没有然后了。这项目后来被我重写的时候,光是排查原来那些隐藏的bug就花了一周。
错误处理的本质是什么?
很多人觉得错误处理就是try-catch一下、返回个错误码就完事了。这想法,就像觉得做饭就是「把东西煮熟」一样——技术上来说没错,但离「做好」差了十万八千里。
错误处理的本质是让调用方知道你出了问题,并且给他足够的信息来应对。
一个好的错误响应应该包含:
- 错误码:机器可读,方便做逻辑判断
- 人类可读的消息:告诉你发生了什么
- trace/request_id:能追溯到具体哪次请求
- 解决建议(可选但推荐):用户看到这个能知道下一步怎么办
我推荐的错误响应格式
{
"code": "VALIDATION_ERROR",
"message": "请求参数校验失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
}
],
"request_id": "req_abc123xyz",
"help": "https://api.example.com/docs/errors#VALIDATION_ERROR"
}
这是我现在项目里的标配格式。code用大写下划线风格,message给人类看,details在字段级错误时特别有用,request_id是排查问题的神器。
错误码体系怎么设计?
很多团队的错误码是「想到一个加一个」,最后搞了几百个还不带分类的。我建议按模块+类型二级分类:
AUTH_001 // 认证模块,001为用户不存在
AUTH_002 // 认证模块,002为密码错误
ORDER_001 // 订单模块,001为库存不足
ORDER_002 // 订单模块,002为价格变动
这种格式的好处是:只看错误码,你大概就知道是哪个模块出了问题。debug的时候效率提升不是一星半点。
异常要分层次处理
我见过最离谱的一个项目,所有异常都一股脑catch(Exception e)处理。这跟把所有垃圾都扔进一个垃圾桶有什么区别?
我的经验是分三层:
- 业务异常(如库存不足):这是业务逻辑的一部分,应该显式抛出,业务码用4xx
- 参数校验异常:属于用户输入问题,返回400,并在details里告诉用户哪个字段有问题
- 系统异常(数据库挂了、外部服务超时):这是你控制不了的,返回500,但内部要记录详细日志
日志!日志!日志!
重要的事说三遍。系统异常如果没日志,那简直是灾难。你以为自己返回了500用户就知道怎么办了?用户只会觉得你服务烂。
日志要记录:
log.error("External payment service failed", "request_id={}, user_id={}, error={}",
requestId, userId, e.getMessage());
不要只记个error occurred就完事。排查问题的时候你恨不得给当初写这条日志的人一巴掌。
全局异常处理器是必备的
如果你用Java Spring,推荐配一个@ControllerAdvice;如果用Python FastAPI,exception_handler用起来。这些框架工具不是摆设,是让你少加班的好帮手。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(BusinessException e) {
return ResponseEntity
.status(e.getStatus())
.body(ErrorResponse.of(e.getCode(), e.getMessage()));
}
}
有了这个,你的业务代码里就不需要每个方法都写catch了,干净利落。
说个反直觉的
有时候,适当的「宽容」比严格校验更受欢迎。比如用户输入了全角空格,你非要报错说格式不对?直接trim一下不好吗?但在涉及安全的地方(密码、验证码),请务必严格。
总结一下
好的错误处理不是一蹴而就的,是踩坑踩出来的经验。我的建议:
- 统一错误响应格式,越早定越好
- 错误码要分类,带上模块前缀
- 异常分层处理,别一把梭
- 系统异常必须记录详细日志
- 用好框架提供的全局异常处理
- 用户可见的错误要给解决建议
如果你现在的项目错误处理还是一团浆糊,建议从今天开始慢慢改。不用一口气重写,先把新接口按这套来,老接口借bug修复的时候顺手优化。你的下一个接手的同事会感谢你的。
有问题欢迎留言讨论~