Ruby - 例外


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

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

Ruby 提供了一种很好的异常处理机制。我们将可能引发异常的代码包含在开始/结束块中,并使用救援子句告诉 Ruby 我们要处理的异常类型。

句法

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

开始救援的一切都受到保护。如果在执行此代码块期间发生异常,则控制权将传递到rescueend之间的块。

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

如果异常与指定的任何错误类型都不匹配,我们可以在所有救援子句之后使用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被替换为文件,因为打开失败。

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

使用重试语句

您可以使用救援块捕获异常,然后使用重试语句从头开始执行开始块。

句法

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 被重新分配。
  • 通过重试转到开头。
  • 这次文件打开成功。
  • 继续必要的过程。

注意- 请注意,如果重新替换名称的文件不存在,则此示例代码将无限重试。如果您对异常过程使用重试,请务必小心。

使用 raise 语句

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

句法

raise 

OR

raise "Error Message" 

OR

raise ExceptionType, "Error Message"

OR

raise ExceptionType, "Error Message" condition

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

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

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

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

例子

#!/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子句,则它位于救援子句之后和任何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 派上用场的地方。

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 的标准类和模块会引发异常。所有异常类形成一个层次结构,其中 Exception 类位于顶部。下一个级别包含七种不同的类型 -

  • 打断
  • 无内存错误
  • 信号异常
  • 脚本错误
  • 标准误差
  • 系统退出

这一级别还有另一个异常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 实例,原因是特定的异常导致数据写入失败。