Ruby 异常

在 Ruby 中,异常(Exception) 是处理程序运行时错误(如文件不存在、除以零等)的机制。Ruby 提供了强大的异常处理功能,通过 beginrescueensureraise 等关键字实现。以下是对 Ruby 异常的中文讲解,涵盖异常的创建、捕获、处理及常见用法,力求简洁清晰。


1. 什么是异常?

异常是程序运行时发生的错误或意外情况,会中断正常执行流程。Ruby 使用异常对象(继承自 Exception 类)表示错误,常见子类包括 StandardError(默认捕获的异常基类)、RuntimeErrorArgumentErrorErrno::ENOENT(文件不存在)等。


2. 异常处理结构

Ruby 使用 beginrescueensure 结构捕获和处理异常。

基本语法

begin
  # 可能引发异常的代码
rescue [异常类] => 变量
  # 处理异常
else
  # 未发生异常时执行
ensure
  # 无论是否发生异常都执行
end

示例

begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "错误:#{e.message}"  # 输出:错误:divided by 0
ensure
  puts "清理工作"
end
# 输出:
# 错误:divided by 0
# 清理工作

说明

  • rescue:捕获指定异常类(如 ZeroDivisionError)。
  • => e:将异常对象赋值给变量 e,可访问 e.message(错误信息)或 e.backtrace(调用栈)。
  • else:无异常时执行(可选)。
  • ensure:无论是否有异常都执行(可选,常用于清理资源)。

3. 抛出异常

使用 raise(或 fail)手动抛出异常。

基本用法

def divide(a, b)
  raise ArgumentError, "除数不能为零" if b == 0
  a / b
end

begin
  divide(10, 0)
rescue ArgumentError => e
  puts e.message  # 输出:除数不能为零
end

简写

  • 不指定异常类,默认抛出 RuntimeError
  raise "这是一个错误"  # 等价于 raise RuntimeError, "这是一个错误"
  • 指定异常类:
  raise ArgumentError.new("无效参数")

4. 捕获多种异常

可以指定多个异常类,或省略异常类捕获所有 StandardError 子类。

begin
  File.open("nonexistent.txt", "r")
rescue Errno::ENOENT
  puts "文件不存在"
rescue Errno::EACCES
  puts "无权限访问"
rescue StandardError => e
  puts "其他错误:#{e.message}"
end

说明

  • 按顺序匹配异常类,优先捕获更具体的异常。
  • 不指定异常类时,默认捕获 StandardError 及其子类。

5. 异常类层次结构

Ruby 的异常类继承自 Exception,常见子类包括:

  • StandardError:大多数标准异常的基类(如 RuntimeError, ArgumentError, ZeroDivisionError)。
  • Errno::*:系统相关错误(如 Errno::ENOENT 表示文件不存在)。
  • NoMethodError:调用不存在的方法。
  • TypeError:类型错误。
  • Exception:所有异常的根类(不建议直接捕获,以免捕获严重错误如 NoMemoryError)。

推荐:通常只捕获 StandardError 或其子类,避免捕获 Exception


6. 自定义异常

可以定义自定义异常类,继承 StandardError 或其他异常类。

class MyCustomError < StandardError
end

def check_positive(num)
  raise MyCustomError, "必须是正数" if num <= 0
  num
end

begin
  check_positive(-5)
rescue MyCustomError => e
  puts e.message  # 输出:必须是正数
end

7. 重试(retry)

rescue 块中使用 retry 可以重试 begin 块的代码。

attempts = 0
begin
  attempts += 1
  raise "失败" if attempts < 3
  puts "成功"
rescue
  puts "第 #{attempts} 次尝试失败"
  retry if attempts < 3
end
# 输出:
# 第 1 次尝试失败
# 第 2 次尝试失败
# 成功

注意:谨慎使用 retry,避免无限循环。


8. 异常的传播

如果异常未被捕获,会沿调用栈向上传播,最终终止程序。

def risky_method
  raise "出错了"
end

begin
  risky_method
rescue => e
  puts e.message  # 输出:出错了
end

9. 注意事项

  • 只捕获必要的异常:避免捕获所有异常(rescue Exception),以免隐藏严重问题。
  # 不推荐
  begin
    1 / 0
  rescue Exception
    puts "捕获所有异常"
  end
  • 清理资源:使用 ensure 确保文件、连接等资源被正确关闭。
  file = nil
  begin
    file = File.open("example.txt", "r")
    # 操作文件
  rescue Errno::ENOENT
    puts "文件不存在"
  ensure
    file.close if file
  end
  • 块方式优先:使用 File.open 等带块的方法,自动管理资源。
  • 异常信息:使用 e.messagee.backtrace 获取详细信息。
  begin
    raise "错误"
  rescue => e
    puts e.backtrace.join("\n")  # 输出调用栈
  end
  • 性能:异常处理有开销,避免在正常流程中频繁使用 raiserescue

10. 示例:综合应用

module FileProcessor
  class FileNotFoundError < StandardError; end

  def self.process_file(filename)
    raise FileNotFoundError, "文件 #{filename} 不存在" unless File.exist?(filename)
    File.open(filename, "r") do |file|
      file.each_line { |line| puts line }
    end
  end
end

begin
  FileProcessor.process_file("nonexistent.txt")
rescue FileProcessor::FileNotFoundError => e
  puts "错误:#{e.message}"
rescue StandardError => e
  puts "其他错误:#{e.message}"
ensure
  puts "处理完成"
end
# 输出:
# 错误:文件 nonexistent.txt 不存在
# 处理完成

11. 总结

Ruby 的异常处理通过 beginrescueensureraise 实现,提供了灵活的错误管理机制。捕获特定异常(如 StandardError 子类)、使用自定义异常和 retry 可增强代码健壮性。结合块方式和 ensure,可以安全管理资源。注意避免过度捕获异常,合理使用异常信息,确保代码清晰高效。

如果你有具体问题或需要更详细的示例,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注