Ruby - 异常

执行和异常总是一起发生的。 如果您正在打开一个不存在的文件,那么如果您没有正确处理这种情况,那么您的程序就会被认为质量不佳。

如果发生异常,程序将停止。 因此异常用于处理各种类型的错误,这些错误可能在程序执行期间发生并采取适当的措施,而不是完全停止程序。

Ruby 提供了一个很好的机制来处理异常。 我们将可能引发异常的代码包含在 begin/end 块中,并使用 rescue 子句告诉 Ruby 我们要处理的异常类型。


语法

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# Other exceptions
ensure
# Always will be executed
end

beginrescue 的所有内容都受到保护。 如果在执行此代码块期间发生异常,则将控制权传递给 rescueend 之间的块。

对于 begin 块中的每个 rescue 子句,Ruby 依次将引发的异常与每个参数进行比较。 如果在救援子句中命名的异常与当前抛出的异常的类型相同,或者是该异常的超类,则匹配成功。

如果异常与指定的任何错误类型都不匹配,我们可以在所有 rescue 子句之后使用 else 子句。

示例

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
      file = STDIN
end
print file, "==", STDIN, "\n"

这将产生以下结果. 您可以看到 STDIN 被替换为 file 因为 open 失败。

#<IO:0xb7d16f84>==#<IO:0xb7d16f84>

使用 retry 语句

您可以使用 rescue 块捕获异常,然后使用 retry 语句从头开始执行 begin 块。

语法

begin
   # Exceptions raised by this code will 
   # be caught by the following rescue clause
rescue
   # This block will capture all types of exceptions
   retry  # This will move control to the beginning of begin
end

示例

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
   fname = "existant_file"
   retry
end

以下是处理的流程 −

  • 打开时出现异常。
  • 去救援。 fname 已重新分配。
  • 通过重试到开头的开始。
  • 本次文件打开成功。
  • 继续基本流程。

注意 − Notice that if the file of re-substituted name does not exist this example code retries infinitely. Be careful if you use retry for an exception process.


使用 raise 语句

您可以使用 raise 语句来引发异常。 每当调用以下方法时,都会引发异常。 将打印第二条消息。

语法

raise 

OR

raise "Error Message" 

OR

raise ExceptionType, "Error Message"

OR

raise ExceptionType, "Error Message" condition

第一种形式只是重新引发当前异常(如果没有当前异常,则返回 RuntimeError)。 这用于需要在传递异常之前拦截异常的异常处理程序。

第二种形式创建一个新的RuntimeError 异常,将其消息设置为给定的字符串。 然后在调用堆栈中引发此异常。

第三种形式使用第一个参数创建异常,然后将关联的消息设置为第二个参数。

第四种形式与第三种形式相似,但您可以添加任何条件语句,如 unless 以引发异常。

示例

#!/usr/bin/ruby

begin  
   puts 'I am before the raise.'  
   raise 'An error has occurred.'  
   puts 'I am after the raise.'  
rescue  
   puts 'I am rescued.'  
end  
puts 'I am after the begin block.'  

这将产生以下结果 −

I am before the raise.  
I am rescued.  
I am after the begin block.  

另一个示例显示 raise 的用法 −

#!/usr/bin/ruby

begin  
   raise 'A test exception.'  
rescue Exception => e  
   puts e.message  
   puts e.backtrace.inspect  
end  

这将产生以下结果 −

A test exception.
["main.rb:4"]

使用 ensure 声明

有时,无论是否引发异常,您都需要保证在代码块末尾完成某些处理。 例如,您可能在进入块时打开了一个文件,您需要确保它在块退出时关闭。

ensure 子句就是这样做的。 ensure 位于最后一个救援子句之后,并包含一段代码,该代码块将始终在块终止时执行。 如果块正常退出,如果它引发并拯救异常,或者如果它被未捕获的异常终止,则 ensure 块将运行。

语法

begin 
   #.. process 
   #..raise exception
rescue 
   #.. handle error 
ensure 
   #.. finally ensure execution
   #.. This will always execute.
end

示例

begin
   raise 'A test exception.'
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
ensure
   puts "Ensuring execution"
end

这将产生以下结果 −

A test exception.
["main.rb:4"]
Ensuring execution

使用 else 语句

如果存在 else 子句,则它位于 rescue 子句之后和任何 ensure 子句之前。

else 子句的主体只有在代码主体没有引发异常时才会执行。

语法

begin 
   #.. process 
   #..raise exception
rescue 
   # .. handle error
else
   #.. executes if there is no exception
ensure 
   #.. finally ensure execution
   #.. This will always execute.
end

示例

begin
   # raise 'A test exception.'
   puts "I'm not raising exception"
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
else
   puts "Congratulations-- no errors!"
ensure
   puts "Ensuring execution"
end

这将产生以下结果 −

I'm not raising exception
Congratulations-- no errors!
Ensuring execution

可以使用 $! 变量捕获引发的错误消息。


Catch 和 Throw

虽然 raise 和 rescue 的异常机制非常适合在出现问题时放弃执行,但有时能够在正常处理期间跳出一些深度嵌套的构造也很不错。 这就是 catch 和 throw 派上用场的地方。

catch 定义了一个用给定名称(可以是符号或字符串)标记的块。 该块正常执行,直到遇到抛出。

语法

throw :lablename
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end

OR

throw :lablename condition
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end

示例

以下示例使用 throw 来终止与用户的交互 if '!' 为响应任何提示而键入。

def promptAndGet(prompt)
   print prompt
   res = readline.chomp
   throw :quitRequested if res == "!"
   return res
end

catch :quitRequested do
   name = promptAndGet("Name: ")
   age = promptAndGet("Age: ")
   sex = promptAndGet("Sex: ")
   # ..
   # process information
end
promptAndGet("Name:")

您应该在您的机器上尝试上述程序,因为它需要手动交互。 这将产生以下结果 −

Name: Ruby on Rails
Age: 3
Sex: !
Name:Just Ruby

类异常

Ruby 的标准类和模块会引发异常。 所有异常类形成一个层次结构,异常类位于顶部。 下一个级别包含七种不同的类型 −

  • Interrupt
  • NoMemoryError
  • SignalException
  • ScriptError
  • StandardError
  • SystemExit

在这个级别还有一个例外,Fatal,但是 Ruby 解释器只在内部使用它。

ScriptError 和 StandardError 都有许多子类,但这里我们无需赘述。 重要的是,如果我们创建自己的异常类,它们必须是 Exception 类或其后代之一的子类。

我们来看一个例子 −

class FileSaveError < StandardError
   attr_reader :reason
   def initialize(reason)
      @reason = reason
   end
end

现在,看下面的例子,它将使用这个异常 −

File.open(path, "w") do |file|
begin
   # Write out the data ...
rescue
   # Something went wrong!
   raise FileSaveError.new($!)
end
end

这里重要的一行是 raise FileSaveError.new($!)。 我们调用 raise 来表示发生了异常,并传递给它一个新的 FileSaveError 实例,原因是特定异常导致数据写入失败。