人人都会AI编程

11.5 异常最佳实践与错误处理原则

更新时间:2026-07-12

理解和会写 try/except 只是第一步,写出健壮、可维护的异常处理代码才是目的。这一节总结几条经过大量生产验证的实践原则,直接可用。


原则一:捕获具体的异常,避免裸 except

错误写法

try:
    risky_operation()
except:
    print("出错了")

except 会捕获所有异常,包括 KeyboardInterrupt(Ctrl+C)和 SystemExit,导致程序无法被正常中断。

正确写法

try:
    risky_operation()
except ValueError as e:
    logger.error(f"数据格式错误:{e}")
except IOError as e:
    logger.error(f"文件读写失败:{e}")
  • 只捕获你预期可能发生、并且知道如何处理的那几类异常。
  • 让未知异常继续往上抛出,暴露问题,避免隐藏 bug。

原则二:不要吞掉异常,至少要记录

错误写法

try:
    do_something()
except Exception:
    pass          # 静默忽略,问题消失了

吞掉异常是生产事故的常见来源——调用者根本不知道发生了什么,查问题无从下手。

最低要求

import logging

try:
    do_something()
except Exception as e:
    logging.exception("处理时发生未预期错误")   # 会记录完整堆栈
    # 如果无法恢复,可以选择重新 raise
    raise

即使当前无法处理,也留下日志证据,方便事后排查。


原则三:finally 用于清理资源,不是用于业务逻辑

finally 块中的代码无论是否发生异常都会执行,最适合做资源释放操作。

file = None
try:
    file = open("data.txt", "r")
    process(file)
except IOError:
    logger.error("文件处理失败")
finally:
    if file:
        file.close()

更推荐的做法是用上下文管理器(with 语句),它已经内置了资源清理逻辑:

with open("data.txt", "r") as file:
    process(file)
# 出了 with 块,文件自动关闭,即使发生异常也一样

对于自定义资源,也建议实现 enter/exit,让使用者可以用 with 统一管理生命周期。


原则四:不要用异常控制正常流程

异常是为处理意外、非正常情况而设计的,用它来做分支判断会导致代码难读且性能较差。

错误示例

# 用异常判断字典里有没有 key
try:
    value = my_dict["key"]
except KeyError:
    value = default

正确做法

value = my_dict.get("key", default)

异常仅用于真正意外的场景(如网络突然断开、文件无权限),普通条件判断留给 if / else


原则五:异常链与原因传递

在捕获异常后重新抛出新异常时,要保留原始异常信息,避免排查时丢失根因。

错误写法

try:
    open("config.json")
except FileNotFoundError:
    raise RuntimeError("配置文件不可用")   # 原始异常信息丢失

正确写法(使用 raise ... from ...):

try:
    open("config.json")
except FileNotFoundError as e:
    raise RuntimeError("配置文件不可用") from e

这样异常堆栈会清晰地显示:“配置文件不可用”是由 FileNotFoundError 引起的,便于定位根因。


原则六:合理使用 else 子句

try...except 还可以带一个 else 块,其中代码仅当 try没有任何异常时才会执行。适合放那些“依赖 try 块成功执行”的逻辑。

try:
    conn = create_connection()
except ConnectionError:
    logger.error("无法建立连接")
else:
    # 连接建立成功才执行
    execute_query(conn)

这样可以让代码意图更清晰:连接成功与数据库操作紧密相关,但不会被错误地放入 except 分支。


原则七:全局异常处理兜底

在程序的顶层(如 Web 框架的全局中间件、后台任务的主函数)加一个兜底的异常捕获,防止未处理异常导致进程崩溃,并输出完整日志。

def main():
    try:
        run_app()
    except Exception:
        logging.exception("应用发生未捕获异常")
        # 可以在此做告警、发送通知等

注意:这里捕获后通常需要优雅退出或重启,而不是静默忽略。


总结:记住这四条就够了

日常编码中,只要记住:

  1. 具体捕获,别用裸 except
  2. 不要吞异常,起码记日志。
  3. 清理资源用 finallywith
  4. 异常仅用于异常情况,别当流程控制用。

按这几条写出来的异常处理,既不会无故崩溃,也不会让问题藏到雷区,可以有效减少线上事故的排查时间。