第六部分:错误处理与异常模型 —— 构建健壮系统的基石
错误处理不是事后的补救,而是系统设计的一部分。深度运用编程语言的错误处理机制,可以写出自我解释、易于恢复的代码。
6.1 异常模型:受检异常与非受检异常(Java视角)
Java区分了两类异常:
受检异常(Checked Exception):必须显式处理(try-catch或throws),如IOException、SQLException,代表可恢复的意外条件。
非受检异常(Unchecked Exception):不强制处理,如NullPointerException、IllegalArgumentException,代表编程错误。
争议:受检异常因为强制处理而饱受诟病,导致空catch或抛出更高层的异常。很多现代语言(C#、Kotlin、Go)不采用受检异常。
示例31:Java受检异常的处理模式
// 反面:吞掉异常
try {
Files.readAllBytes(Paths.get("file.txt"));
} catch (IOException e) {
// 什么都不做,错误被隐藏
}
// 正面:记录并重新抛出包装异常
try {
Files.readAllBytes(Paths.get("file.txt"));
} catch (IOException e) {
throw new RuntimeException("读取文件失败", e);
}
6.2 基于返回值的错误处理:Go的error、Rust的Result
Go语言采用显式的多返回值错误模式,强制调用者检查错误。Rust则用Result类型,提供更丰富的组合方法。
示例32:Go错误处理惯用法
func readFile(filename string) (string, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return "", fmt.Errorf("read file %s: %w", filename, err)
}
return string(data), nil
}
func main() {
content, err := readFile("config.txt")
if err != nil {
log.Fatalf("错误: %v", err) // 终止程序
}
fmt.Println(content)
}
进阶技巧:
使用fmt.Errorf的%w包装错误,保留错误链。
使用errors.Is和errors.As进行特定错误判断。
自定义错误类型实现error接口。
示例33:Rust的Result类型与模式匹配
use std::fs::File;
use std::io::Read;
fn read_username() -> Result<String, std::io::Error> {
let mut file = File::open("username.txt")?; // ? 运算符传播错误
let mut name = String::new();
file.read_to_string(&mut name)?;
Ok(name)
}
fn main() {
match read_username() {
Ok(name) => println!("Username: {}", name),
Err(e) => eprintln!("Error reading username: {}", e),
}
}
?运算符:如果Result是Err,则提前返回该错误;如果是Ok,则取出值继续。这极大地简化了错误传播。
6.3 结合Option/Maybe类型处理空值
空指针异常(NullPointerException)是大多数语言中最常见的运行时错误。现代语言通过Optional(Java)、Option(Rust)、Maybe(Haskell)来强制处理可能为空的值。
示例34:Java Optional的正确使用
import java.util.Optional;
public class OptionalDemo {
public static Optional<String> findUserEmail(String userId) {
// 模拟查询,可能返回null
if ("123".equals(userId)) {
return Optional.of("alice@example.com");
} else {
return Optional.empty();
}
}
public static void main(String[] args) {
// 错误用法:直接调用.get()可能抛出NoSuchElementException
// String email = findUserEmail("456").get();
// 正确用法1:提供默认值
String email = findUserEmail("456").orElse("default@example.com");
// 正确用法2:如果存在才执行
findUserEmail("123").ifPresent(e -> System.out.println("Sending to " + e));
// 正确用法3:链式转换
String domain = findUserEmail("123")
.filter(e -> e.contains("@"))
.map(e -> e.split("@")[1])
.orElseThrow(() -> new IllegalArgumentException("Invalid email"));
}
}
关键原则:不要在代码中传播null,而是使用Optional作为返回值类型,强迫调用者处理缺失情况。
6.4 自定义错误类型与错误上下文
为了提供更丰富的错误信息,你可以定义自己的错误类型,包含错误代码、上下文、原始异常等。
示例35:Python自定义异常与异常链
class ValidationError(Exception):
"""自定义验证错误"""
def __init__(self, message, field, code):
super().__init__(message)
self.field = field
self.code = code
def validate_age(age):
if age < 0:
raise ValidationError("年龄不能为负数", "age", "NEGATIVE")
if age > 150:
raise ValidationError("年龄超出合理范围", "age", "TOO_HIGH")
try:
validate_age(-5)
except ValidationError as e:
print(f"字段 {e.field} 错误码 {e.code}: {e}")
# 异常链:从一个异常引发另一个
def load_data():
try:
int("not a number")
except ValueError as e:
raise RuntimeError("解析数据失败") from e
try:
load_data()
except RuntimeError as e:
print(f"异常: {e}")
print(f"原始异常: {e.__cause__}")