在 Ruby 中,异常(Exception)是一种用于处理运行时错误的机制。当程序遇到无法正常执行的情况时,会抛出异常(raise an exception),如果没有被捕获(rescued),程序将终止并显示错误信息。

本文将详细介绍 Ruby 的异常机制,包括异常类层级、begin-rescue 结构、自定义异常、确保执行的 ensure 以及异常传播方式等。


📌 目录

  1. 🔹 Ruby 异常概述
  2. 🔹 Ruby 异常类层级
  3. 🔹 捕获异常 (begin-rescue)
  4. 🔹 ensure 代码块(无论异常是否发生都执行)
  5. 🔹 else 代码块(仅在没有异常时执行)
  6. 🔹 raise 关键字(抛出异常)
  7. 🔹 自定义异常
  8. 🔹 retry 关键字(重新执行代码)
  9. 🔹 throwcatch(高级异常处理)
  10. 🔹 参考资料

🔹 Ruby 异常概述

在 Ruby 中,所有异常都是 Exception 类的子类。当发生错误时,Ruby 会创建一个异常对象并沿着调用栈传播,直到被捕获或终止程序。

示例:没有异常处理的代码

puts 10 / 0  # ZeroDivisionError: divided by 0

上面的代码会引发 ZeroDivisionError 并导致程序崩溃。因此,我们需要异常处理来防止程序意外终止。


🔹 Ruby 异常类层级

Ruby 提供了许多异常类,每种异常类型代表不同的错误情况。

部分常见的异常类层级如下(继承自 Exception):

Exception  
 ├── NoMemoryError  
 ├── ScriptError  
 │   ├── LoadError  
 │   ├── NotImplementedError  
 │   ├── SyntaxError  
 ├── SignalException  
 │   ├── Interrupt  
 ├── StandardError(大多数异常继承自它)  
 │   ├── ArgumentError  
 │   ├── IOError  
 │   ├── IndexError  
 │   │   ├── KeyError  
 │   ├── NameError  
 │   │   ├── NoMethodError  
 │   ├── RuntimeError  
 │   ├── SystemCallError  
 │   ├── TypeError  
 │   ├── ZeroDivisionError  
 ├── SystemExit  
 ├── Fatal  

注意:Ruby 的大多数常见异常都继承自 StandardError


🔹 捕获异常 (begin-rescue)

在 Ruby 中,我们使用 begin-rescue 结构来捕获异常,以避免程序崩溃。

基本使用

begin
  puts 10 / 0
rescue ZeroDivisionError
  puts "错误: 除数不能为零!"
end

输出:

错误: 除数不能为零!

10 / 0 发生 ZeroDivisionError 时,rescue 代码块会捕获异常并执行相应代码。


捕获多个异常

可以在 rescue 中捕获多个异常类型,使用 rescue A, B 形式:

begin
  File.open("不存在的文件.txt")
rescue ZeroDivisionError, Errno::ENOENT => e
  puts "发生错误: #{e.class} - #{e.message}"
end

输出:

发生错误: Errno::ENOENT - No such file or directory


🔹 ensure 代码块(无论异常是否发生都执行)

无论是否发生异常,ensure 代码块都会执行,适用于释放资源(如关闭文件、断开数据库连接等)。

begin
  puts 10 / 0
rescue ZeroDivisionError
  puts "发生错误: 除数不能为零!"
ensure
  puts "这个代码块无论如何都会执行"
end

输出:

发生错误: 除数不能为零!
这个代码块无论如何都会执行


🔹 else 代码块(仅在没有异常时执行)

else 代码块用于当 begin 代码块 没有发生异常时 执行:

begin
  puts "正常执行的代码"
rescue
  puts "捕获到异常"
else
  puts "没有异常,执行此代码"
end

输出:

正常执行的代码
没有异常,执行此代码


🔹 raise 关键字(抛出异常)

可以使用 raise 主动抛出异常:

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

begin
  puts divide(10, 0)
rescue ArgumentError => e
  puts "捕获到异常: #{e.message}"
end

输出:

捕获到异常: 除数不能为零


🔹 自定义异常

可以继承 StandardError 来创建自定义异常:

class MyCustomError < StandardError; end

def test_error
  raise MyCustomError, "这是一个自定义异常"
end

begin
  test_error
rescue MyCustomError => e
  puts "捕获到自定义异常: #{e.message}"
end

输出:

捕获到自定义异常: 这是一个自定义异常


🔹 retry 关键字(重新执行代码)

retry 用于重新执行 begin 代码块,但需谨慎使用,否则可能导致无限循环。

attempt = 0
begin
  attempt += 1
  puts "尝试执行: 第 #{attempt} 次"
  raise "故意抛出异常" if attempt < 3
rescue
  retry if attempt < 3
end

输出:

尝试执行: 第 1 次
尝试执行: 第 2 次
尝试执行: 第 3 次


🔹 throwcatch(高级异常处理)

raise-rescue 不同,catch-throw 适用于非错误情况下的流程控制。

catch(:done) do
  (1..5).each do |i|
    throw :done if i == 3
    puts i
  end
end

输出:

1
2

i == 3 时,throw :done 直接终止 catch(:done) 代码块的执行。


🔹 参考资料