执行和异常总是一起的。如果打开一个文件,但是它不存在,那么如果没有处理这种情况,那么程序被认为是质量比较差的。
如果发生异常程序停止。因此,例外是用来处理不同类型的程序执行过程中可能发生的错误,并采取适当的处理动作,而不是完全停止程序。
Ruby提供了一个很好的机制来处理异常。我们附上的代码,可以在 begin/end 块抛出一个异常,并使用异常子句来告诉Ruby我们要处理的异常类型。
语法 :
begin # - rescue OneTypeOfException # - rescue AnotherTypeOfException # - else # Other exceptions ensure # Always will be executed end
一切从 rescue开始保护。这个代码块在执行过程中如果出现异常,控制传递给 rescue 和结束之间的块。
对于每一个rescue 子句在BEGIN块,Ruby依次对每个参数进行比较异常。rescue 子句中命名的异常是会成功的,如果目前抛出的异常的类型相同,或者是该异常的超类的匹配。
rescue 子句中命名的异常是会成功的,如果目前抛出的异常的类型相同,或者是该异常的超类的匹配。
例子:
#!/usr/bin/ruby begin file = open("/unexistant_file") if file puts "File opened successfully" end rescue file = STDIN end print file, "==", STDIN, " "
这将产生以下结果。我们可以看到 STDIN 替代文件,因为打开失败。
#<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
以下是处理的流程:
-
发生异常打开的
-
进入 rescue. fname的重新分配
-
从 begin 块开始重试
-
此时文件成功打开
-
继续过程
注意,如果重新替代的文件名称不存在这个例子代码无限重试。要小心,如果使用异常处理重试。
使用 raise 语句:
可以使用raise语句引发异常。下面的方法抛出一个异常时它被调用。它的第二个消息将被打印。
语法:
raise OR raise "Error Message" OR raise ExceptionType, "Error Message" OR raise ExceptionType, "Error Message" condition
第一形式简单重新引发当前异常(或抛出一个RuntimeError,如果没有当前异常)。这是用在需要拦截之前将它传递一个异常的异常处理程序。
第二种形式创建一个新的 RuntimeError 异常,设置它的消息给定的字符串。然后提出了此异常调用堆栈。
第三种形式使用的第一个参数创建一个异常,第二个参数设置相关的消息。
第四种形式是类似第三种形式的,但可以添加任何条件语句像 unlessto 来引发异常。
例子:
#!/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 在 rescue 子句的后面,并包含一个代码块,将永远被执行块终止。如果块退出正常,如果它会引发rescues 异常或者如果它是由未捕获的异常终止,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 :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
例子:
下面的示例使用一个抛出来终止交互,用户如果'!“键入的任何提示。
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
还有另一个异常,在这个层面上是致命的,但Ruby解释器只使用这个内部。
两个 ScriptError 和 StandardError 有一些子类,但我们不需要在这里详谈。最重要的是,如果我们创建我们自己的异常类,他们需要的任一类异常或者其子类。
让我们来看一个例子:
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
这里最重要的行是提高 FileSaveError.new($!)。我们调用抛出发生了异常的信号,传递给它一个新的 FileSaveError的实例,是特定的异常导致写入数据失败的原因。