错误处理 (Error Handling)#
错误处理不仅是如何捕获 bug,更是编程语言控制流([[控制流]])设计哲学的一部分。不同的范式对待错误有着截然不同的态度。
1. 错误即数据 (Error as Values)#
将错误视为普通的返回值,强制调用者进行检查。这种方式控制流清晰,没有隐藏的跳转。
- 返回码 (Return Codes):C 语言的经典做法,通过返回值表示状态,通过指针参数返回结果。
- 多返回值机制:Go 语言的标志性设计
val, err := func()。参考:[[错误即是值]]
- 代数数据类型 (ADT):Rust 的
Result<T, E>或 Haskell 的EitherMonad。通过编译器强制模式匹配来处理成功与失败分支,是目前被认为最严谨的错误处理方式。
2. 错误即控制流 (Error as Control Flow)#
当错误发生时,打断当前的正常执行顺序,沿着调用栈向上抛出,直到被捕获。
- 异常处理 (Exceptions, try-catch-throw):C++、Java、Python 等语言的标配。优点是让正常代码路径保持干净,缺点是存在隐藏的控制流跳转。
- Checked vs Unchecked Exceptions:
- Checked (如 Java):强制方法签名声明抛出,强制调用者捕获。初衷是严谨,但容易导致样板代码和封装破坏,现代语言多放弃此设计。
- Unchecked (如 C#, Python):不强制声明和捕获。
3. 容错哲学:Let it crash#
Erlang 和 Actor 模型的标志性理念。
- 不防守编程:在工作线程 / Actor 中遇到意外错误直接崩溃,不试图挽救。
- 监督树 (Supervisor Tree):由专门的监督进程监控工作进程,一旦崩溃,按照预定策略(如重启)恢复。
4. 工程最佳实践思考#
- 可恢复错误 vs 恐慌 (Panic / Fatal):可恢复的业务异常应当作数据返回(或抛出普通异常),不可恢复的环境 / 依赖错误(如内存不足、死锁)应当直接 Panic 终止程序。
- 错误上下文 (Error Context / Wrapping):错误在向上传递时,必须附带当前层的上下文信息,否则排查困难。
- 异常还是返回值? 随着 Go / Rust 的流行,"错误即值" 重新成为系统编程和高并发后端的主流选择。