Ruby 异常
Ruby 异常处理
Section titled “Ruby 异常处理”异常是 Ruby 处理程序执行期间发生的错误或意外事件的方式。如果未处理,异常通常会导致程序终止。适当的异常处理允许程序优雅地响应错误、记录问题、重试操作或采取替代措施。
Ruby 使用 begin、rescue、else、ensure 和 end 关键字来管理异常。
基本 begin/rescue 语法
Section titled “基本 begin/rescue 语法”begin # 可能引发异常的代码(“受保护”块) # ...rescue StandardError => variable_for_exception_object # 处理特定 StandardError 或其子类的代码 # 'variable_for_exception_object' 现在持有异常对象 # ...rescue AnotherExceptionType => e # 处理 AnotherExceptionType 的代码 # ...rescue # 处理任何继承自 StandardError 的异常 # (如果未指定特定类型,则默认为 StandardError) # 通常最好捕获(rescue)特定的异常类型。else # 仅在 'begin' 块中未引发任何异常时执行的代码 # ...ensure # 无论是否发生异常,始终执行的代码。 # 通常用于清理(例如,关闭文件、释放资源)。 # ...endbegin...rescue块内的代码被执行。- 如果发生异常,Ruby 会查找与引发的异常类型(或其超类之一)匹配的
rescue子句。 - 如果找到匹配项,则执行该
rescue块中的代码。可以使用=> e(其中e是一个变量)捕获异常对象本身。 - 如果在
rescue子句中未指定特定的异常类型(即只有rescue),则默认捕获StandardError及其后代。最佳实践是捕获特定异常或至少StandardError,而不是Exception(后者可能会捕获您可能不想干扰的信号,如Interrupt或SystemExit)。 else子句(可选)仅在begin块完成且未引发任何异常时执行。ensure子句(可选)始终执行,无论是否发生异常、异常是否被捕获,或者块是否正常退出。这对于清理任务至关重要。
#!/usr/bin/env ruby# frozen_string_literal: true
begin print "输入一个数字: " num1_str = gets.chomp num1 = Integer(num1_str) # 对于无效输入,这可能引发 ArgumentError
print "输入另一个数字: " num2_str = gets.chomp num2 = Integer(num2_str)
# 如果 num2 为 0,这可能引发 ZeroDivisionError result = num1 / num2 puts "除法结果: #{result}"
rescue ArgumentError => e puts "无效输入:请输入有效数字。详情: #{e.message}"rescue ZeroDivisionError => e puts "错误:不能除以零。详情: #{e.message}"rescue StandardError => e puts "发生了一个意外的标准错误: #{e.message}" puts "回溯信息: #{e.backtrace.join("\n")}"else puts "计算成功,没有错误!"ensure puts "正在退出计算块。"end如果您为第二个数字输入非数字内容或 0,相应的 rescue 块将处理该错误。
retry 语句
Section titled “retry 语句”在 rescue 块内部,retry 语句会导致 Ruby 跳回 begin 块的开头并重新执行代码。使用 retry 时需谨慎,以免造成无限循环,通常需要包含计数器或改变条件。
begin # ... 可能失败的代码 ...rescue SomeError => e # ... 处理错误,可能修改状态 ... retry # 跳到 'begin' 块的开头end#!/usr/bin/env ruby# frozen_string_literal: true
attempts = 0max_attempts = 3
begin attempts += 1 puts "尝试 ##{attempts}" print "输入要打开的文件名: " filename = gets.chomp
file = File.open(filename, "r") puts "文件 '#{filename}' 已成功打开。" puts file.read(50) # Read first 50 chars file.close
rescue Errno::ENOENT => e # 文件未找到错误 puts "错误: #{e.message}" if attempts < max_attempts puts "正在重试..." retry else puts "达到最大尝试次数。无法打开文件。" endrescue StandardError => e puts "发生了另一个错误: #{e.message}"endraise 语句
Section titled “raise 语句”您可以使用 raise 语句显式引发(或抛出)异常。这对于在您自己的代码中发出错误信号非常有用。
# 1. 重新引发当前异常(常用于 rescue 块中)raise
# 2. 引发带特定消息的 RuntimeErrorraise "Error Message"
# 3. 引发带特定消息的特定类型异常raise ExceptionType, "Error Message"
# 4. 引发特定的异常对象(允许设置自定义属性)custom_error = MyCustomError.new("Detailed message")# custom_error.details = { 代码: 123 }raise custom_error
# 5. 条件引发(不太常见的形式,通常 `raise if condition` 更清晰)# raise ExceptionType, "Error Message" if condition#!/usr/bin/env ruby# frozen_string_literal: true
def check_age(age) if age < 0 raise ArgumentError, "Age cannot be negative." elsif age < 18 raise StandardError, "User is underage." end puts "年龄 #{age} 是有效的。"end
begin check_age(25) check_age(-5) # 这将引发 ArgumentError # check_age(16) # 这将引发 StandardErrorrescue ArgumentError => e puts "捕获到 ArgumentError: #{e.message}"rescue StandardError => e puts "捕获到 StandardError: #{e.message}"end
puts "年龄检查块之后。"输出结果为:
Age 25 is valid.Caught ArgumentError: Age cannot be negative.After age check block.捕获异常信息
Section titled “捕获异常信息”当异常被捕获(例如,rescue => e)时,变量 e 持有异常对象。异常对象的常用方法包括:
e.message:错误消息。e.backtrace:表示异常发生时调用堆栈的字符串数组。e.class:异常的类。
#!/usr/bin/env rubybegin 1 / 0rescue ZeroDivisionError => e puts "错误类: #{e.class}" puts "错误消息: #{e.message}" puts "回溯信息(前 3 行):" e.backtrace.first(3).each { |line| puts " #{line}" }end$! 全局变量也持有上次引发的异常,$@ 持有其回溯信息,但通常首选使用 => e 语法以提高清晰度和作用域。
catch 和 throw
Section titled “catch 和 throw”catch 和 throw 提供了一种非局部跳转机制,用于在正常处理过程中跳出深度嵌套的结构,主要不是用于错误处理(错误处理是 raise/rescue 的领域)。
catch(:label_name) do ... end 定义一个块。该块内部(或块内调用的任何方法)的 throw(:label_name) 或 throw(:label_name, return_value) 将立即跳到 catch 块的末尾。catch 块然后返回 throw 的第二个参数,如果没有给出则返回 nil。
result = catch(:done) do # ... 一些处理 ... 10.times do |i| # ... 更多处理 ... if i == 5 throw :done, "在迭代 #{i} 处完成" # 跳出并带返回值 end end "Completed all iterations" # 如果发生 throw,则不会到达这里end
puts result # => "Finished at iteration 5"示例:终止用户输入循环
Section titled “示例:终止用户输入循环”#!/usr/bin/env ruby# frozen_string_literal: true
def process_input(prompt_message) print prompt_message input = gets.chomp throw :quit_requested if input.downcase == 'quit' inputend
puts "输入每个字段的数据。输入 'quit' 提前退出。"
user_data = catch(:quit_requested) do name = process_input("姓名: ") age = process_input("年龄: ") city = process_input("城市: ") { name: name, age: age, city: city } # 如果输入全部给出,则返回哈希end
if user_data.is_a?(Hash) puts "\n收集到的用户数据:" user_data.each { |key, value| puts " #{key.capitalize}: #{value}" }else puts "\n输入过程提前退出了。"endThe Ruby Exception Hierarchy
Section titled “The Ruby Exception Hierarchy”Ruby 中的所有异常类都继承自 Exception 类。然而,通常建议从 StandardError 或其更具体的后代类捕获(rescue),因为 Exception 包括您通常不希望捕获的系统级事件,如 SystemExit(来自 exit)和 Interrupt(来自 Ctrl-C)。
简化的层次结构视图:
Exception ├── NoMemoryError ├── ScriptError │ ├── LoadError (例如,require 失败) │ └── SyntaxError ├── SignalException │ └── Interrupt (Ctrl-C) ├── StandardError (大多数常见的应用程序级错误继承自此) │ ├── ArgumentError │ ├── IOError │ │ └── EOFError │ ├── IndexError │ ├── NameError │ │ └── NoMethodError │ ├── RangeError │ ├── RegexpError │ ├── RuntimeError (raise "message" 的默认类型) │ ├── TypeError │ └── ZeroDivisionError │ └── ... 许多其他 ... ├── SystemExit (来自 exit) └── ... 其他内部错误 ...创建自定义异常
Section titled “创建自定义异常”为您的应用程序或库错误定义自定义异常类是良好的实践。这些类通常应继承自 StandardError 或其后代类之一。
#!/usr/bin/env ruby# frozen_string_literal: true
class NetworkDataError < StandardError attr_reader :error_code
def initialize(message = "Network data processing error", error_code = nil) super(message) @error_code = error_code endend
class ApiService def fetch_data(url) # 模拟一个可能失败的 API 调用 if url.include?("error_site") raise NetworkDataError.new("Failed to fetch from #{url}", 503) elsif url.include?("empty_site") raise NetworkDataError.new("No data received from #{url}", 204) end "Sample data from #{url}" endend
service = ApiService.newurls_to_try = ["http://example.com", "http://error_site.com", "http://empty_site.com"]
urls_to_try.each do |url| begin data = service.fetch_data(url) puts "#{url} 成功:#{data}" rescue NetworkDataError => e puts "#{url} 自定义错误:#{e.message}(代码: #{e.error_code || 'N/A'})" rescue StandardError => e puts "#{url} 通用错误:#{e.message}" endend