执行和异常总是在一起.如果您打开一个不存在的文件,那么如果您没有正确处理这种情况,那么您的程序将被视为质量不佳.
如果异常,程序将停止发生.因此异常用于处理各种类型的错误,这些错误可能在程序执行期间发生,并采取适当的操作而不是完全停止程序.
Ruby提供了一种处理异常的好机制.我们附上可能在 begin/end 块中引发异常的代码,并使用 rescue 子句告诉Ruby我们想要处理的异常类型.
begin # - rescue OneTypeOfException # - rescue AnotherTypeOfException # - else # Other exceptions ensure # Always will be executed end
从开始到救援的所有内容都受到保护.如果在执行此代码块期间发生异常,则控制将传递到 rescue 和 end 之间的块.
For在 begin 块中的每个 rescue 子句中,Ruby依次将引发的Exception与每个参数进行比较.如果rescue子句中指定的异常与当前抛出的异常的类型相同,或者是该异常的超类,则匹配将成功.
在异常执行的情况下不匹配任何指定的错误类型,我们可以在所有 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>
您可以使用 rescue 块捕获异常,然后使用重试语句从头开始执行开始块.
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 beginend
#!/usr/bin/ruby begin file = open("/unexistant_file") if file puts "File opened successfully" end rescue fname = "existant_file" retry end
以下是流程的流程 :
打开时发生异常.
去救援. fname被重新分配.
通过重试进入开始的开始.
此时间文件成功打开.
继续基本流程.
注意 : 请注意,如果重新替换名称的文件不存在,则此示例代码将无限重试.如果你使用重试进行异常处理,请小心.
你可以使用 raise 声明提出异常.无论何时调用它,以下方法都会引发异常.它将打印第二条消息.
raise OR raise "Error Message" OR raise ExceptionType, "Error Message" OR raise ExceptionType, "Error Message" condition
第一种形式只是重新引发当前异常(如果没有当前异常,则返回RuntimeError) ).这用于需要在传递异常之前拦截异常的异常处理程序.
第二种形式创建一个新的 RuntimeError 异常,将其消息设置为给定串.然后在调用堆栈中引发此异常.
第三种形式使用第一个参数创建异常,然后将关联的消息设置为第二个参数.
第四种形式类似于第三种形式,但你可以添加任何条件语句,如,除非引发异常.
#!/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"]
有时候,你需要保证在代码块结束时完成某些处理,无论是否引发异常.例如,你可能在进入块时打开一个文件,你需要确保它在块退出时关闭.
确保子句只是这个. 确保在最后一个救援子句之后,并且包含一块代码,这些代码将在块终止时始终执行.如果块正常退出,如果它引发并拯救异常,或者如果它被未捕获的异常终止,那么确保块将被运行无关紧要.
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 子句存在,它将在 rescue 子句之后和任何确保之前.
只有在代码主体没有引发异常时才会执行 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 定义了一个用给定名称标记的块(可以是Symbol或String) .该块正常执行,直到遇到throw.
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来终止与用户的交互,如果'! '是为响应任何提示而输入的.
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类.下一级包含七种不同的类型 :
中断
NoMemoryError
SignalException
ScriptError
StandardError
SystemExit
此级别还有另一个例外,致命,但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实例,原因是该特定异常导致数据写入失败.