业务错误应抛自定义BizError异常而非通用Exception,系统异常需分级捕获并隔离处理,错误码须为固定字符串、带前缀枚举或中心化整数ID,严禁动态生成或混用HTTP状态码。

业务错误该不该抛异常
业务错误不是程序崩溃,而是用户操作不合法或业务规则不满足,比如「余额不足」「订单已取消」。这类问题不该用 Exception 无差别兜底,否则上层无法区分是代码 bug 还是业务拒绝。
推荐做法是定义显式业务错误类,继承自 Exception 但语义独立:
class BizError(Exception):
def __init__(self, code: str, message: str):
self.code = code
self.message = message
super().__init__(message)
使用示例
if order.status == "cancelled":
raise BizError("ORDER_CANCELLED", "该订单已被取消")
-
BizError不该被通用except Exception:捕获,应显式处理 - 避免用
ValueError或RuntimeError替代,它们属于系统层语义 -
前端需要稳定错误码时,
code字段必须是字符串常量,不能拼接或动态生成
系统异常必须分级捕获
数据库超时、网络断连、JSON 解析失败——这些是系统级故障,不可预测且需降级或重试。它们必须和业务错误隔离,否则会导致错误码混乱、监控失真、重试逻辑误触发。
常见系统异常类型及建议处理方式:
立即学习“Python免费学习笔记(深入)”;
-
requests.exceptions.Timeout:标记为临时性失败,可重试,返回 HTTP 503 -
psycopg2.OperationalError:连接池耗尽或 DB 挂了,需熔断,返回 HTTP 500 -
json.JSONDecodeError:上游数据格式错误,属外部依赖问题,记录日志并返回 400(若确认非客户端输入) -
ValidationError(来自 Pydantic):属于输入校验失败,归为客户端错误(HTTP 422),不视为系统异常
FastAPI 中统一错误响应结构
直接在路由函数里 raise 异常,靠异常处理器转成标准 JSON 响应,比每个接口手动 return JSONResponse 更可靠。
关键点:
- 用
HTTPException处理客户端错误(4xx),它自带status_code和detail - 自定义
BizError需注册独立 handler,避免被Exception兜底覆盖 - 所有系统异常默认走全局
Exceptionhandler,但必须记录完整 traceback 到日志,不能只打str(e)
@app.exception_handler(BizError)
async def biz_error_handler(request: Request, exc: BizError):
return JSONResponse(
status_code=400,
content={"code": exc.code, "message": exc.message}
)
@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
logger.error("Unhandled exception", exc_info=exc)
return JSONResponse(
status_code=500,
content={"code": "INTERNAL_ERROR", "message": "服务暂时不可用"}
)
错误码设计别碰这三条红线
错误码不是随便编的字符串,它是前后端联调、监控告警、客服排查的唯一线索。以下三点踩中任一,后续维护成本会指数上升:
- 同一个业务场景在不同接口返回不同错误码(如「库存不足」在下单接口是
STOCK_SHORT,在支付接口变成INSUFFICIENT_STOCK) - 把 HTTP 状态码直接当业务码用(如返回
404作为「用户不存在」的业务标识,导致前端无法区分是资源未找到还是路径写错) - 错误码含变量或上下文信息(如
ORDER_NOT_FOUND_123456),导致监控无法聚合、日志检索失效
真正稳定的错误码只有三类:固定字符串(ORDER_EXPIRED)、带固定前缀的枚举(pay.timeout)、或由中心化配置服务下发的整数 ID(需配套管理后台)。










