Ruby 简明教程

Ruby - Exceptions

执行和异常总是同时发生。如果您打开一个不存在的文件,则如果没有正确处理此情况,那么您的程序将被认为质量很差。

The execution and the exception always go together. If you are opening a file, which does not exist, then if you did not handle this situation properly, then your program is considered to be of bad quality.

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

The program stops if an exception occurs. So exceptions are used to handle various type of errors, which may occur during a program execution and take appropriate action instead of halting program completely.

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

Ruby provide a nice mechanism to handle exceptions. We enclose the code that could raise an exception in a begin/end block and use rescue clauses to tell Ruby the types of exceptions we want to handle.

Syntax

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

从 begin 到 rescue 的所有内容都受到保护。如果在此代码块执行期间发生异常,控制将传递到 rescue 和 end 之间的块。

Everything from begin to rescue is protected. If an exception occurs during the execution of this block of code, control is passed to the block between rescue and end.

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

For each rescue clause in the begin block, Ruby compares the raised Exception against each of the parameters in turn. The match will succeed if the exception named in the rescue clause is the same as the type of the currently thrown exception, or is a superclass of that exception.

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

In an event that an exception does not match any of the error types specified, we are allowed to use an else clause after all the rescue clauses.

Example

#!/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 失败。

This will produce the following result. You can see that STDIN is substituted to file because open failed.

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

Using retry Statement

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

You can capture an exception using rescue block and then use retry statement to execute begin block from the beginning.

Syntax

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

Example

#!/usr/bin/ruby

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

以下是流程 −

The following is the flow of the process −

  1. An exception occurred at open.

  2. Went to rescue. fname was re-assigned.

  3. By retry went to the beginning of the begin.

  4. This time file opens successfully.

  5. Continued the essential process.

NOTE − 请注意,如果重新替换的文件不存在,此示例代码会无限次重试。如果您对异常过程使用 retry,请小心。

NOTE − 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.

Using raise Statement

您可以使用 raise 语句引发异常。以下方法在每次调用时都会引发异常。它会打印其第二个消息。

You can use raise statement to raise an exception. The following method raises an exception whenever it’s called. It’s second message will be printed.

Syntax

raise

OR

raise "Error Message"

OR

raise ExceptionType, "Error Message"

OR

raise ExceptionType, "Error Message" condition

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

The first form simply re-raises the current exception (or a RuntimeError if there is no current exception). This is used in exception handlers that need to intercept an exception before passing it on.

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

The second form creates a new RuntimeError exception, setting its message to the given string. This exception is then raised up the call stack.

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

The third form uses the first argument to create an exception and then sets the associated message to the second argument.

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

The fourth form is similar to the third form but you can add any conditional statement like unless to raise an exception.

Example

#!/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.'

这会产生以下结果 −

This will produce the following result −

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

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

One more example showing the usage of raise −

#!/usr/bin/ruby

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

这会产生以下结果 −

This will produce the following result −

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

Using ensure Statement

有时候,你需要保证在代码块结束时完成一些处理,无论是否引发异常。例如,你可能在进入该代码块时打开了文件,你需要确保在该块退出时将其关闭。

Sometimes, you need to guarantee that some processing is done at the end of a block of code, regardless of whether an exception was raised. For example, you may have a file open on entry to the block and you need to make sure it gets closed as the block exits.

ensure 子句就是用来这么做的。ensure 位于最后一个 rescue 子句之后,并且包含一个将在块终止时始终执行的代码块。无论该块正常退出,是引发和捕获了一个异常,还是被未捕获的异常终止,ensure 块都将被运行。

The ensure clause does just this. ensure goes after the last rescue clause and contains a chunk of code that will always be executed as the block terminates. It doesn’t matter if the block exits normally, if it raises and rescues an exception, or if it is terminated by an uncaught exception, the ensure block will get run.

Syntax

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

Example

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

这会产生以下结果 −

This will produce the following result −

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

Using else Statement

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

If the else clause is present, it goes after the rescue clauses and before any ensure.

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

The body of an else clause is executed only if no exceptions are raised by the main body of code.

Syntax

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

Example

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

这会产生以下结果 −

This will produce the following result −

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

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

Raised error message can be captured using $! variable.

Catch and Throw

虽然 raise 和 rescue 的异常机制非常适合在出现问题时放弃执行,但在正常处理过程中有时也可以从一些深度嵌套的构造中跳出。这就是 catch 和 throw 派上用场的时候。

While the exception mechanism of raise and rescue is great for abandoning the execution when things go wrong, it’s sometimes nice to be able to jump out of some deeply nested construct during normal processing. This is where catch and throw come in handy.

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

The catch defines a block that is labeled with the given name (which may be a Symbol or a String). The block is executed normally until a throw is encountered.

Syntax

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

Example

以下示例在一个请求中输入了“!”来使用 throw 终止与用户的交互。

The following example uses a throw to terminate interaction with the user if '!' is typed in response to any prompt.

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:")

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

You should try the above program on your machine because it needs manual interaction. This will produce the following result −

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

Class Exception

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

Ruby’s standard classes and modules raise exceptions. All the exception classes form a hierarchy, with the class Exception at the top. The next level contains seven different types −

  1. Interrupt

  2. NoMemoryError

  3. SignalException

  4. ScriptError

  5. StandardError

  6. SystemExit

在此级别还有另一个异常 Fatal ,但 Ruby 解释器仅在内部使用它。

There is one other exception at this level, Fatal, but the Ruby interpreter only uses this internally.

ScriptError 和 StandardError 都有许多子类,但我们不必在这里深入细节。重要的是,如果我们创建自己的异常类,它们需要是类 Exception 或其子代的子类。

Both ScriptError and StandardError have a number of subclasses, but we do not need to go into the details here. The important thing is that if we create our own exception classes, they need to be subclasses of either class Exception or one of its descendants.

我们来看一个示例 −

Let’s look at an example −

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

现在,看一看下面的示例,它将使用此异常 −

Now, look at the following example, which will use this exception −

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 的一个新实例,原因是该特定异常导致数据的写入失败。

The important line here is raise FileSaveError.new($!). We call raise to signal that an exception has occurred, passing it a new instance of FileSaveError, with the reason being that specific exception caused the writing of the data to fail.