文章目录

Ruby 异常处理:begin-rescue-end 块

发布于 2026-04-14 04:13:09 · 浏览 43 次 · 评论 0 条

Ruby 异常处理:begin-rescue-end 块

编写代码时,程序难免会遇到意外情况,例如文件不存在、网络中断或除以零等。如果不处理这些错误,程序会直接崩溃并停止运行。Ruby 提供了 begin-rescue-end 结构,专门用于捕获和处理这些异常,让程序在出错后也能继续执行或优雅地退出。


1. 认识异常处理的基本结构

最基础的异常处理由三部分组成:beginrescueendbegin 标记着可能出错的代码块的开始,rescue 负责捕获错误并处理,end 标记结束。

打开你的 Ruby 编辑器,输入以下代码来观察一个未经处理的错误:

puts 10 / 0

运行这段代码,程序会报错并终止,提示 ZeroDivisionError(除以零错误)。

现在,使用 begin-rescue-end 结构来包裹这段代码,防止程序崩溃:

begin
  puts 10 / 0
rescue
  puts "捕获到一个错误,程序继续运行。"
end

puts "这行代码依然会被执行。"

执行上述代码,你会发现程序不再报错退出,而是输出了提示信息,并继续执行了后面的打印语句。


2. 捕获特定类型的异常

在实际开发中,盲目捕获所有错误是不专业的做法。我们通常需要知道具体发生了什么错误,并针对不同的错误采取不同的措施。Ruby 允许我们在 rescue 后指定具体的异常类。

修改代码,专门处理 ZeroDivisionError

begin
  puts 10 / 0
rescue ZeroDivisionError
  puts "除数不能为零,请检查计算逻辑。"
rescue TypeError
  puts "类型错误,确保输入的是数字。"
end

如果不确定具体的异常类名,运行一段会出错的代码,Ruby 会在报错信息中告诉你异常的名称(如 ZeroDivisionError),然后你就可以将其填入 rescue 语句中。


3. 获取异常的详细信息

当错误发生时,我们往往需要知道错误的详细描述,以便于调试。可以在 rescue 后面跟一个变量(通常命名为 e),Ruby 会自动将异常对象赋值给这个变量。

编写以下代码来输出错误详情:

begin
  # 这里故意写一个未定义的变量
  puts undefined_variable
rescue => e
  puts "发生异常:#{e.class}"
  puts "错误信息:#{e.message}"
end

注意,rescue => e 这种写法等同于 rescue StandardError => e,它会捕获大部分常规错误,但不会捕获像内存不足这样的严重系统错误。


4. 使用 else 和 ensure

完整的异常处理结构还包含 elseensure 两个可选部分:

  • else:如果没有发生任何异常,则执行 else 中的代码。
  • ensure:无论是否发生异常,ensure 中的代码永远都会被执行。这通常用于清理资源,如关闭文件或数据库连接。

输入并分析以下示例代码:

begin
  puts "正在执行风险操作..."
  puts 10 / 2  # 这里没有错误
rescue ZeroDivisionError
  puts "捕获到除零错误。"
else
  puts "风险操作成功完成,没有错误。"
ensure
  puts "清理现场,释放资源(无论成败都必须做)。"
end

观察输出结果,由于没有发生错误,rescue 被跳过,执行了 else,最后执行了 ensure。如果把 10 / 2 改为 10 / 0else 将会被跳过,但 ensure 依然会执行。

为了更直观地理解这个流程,请参考以下执行逻辑图:

graph TD A["开始 begin"] --> B["执行 begin 块代码"] B --> C{"是否发生异常?"} C -- 是 --> D["执行 rescue 块"] C -- 否 --> E["执行 else 块"] D --> F["执行 ensure 块"] E --> F F --> G["结束 end"]

5. 主动抛出异常

除了被动等待 Ruby 报错,你还可以在代码中主动抛出异常。这在参数校验时非常有用。例如,如果用户输入的年龄是负数,这不符合逻辑,我们就应该主动报错。

使用 raise 关键字来抛出异常:

def set_age(age)
  if age < 0
    raise ArgumentError, "年龄不能是负数"
  end
  puts "年龄设置为:#{age}"
end

begin
  set_age(-5)
rescue ArgumentError => e
  puts "设置失败:#{e.message}"
end

运行后,程序会输出“设置失败:年龄不能是负数”。


6. 重试机制

有时遇到的错误是临时的,比如网络瞬间抖动。在这种情况下,我们可以使用 retry 关键字在 rescue 块中重新尝试执行 begin 块中的代码。

注意retry 必须放在 rescue 块内。为了防止死循环(例如网络一直不通),必须配合计数器使用。

编写一个带有重试逻辑的脚本:

attempts = 0
max_attempts = 3

begin
  attempts += 1
  puts "第 #{attempts} 次尝试连接服务器..."

  # 模拟随机失败,假设 rand > 0.5 才能成功
  raise "连接超时" if rand < 0.8

  puts "连接成功!"

rescue => e
  puts "尝试失败:#{e.message}"
  if attempts < max_attempts
    puts "准备重试..."
    retry  # 跳回 begin 开始处
  else
    puts "已达到最大重试次数,放弃连接。"
  end
end

运行多次该代码,观察它在失败次数未达到 3 次时如何自动重试,直到成功或次数耗尽。


7. 常见异常类速查

Ruby 内置了许多异常类,以下列出了开发中最常遇到的几种:

异常类 含义 常见触发场景
StandardError 标准异常 大多数常规错误的父类
RuntimeError 运行时错误 调用 raise 但未指定类型时默认抛出
ArgumentError 参数错误 传递了错误数量或类型的参数
TypeError 类型错误 对字符串调用数学运算等
ZeroDivisionError 除零错误 除数为 0
NoMethodError 方法未定义 调用了对象不存在的方法
LoadError 加载错误 使用 require 加载不存在的文件

8. 嵌套异常处理

在复杂的应用中,可能需要在一个异常处理结构内部再套一层。例如,在清理资源(ensure)时,清理操作本身也可能失败。

编写嵌套结构的代码示例:

begin
  puts "外层:开始操作"

  begin
    puts "内层:执行高风险任务"
    raise "内层任务失败"
  rescue => e
    puts "内层:捕获到错误 - #{e.message}"
  end

  puts "外层:操作结束"

rescue => e
  puts "外层:这不应该被触发,除非内层崩溃"
end

执行后你会发现,内层的 rescue 已经处理了错误,外层的 rescue 不会被触发。只有当内层无法处理的异常向上冒泡时,外层才会捕获。

掌握了 begin-rescue-end 块的用法,你就能编写出健壮、可靠且易于维护的 Ruby 程序,从容应对各种运行时突发状况。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文