Ruby 异常处理
异常处理是编写健壮程序的重要组成部分。在程序运行过程中,可能会遇到各种预期外的情况,如文件不存在、网络连接失败、无效输入等。Ruby提供了完善的异常处理机制,帮助开发者优雅地处理这些情况,提高程序的稳定性和用户体验。本章将详细介绍Ruby中异常处理的各种方法和最佳实践。
🎯 异常基础
什么是异常
异常是在程序执行过程中发生的不正常情况,它会中断程序的正常执行流程。Ruby中的异常是对象,继承自[Exception](file:///D:/Workspace/Coding/VueProjects/tutorials-web/docs/ruby/../../../../../../Ruby30-x64/lib/ruby/3.0.0/exception.rb#L1-L338)类或其子类。
ruby
# 基本异常处理
begin
# 可能出错的代码
result = 10 / 0
rescue
# 处理异常
puts "发生错误了!"
end
# 更具体的异常处理
begin
result = 10 / 0
rescue ZeroDivisionError
puts "不能除以零!"
end
# 捕获异常对象
begin
result = 10 / 0
rescue ZeroDivisionError => e
puts "错误类型: #{e.class}"
puts "错误信息: #{e.message}"
puts "错误追踪: #{e.backtrace}"
endRuby异常层次结构
ruby
# Ruby异常类层次结构(部分)
# Exception
# ├── NoMemoryError
# ├── ScriptError
# │ ├── LoadError
# │ ├── NotImplementedError
# │ └── SyntaxError
# ├── SignalException
# │ └── Interrupt
# ├── StandardError # 最常用的异常基类
# │ ├── ArgumentError
# │ ├── IOError
# │ ├── IndexError
# │ ├── LocalJumpError
# │ ├── NameError
# │ │ └── NoMethodError
# │ ├── RangeError
# │ ├── RegexpError
# │ ├── RuntimeError
# │ ├── SecurityError
# │ ├── StandardError
# │ ├── StopIteration
# │ ├── SyntaxError
# │ ├── SystemCallError
# │ ├── SystemStackError
# │ ├── ThreadError
# │ ├── TypeError
# │ └── ZeroDivisionError
# ├── SystemExit
# └── fatal
# 常见异常类型示例
begin
# ArgumentError - 参数错误
def greet(name)
raise ArgumentError, "名字不能为空" if name.nil? || name.empty?
puts "你好, #{name}!"
end
greet("") # 会抛出ArgumentError
rescue ArgumentError => e
puts "参数错误: #{e.message}"
end
begin
# TypeError - 类型错误
result = "hello" + 5 # 会抛出TypeError
rescue TypeError => e
puts "类型错误: #{e.message}"
end
begin
# NoMethodError - 方法不存在错误
obj = "hello"
obj.nonexistent_method # 会抛出NoMethodError
rescue NoMethodError => e
puts "方法错误: #{e.message}"
end🚨 异常处理语法
基本异常处理结构
ruby
# 完整的异常处理结构
begin
# 可能出错的代码
puts "开始执行"
raise "这是一个自定义错误"
puts "这行不会执行"
rescue StandardError => e
# 处理标准异常
puts "捕获到异常: #{e.message}"
rescue Exception => e
# 处理所有异常(不推荐)
puts "捕获到所有异常: #{e.message}"
else
# 没有异常时执行
puts "没有发生异常"
ensure
# 无论是否有异常都会执行
puts "清理工作"
end多重异常处理
ruby
# 处理多种异常类型
begin
# 可能抛出不同异常的代码
file = File.open("nonexistent.txt")
data = file.read
result = 10 / 0
rescue Errno::ENOENT => e
# 文件不存在错误
puts "文件不存在: #{e.message}"
rescue ZeroDivisionError => e
# 除零错误
puts "除零错误: #{e.message}"
rescue IOError => e
# IO错误
puts "IO错误: #{e.message}"
rescue StandardError => e
# 其他标准错误
puts "其他错误: #{e.message}"
end
# 使用数组处理多种异常
begin
# 可能出错的代码
raise ArgumentError, "参数错误"
rescue [ArgumentError, TypeError, NameError] => e
puts "捕获到类型相关的错误: #{e.class} - #{e.message}"
end异常重新抛出和嵌套
ruby
# 重新抛出异常
begin
begin
raise "内部错误"
rescue => e
puts "内部捕获: #{e.message}"
# 重新抛出异常
raise
end
rescue => e
puts "外部捕获: #{e.message}"
end
# 抛出不同异常
begin
begin
raise "原始错误"
rescue => e
puts "捕获原始错误: #{e.message}"
# 抛出新异常
raise RuntimeError, "转换后的错误"
end
rescue RuntimeError => e
puts "捕获转换后错误: #{e.message}"
end
# 异常链接(Ruby 2.1+)
begin
begin
raise "原始错误"
rescue => e
raise "新错误", cause: e
end
rescue => e
puts "新错误: #{e.message}"
puts "原始错误: #{e.cause.message}" if e.cause
end🏗️ 自定义异常
创建自定义异常类
ruby
# 基本自定义异常
class CustomError < StandardError
end
# 带构造函数的自定义异常
class ValidationError < StandardError
attr_reader :field, :value
def initialize(field, value, message = nil)
@field = field
@value = value
message ||= "字段 '#{field}' 的值 '#{value}' 无效"
super(message)
end
end
# 使用自定义异常
def validate_age(age)
raise ValidationError.new("age", age) if age < 0 || age > 150
true
end
begin
validate_age(-5)
rescue ValidationError => e
puts "验证错误: #{e.message}"
puts "字段: #{e.field}, 值: #{e.value}"
end
# 层次化的自定义异常
class ApplicationError < StandardError
end
class DatabaseError < ApplicationError
attr_reader :query
def initialize(query, message = nil)
@query = query
message ||= "数据库查询失败: #{query}"
super(message)
end
end
class NetworkError < ApplicationError
attr_reader :url
def initialize(url, message = nil)
@url = url
message ||= "网络请求失败: #{url}"
super(message)
end
end
# 使用层次化异常
def fetch_data(url)
# 模拟网络请求
if url.include?("error")
raise NetworkError.new(url, "连接超时")
end
"数据内容"
end
begin
data = fetch_data("http://example.com/error")
rescue ApplicationError => e
puts "应用错误: #{e.message}"
case e
when NetworkError
puts "网络错误,URL: #{e.url}"
when DatabaseError
puts "数据库错误,查询: #{e.query}"
end
end🔧 异常相关方法
抛出异常
ruby
# 使用raise抛出异常
raise "这是一个错误消息"
raise RuntimeError, "运行时错误"
raise RuntimeError.new("自定义运行时错误")
# 使用fail抛出异常(与raise相同)
fail "这也是一个错误消息"
fail ArgumentError, "参数错误"
# 抛出特定异常对象
exception = ArgumentError.new("无效参数")
raise exception
# 重新抛出当前异常
begin
raise "原始错误"
rescue
puts "处理中..."
raise # 重新抛出
end异常检查和处理
ruby
# 检查是否可以捕获异常
def safe_operation
catch(:done) do
# 可能抛出异常的操作
throw :done, "操作完成"
end
rescue => e
"发生错误: #{e.message}"
end
# 使用retry重试
attempt = 0
begin
attempt += 1
puts "尝试第#{attempt}次"
raise "模拟错误" if attempt < 3
puts "成功了!"
rescue => e
if attempt < 3
puts "重试中..."
retry
else
puts "最终失败: #{e.message}"
end
end异常信息获取
ruby
begin
raise "自定义错误消息"
rescue => e
puts "异常类: #{e.class}"
puts "异常消息: #{e.message}"
puts "异常追踪:"
e.backtrace.each { |line| puts " #{line}" }
# 获取详细的异常信息
puts "异常详情: #{e.inspect}"
# 获取原因(Ruby 2.1+)
puts "异常原因: #{e.cause}" if e.cause
end🎯 实用异常处理示例
文件操作异常处理
ruby
class FileProcessor
# 安全读取文件
def self.safe_read(filename)
File.open(filename, "r") do |file|
file.read
end
rescue Errno::ENOENT
puts "错误: 文件 '#{filename}' 不存在"
nil
rescue Errno::EACCES
puts "错误: 没有权限读取文件 '#{filename}'"
nil
rescue => e
puts "读取文件时发生未知错误: #{e.message}"
nil
end
# 安全写入文件
def self.safe_write(filename, content)
File.open(filename, "w") do |file|
file.write(content)
end
true
rescue Errno::EACCES
puts "错误: 没有权限写入文件 '#{filename}'"
false
rescue => e
puts "写入文件时发生未知错误: #{e.message}"
false
end
# 处理文件并返回结果
def self.process_file(filename)
content = safe_read(filename)
return nil unless content
# 处理内容
processed_content = content.upcase
# 写入新文件
output_filename = "#{filename}.processed"
safe_write(output_filename, processed_content)
output_filename
end
end
# 使用文件处理器
# result = FileProcessor.process_file("example.txt")
# if result
# puts "文件处理完成: #{result}"
# else
# puts "文件处理失败"
# end网络请求异常处理
ruby
require 'net/http'
require 'uri'
class NetworkClient
class NetworkError < StandardError
attr_reader :url, :status_code
def initialize(url, status_code = nil, message = nil)
@url = url
@status_code = status_code
message ||= status_code ?
"HTTP #{status_code} 错误: #{url}" :
"网络请求失败: #{url}"
super(message)
end
end
class TimeoutError < NetworkError
def initialize(url, message = nil)
super(url, nil, message || "请求超时: #{url}")
end
end
# 发送GET请求
def self.get(url, timeout: 5)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.open_timeout = timeout
http.read_timeout = timeout
response = http.get(uri.request_uri)
case response
when Net::HTTPSuccess
response.body
else
raise NetworkError.new(url, response.code.to_i)
end
rescue Net::OpenTimeout, Net::ReadTimeout
raise TimeoutError.new(url)
rescue SocketError
raise NetworkError.new(url, nil, "DNS解析失败: #{url}")
rescue => e
raise NetworkError.new(url, nil, "未知网络错误: #{e.message}")
end
# 安全的GET请求
def self.safe_get(url, timeout: 5)
get(url, timeout: timeout)
rescue NetworkError => e
puts "网络错误: #{e.message}"
nil
end
end
# 使用网络客户端
# response = NetworkClient.safe_get("https://api.github.com/users/octocat")
# if response
# puts "响应内容: #{response[0..100]}..."
# else
# puts "请求失败"
# end数据验证异常处理
ruby
class DataValidator
class ValidationError < StandardError
attr_reader :field, :value, :constraint
def initialize(field, value, constraint, message = nil)
@field = field
@value = value
@constraint = constraint
message ||= "字段 '#{field}' 的值 '#{value}' 不满足约束 '#{constraint}'"
super(message)
end
end
# 验证必填字段
def self.validate_required(data, field)
value = data[field]
raise ValidationError.new(field, value, "required",
"字段 '#{field}' 是必填的") if value.nil? || value.to_s.empty?
value
end
# 验证数字范围
def self.validate_range(data, field, min, max)
value = data[field]
raise ValidationError.new(field, value, "range(#{min}-#{max})",
"字段 '#{field}' 必须在 #{min} 到 #{max} 之间") unless value.is_a?(Numeric) && value.between?(min, max)
value
end
# 验证邮箱格式
def self.validate_email(data, field)
value = data[field]
email_pattern = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
raise ValidationError.new(field, value, "email",
"字段 '#{field}' 必须是有效的邮箱地址") unless value.to_s.match?(email_pattern)
value
end
# 批量验证
def self.validate(data, rules)
errors = []
rules.each do |field, rule|
begin
case rule[:type]
when :required
validate_required(data, field)
when :range
validate_range(data, field, rule[:min], rule[:max])
when :email
validate_email(data, field)
end
rescue ValidationError => e
errors << e
end
end
unless errors.empty?
raise ValidationError.new("multiple", nil, "multiple",
"验证失败,共 #{errors.length} 个错误")
end
true
end
end
# 使用数据验证器
# user_data = {
# name: "张三",
# age: 25,
# email: "zhangsan@example.com"
# }
#
# validation_rules = {
# name: { type: :required },
# age: { type: :range, min: 0, max: 150 },
# email: { type: :email }
# }
#
# begin
# DataValidator.validate(user_data, validation_rules)
# puts "数据验证通过"
# rescue DataValidator::ValidationError => e
# puts "验证错误: #{e.message}"
# end📊 异常处理性能优化
异常处理性能考虑
ruby
# 避免使用异常控制正常流程
# 不推荐:使用异常控制流程
def find_user_bad(id)
begin
User.find(id)
rescue ActiveRecord::RecordNotFound
nil
end
end
# 推荐:使用正常方法
def find_user_good(id)
User.find_by(id: id)
end
# 预检查避免异常
def safe_divide(dividend, divisor)
# 预检查
return nil if divisor == 0
dividend / divisor
end
# 使用rescue修饰符处理简单情况
def safe_parse_integer(string)
Integer(string)
rescue ArgumentError
nil
end
# 批量操作中的异常处理
def process_items(items)
results = []
errors = []
items.each_with_index do |item, index|
begin
result = process_item(item)
results << { index: index, item: item, result: result }
rescue => e
errors << { index: index, item: item, error: e.message }
end
end
{ results: results, errors: errors }
end
def process_item(item)
# 处理单个项目
item.upcase
end异常日志记录
ruby
require 'logger'
class ExceptionLogger
def initialize(log_file = "error.log")
@logger = Logger.new(log_file)
@logger.level = Logger::ERROR
end
# 记录异常
def log_exception(e, context = {})
error_info = {
exception_class: e.class.name,
message: e.message,
backtrace: e.backtrace&.first(10),
context: context,
timestamp: Time.now
}
@logger.error(error_info.to_json)
end
# 带上下文的异常处理
def handle_with_context(context = {})
yield
rescue => e
log_exception(e, context)
raise # 重新抛出异常
end
end
# 使用异常日志记录器
# logger = ExceptionLogger.new("app_errors.log")
#
# logger.handle_with_context(user_id: 123, action: "update_profile") do
# # 可能出错的操作
# update_user_profile(user_id: 123, data: profile_data)
# end🛡️ 异常处理最佳实践
1. 异常处理原则
ruby
# 原则1: 具体化异常处理
# 不好的做法
begin
# 多种可能的错误
file = File.open("data.txt")
data = JSON.parse(file.read)
result = 10 / data["divisor"]
rescue => e
puts "出错了: #{e.message}"
end
# 好的做法
begin
file = File.open("data.txt")
data = JSON.parse(file.read)
result = 10 / data["divisor"]
rescue Errno::ENOENT => e
puts "文件不存在: #{e.message}"
rescue JSON::ParserError => e
puts "JSON解析错误: #{e.message}"
rescue ZeroDivisionError => e
puts "除零错误: #{e.message}"
rescue => e
puts "未知错误: #{e.message}"
end
# 原则2: 不要忽略异常
# 不好的做法
begin
risky_operation
rescue
# 什么都不做
end
# 好的做法
begin
risky_operation
rescue => e
log_error(e)
# 或者重新抛出
raise
end
# 原则3: 清理资源
# 好的做法
file = nil
begin
file = File.open("data.txt")
process_file(file)
ensure
file&.close # 确保文件被关闭
end
# 更好的做法
File.open("data.txt") do |file|
process_file(file)
end # 自动关闭文件2. 自定义异常设计
ruby
# 设计良好的异常层次结构
module MyApp
class Error < StandardError
attr_reader :context
def initialize(message = nil, context = {})
@context = context
super(message)
end
end
class ValidationError < Error
attr_reader :field, :value
def initialize(field, value, message = nil, context = {})
@field = field
@value = value
message ||= "字段 '#{field}' 验证失败"
super(message, context)
end
end
class BusinessLogicError < Error
attr_reader :code
def initialize(code, message = nil, context = {})
@code = code
message ||= "业务逻辑错误: #{code}"
super(message, context)
end
end
class ExternalServiceError < Error
attr_reader :service, :http_status
def initialize(service, http_status = nil, message = nil, context = {})
@service = service
@http_status = http_status
message ||= http_status ?
"#{service} 服务错误 (HTTP #{http_status})" :
"#{service} 服务错误"
super(message, context)
end
end
end
# 使用自定义异常
begin
raise MyApp::ValidationError.new("email", "invalid-email",
"邮箱格式不正确", { user_id: 123 })
rescue MyApp::ValidationError => e
puts "验证错误: #{e.message}"
puts "字段: #{e.field}, 值: #{e.value}"
puts "用户ID: #{e.context[:user_id]}"
end3. 异常处理模式
ruby
# 重试模式
class RetryableOperation
def self.execute(max_retries: 3, base_delay: 1)
retries = 0
begin
yield
rescue => e
retries += 1
if retries <= max_retries
delay = base_delay * (2 ** (retries - 1)) # 指数退避
puts "第#{retries}次重试,等待#{delay}秒..."
sleep(delay)
retry
else
puts "重试#{max_retries}次后仍然失败: #{e.message}"
raise
end
end
end
end
# 使用重试模式
# RetryableOperation.execute(max_retries: 3) do
# # 可能失败的操作
# unstable_network_call
# end
# 电路断路器模式
class CircuitBreaker
def initialize(threshold: 5, timeout: 60)
@threshold = threshold
@timeout = timeout
@failure_count = 0
@last_failure_time = nil
@open = false
end
def call
return yield if closed?
if timeout_expired?
@open = false
@failure_count = 0
return yield
end
raise "电路断路器已打开"
end
def record_failure
@failure_count += 1
@last_failure_time = Time.now
@open = @failure_count >= @threshold
end
private
def closed?
!@open
end
def timeout_expired?
@last_failure_time && (Time.now - @last_failure_time) > @timeout
end
end
# 使用电路断路器
# breaker = CircuitBreaker.new(threshold: 3, timeout: 30)
#
# begin
# breaker.call do
# external_service_call
# end
# rescue => e
# breaker.record_failure
# raise
# end4. 实际应用示例
ruby
# Web应用控制器异常处理
class ApplicationController
def handle_request
begin
# 处理请求
process_request
rescue ValidationError => e
render json: { error: "验证错误", details: e.message }, status: 400
rescue RecordNotFound => e
render json: { error: "资源未找到", details: e.message }, status: 404
rescue BusinessLogicError => e
render json: { error: "业务错误", code: e.code, details: e.message }, status: 422
rescue ExternalServiceError => e
Rails.logger.error "外部服务错误: #{e.message}"
render json: { error: "服务暂时不可用" }, status: 503
rescue => e
Rails.logger.error "未处理的异常: #{e.class} - #{e.message}"
Rails.logger.error e.backtrace.join("\n")
render json: { error: "内部服务器错误" }, status: 500
end
end
private
def process_request
# 实际的请求处理逻辑
end
end
# 数据库操作异常处理
class DatabaseManager
def self.with_transaction
ActiveRecord::Base.transaction do
yield
end
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error "记录验证失败: #{e.message}"
raise ValidationError.new("database", nil, "validation", e.message)
rescue ActiveRecord::RecordNotFound => e
Rails.logger.error "记录未找到: #{e.message}"
raise RecordNotFound.new(e.message)
rescue ActiveRecord::StatementInvalid => e
Rails.logger.error "SQL语句错误: #{e.message}"
raise BusinessLogicError.new("DB001", "数据库操作失败")
end
end📚 下一步学习
掌握了Ruby异常处理后,建议继续学习:
- Ruby 面向对象 - 深入理解面向对象编程
- Ruby 数据库访问 - 学习数据库操作
- Ruby 网络编程 - 了解Socket编程
- Ruby 多线程 - 掌握并发编程
继续您的Ruby学习之旅吧!