第 12 章:异常处理
第 12 章:异常处理
“错误是程序的常态,优雅地处理它们是程序员的修养。”
12.1 异常基础
12.1.1 异常类层次
Exception
├── NoMemoryError
├── ScriptError
│ ├── LoadError
│ ├── NotImplementedError
│ ├── SyntaxError
│ └── SystemExit
├── SecurityError
├── SignalException
│ └── Interrupt
├── StandardError(默认 rescue 捕获这个)
│ ├── ArgumentError
│ ├── IOError
│ ├── IndexError
│ ├── KeyError
│ ├── NameError
│ │ └── NoMethodError
│ ├── RangeError
│ │ └── FloatDomainError
│ ├── RegexpError
│ ├── RuntimeError
│ ├── TypeError
│ └── ZeroDivisionError
└── SystemCallError
├── Errno::EACCES
├── Errno::ENOENT
└── ...
12.1.2 begin / rescue / end
# 基本异常处理
begin
result = 10 / 0
rescue ZeroDivisionError => e
puts "除零错误: #{e.message}"
end
# 捕获多种异常
begin
# 可能出错的代码
data = JSON.parse(input)
data.fetch("required_key")
rescue JSON::ParserError => e
puts "JSON 解析错误: #{e.message}"
rescue KeyError => e
puts "键不存在: #{e.message}"
end
# 捕获所有异常(不推荐,应该明确捕获)
begin
risky_operation
rescue => e
puts "发生错误: #{e.class} - #{e.message}"
end
# 捕获 Exception(非常不推荐,会捕获系统信号)
begin
risky_operation
rescue Exception => e
# 这会捕获 Interrupt、SystemExit 等
end
12.1.3 异常对象
begin
1 / 0
rescue => e
e.class # => ZeroDivisionError
e.message # => "divided by 0"
e.backtrace # => 堆栈追踪数组
e.cause # => 原始异常(如果有)
end
12.2 ensure 与 else
12.2.1 ensure(确保执行)
# ensure 无论是否发生异常都会执行
def read_file(path)
file = File.open(path)
content = file.read
content
rescue Errno::ENOENT => e
puts "文件不存在: #{e.message}"
nil
rescue Errno::EACCES => e
puts "没有权限: #{e.message}"
nil
ensure
file&.close # 确保文件关闭
puts "资源已清理"
end
12.2.2 else(无异常时执行)
begin
result = calculate_something
rescue => e
puts "错误: #{e.message}"
else
puts "计算成功: #{result}" # 只在没有异常时执行
ensure
puts "清理工作"
end
# 执行顺序:
# 1. begin 块
# 2. 有异常 → rescue
# 3. 无异常 → else
# 4. 总是 → ensure
12.2.3 方法中的异常处理
# 方法本身就是 begin 块
def divide(a, b)
a / b
rescue ZeroDivisionError
puts "不能除以零"
nil
ensure
puts "计算完成"
end
# 也可以显式使用
def process(data)
begin
validate(data)
transform(data)
rescue ValidationError => e
log_error(e)
raise # 重新抛出
end
end
12.3 raise 与 retry
12.3.1 raise 抛出异常
# 抛出异常
raise "Something went wrong"
raise ArgumentError, "Invalid argument: #{arg}"
raise TypeError.new("Expected String")
# 重新抛出当前异常
begin
risky_operation
rescue => e
log_error(e)
raise # 重新抛出相同的异常
end
# 转换异常
begin
external_api_call
rescue HTTPError => e
raise ServiceError, "API 调用失败: #{e.message}"
end
12.3.2 retry 重试
# 基本重试
attempts = 0
begin
attempts += 1
puts "尝试第 #{attempts} 次..."
connect_to_database
rescue ConnectionError => e
if attempts < 3
sleep(1)
retry # 重试 begin 块
else
puts "连接失败,已重试 #{attempts} 次"
raise
end
end
# 带延迟的重试
def with_retry(max_attempts: 3, delay: 1)
attempts = 0
begin
attempts += 1
yield
rescue StandardError => e
if attempts < max_attempts
sleep(delay * attempts) # 指数退避
retry
else
raise
end
end
end
with_retry(max_attempts: 3, delay: 2) do
external_service.call
end
12.3.3 重试模式
# 指数退避重试
def with_exponential_backoff(max_attempts: 5)
attempts = 0
begin
attempts += 1
yield
rescue StandardError => e
if attempts < max_attempts
delay = 2 ** attempts + rand(0..1)
puts "重试 #{attempts}/#{max_attempts},等待 #{delay}s..."
sleep(delay)
retry
else
raise e
end
end
end
# 只重试特定异常
def with_selective_retry(max_attempts: 3, retry_on: [StandardError])
attempts = 0
begin
attempts += 1
yield
rescue *retry_on => e
if attempts < max_attempts
sleep(1)
retry
else
raise
end
end
end
with_selective_retry(retry_on: [Timeout::Error, ConnectionError]) do
api_call
end
12.4 自定义异常
12.4.1 定义异常类
# 基本自定义异常
class AppError < StandardError; end
class ValidationError < AppError
attr_reader :field, :value
def initialize(field, value, message = nil)
@field = field
@value = value
super(message || "Invalid value for #{field}: #{value}")
end
end
class NotFoundError < AppError
attr_reader :resource, :id
def initialize(resource, id)
@resource = resource
@id = id
super("#{resource} not found: #{id}")
end
end
# 使用
def find_user(id)
user = User.find_by(id: id)
raise NotFoundError.new("User", id) unless user
user
end
begin
find_user(999)
rescue NotFoundError => e
puts e.message # => "User not found: 999"
puts "Resource: #{e.resource}"
puts "ID: #{e.id}"
end
12.4.2 异常层次设计
# 应用级异常层次
module MyApp
class Error < StandardError; end
# 认证相关
class AuthenticationError < Error; end
class AuthorizationError < Error; end
class TokenExpiredError < AuthenticationError; end
# 数据相关
class ValidationError < Error
attr_reader :errors
def initialize(errors = {})
@errors = errors
super(errors.values.flatten.join(", "))
end
end
class RecordNotFoundError < Error
attr_reader :model, :id
def initialize(model, id)
@model = model
@id = id
super("#{model}##{id} not found")
end
end
# 服务相关
class ServiceError < Error; end
class ExternalServiceError < ServiceError
attr_reader :service, :response
def initialize(service, response = nil)
@service = service
@response = response
super("External service error: #{service}")
end
end
end
# 捕获特定或父类异常
begin
do_something
rescue MyApp::AuthenticationError => e
# 处理所有认证错误(包括 TokenExpiredError)
rescue MyApp::ValidationError => e
# 处理验证错误
puts e.errors.inspect
rescue MyApp::Error => e
# 处理所有应用错误
end
12.5 内核方法
12.5.1 raise 方法
# raise 是 Kernel 方法,可以在任何地方调用
def validate!(value)
raise ArgumentError, "Value cannot be nil" if value.nil?
raise ArgumentError, "Value must be positive" if value <= 0
true
end
# 条件 raise
def process(data)
raise "Data is empty" if data.empty?
raise TypeError unless data.is_a?(Array)
data.map(&:to_s)
end
12.5.2 fail 方法
# fail 和 raise 完全相同
fail "Error message"
fail ArgumentError, "Bad argument"
# 约定:fail 表示不应该恢复的错误,raise 表示可以 rescue 的异常
# 但实际上 Ruby 社区通常统一使用 raise
12.6 catch 与 throw
12.6.1 catch/throw 控制流
# catch/throw 不是异常处理,而是控制流机制
def find_value(matrix, target)
matrix.each_with_index do |row, i|
row.each_with_index do |val, j|
throw(:found, { value: val, row: i, col: j }) if val == target
end
end
nil
end
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = catch(:found) do
find_value(matrix, 5)
end
puts result # => { value: 5, row: 1, col: 1 }
12.6.2 catch/throw vs begin/rescue
# catch/throw 用于正常的控制流跳出
# begin/rescue 用于异常情况
# catch/throw 示例:深层嵌套跳出
def deep_search(data)
data.each do |key, value|
if value.is_a?(Hash)
deep_search(value)
elsif value == "target"
throw(:found, key)
end
end
end
data = {
a: { b: { c: "other" } },
d: { e: "target" }
}
result = catch(:found) { deep_search(data) }
puts result # => :d
12.7 实际业务场景
12.7.1 API 错误处理
module API
class ErrorHandler
ERROR_MAP = {
"RecordNotFound" => [404, "资源不存在"],
"ValidationError" => [422, "数据验证失败"],
"Unauthorized" => [401, "未授权"],
"Forbidden" => [403, "禁止访问"],
"RateLimited" => [429, "请求过于频繁"]
}.freeze
def self.handle(error)
status, message = ERROR_MAP.fetch(error.class.name, [500, "服务器内部错误"])
{
status: status,
error: {
type: error.class.name,
message: message,
details: error.respond_to?(:details) ? error.details : nil
}
}
end
end
end
# 在应用中使用
begin
user = User.find(params[:id])
render json: user
rescue RecordNotFound => e
render json: API::ErrorHandler.handle(e), status: 404
rescue ValidationError => e
render json: API::ErrorHandler.handle(e), status: 422
rescue => e
logger.error(e)
render json: { error: "Internal server error" }, status: 500
end
12.7.2 事务处理
class Transaction
def self.execute
result = nil
begin
ActiveRecord::Base.transaction do
result = yield
end
rescue ActiveRecord::RecordInvalid => e
log_error("Transaction failed: #{e.message}")
raise
rescue => e
log_error("Unexpected error: #{e.message}")
raise
end
result
end
end
Transaction.execute do
user = User.create!(name: "Alice")
order = Order.create!(user: user, total: 100)
Payment.process!(order)
end
12.7.3 资源清理
# 确保资源释放
def with_resource
resource = acquire_resource
yield resource
ensure
resource&.release
end
# 临时文件处理
require "tempfile"
def with_temp_file(content)
file = Tempfile.new(["output", ".txt"])
file.write(content)
file.rewind
yield file
ensure
file&.close
file&.unlink
end
with_temp_file("Hello") do |f|
puts f.read
end
12.8 动手练习
- 实现 Circuit Breaker
class CircuitBreaker
# 实现熔断器模式
# 当连续失败超过阈值时,停止调用
end
- 实现异常重试装饰器
def with_retry(max: 3, on: [StandardError], backoff: 1)
# 实现带退避的重试机制
end
- 实现异常聚合
class AggregateError < StandardError
# 收集多个异常并统一报告
end
12.9 本章小结
| 要点 | 说明 |
|---|---|
| begin/rescue | 基本异常捕获结构 |
| ensure | 无论是否异常都执行的清理代码 |
| else | 没有异常时执行的代码 |
| raise | 抛出异常 |
| retry | 重试 begin 块 |
| 自定义异常 | 继承 StandardError,设计清晰的异常层次 |
| catch/throw | 控制流机制,非异常处理 |
📖 扩展阅读
上一章:← 第 11 章:元编程 下一章:第 13 章:模块深入 →