1. 异常类

含有异常信息的数据包是Exception类、或其子类的一个对象。当需要引发异常时,可以使用某个内建的Exception类,或者创建自己的异常类。每个Exception都关联有一个消息字符串和栈回溯信息。如果定义自己的异常,可以添加额外的信息。

当对操作系统的调用返回错误码时,会引发系统错误。Ruby得到这些错误,把每个错误包装到特定对象中,它们都是SystemCallError的子类,定义在Errno模块中,比如Errno::EIO。如果想得到底层的系统错误码,则每个Errno异常对象有一个Errno的类常量,它包含相应的系统错误码,比如Errno::EIO::Errno。


2. 处理异常

在一个begin/end块中,使用一个或多个rescue语句告诉Ruby希望处理的异常类型,例如:

begin
#statement
rescue MyError
  puts $!
raise
end

当异常被引发时,Ruby将相关Exception对象的引用放在全局变量"$!"中。可以不带任何参数来调用raise,它会重新引发"$!"中的异常,它允许我们先编写代码过滤掉一些异常,再把不能处理的异常传递到更高的层次。

在begin块中可以有多个rescue子句,每个rescue子句可以指示捕获多个异常,在rescue子句的结束处,可以提供一个Ruby的局部变量名来接收匹配的异常,例如:

begin
#statement
rescue SyntaxError => boom
  puts boom
rescue StandardError => bang
  puts bang
end

Ruby用引发的异常依次比较begin块中每个rescue子句的每个参数,如果引发的异常匹配了一个参数,Ruby就执行rescue的程序体,同时停止比较。如果编写一个不带参数表的rescue子句,它的默认参数是StandardError,如果没有任何rescue子句与之匹配,或者异常在begin/end块外面被引发,Ruby就沿着调用栈向上查找,在调用者上寻找异常的处理者,接着在调用者的调用者上寻找。

rescue子句的参数通常是Exception类的名称,实际上它们可以是任何返回Exception类的表达式(包括方法调用)。

有时需要保证一些处理在block结束时能够被执行,通过ensure子句可以完成。ensure跟在最后的rescue子句后面,它包含一段当block退出时总是要被执行的代码,不管block是否正常退出,例如:

begin
#statement
rescue SyntaxError => boom
  puts boom
rescue StandardError => bang
  puts bang
ensure
  puts 'end'
end

有时可能可以纠正异常的原因,可以在rescue子句中使用retry语句去重复执行整个begin/end区块,这很可能会导致无限循环。


3. 引发异常

可以使用raise方法在代码中引发异常,例如:

raise
raise "wrong parameters"
raise MyException, "User Define Exception", caller

第一种形式只是简单地重新引发当前异常,如果没有当前异常,则引发RuntimeError。这种形式用于首先截获异常再将其继续传递的异常处理方法中。

第二种形式创建新的RuntimeError异常,把它的消息设置为指定的字符串,然后异常随着调用栈向上引发。

第三种形式使用第一个参数创建异常,然后把相关联的消息设置给第二个参数,同时把栈信息设置给第三个参数。


4. 捕获和抛出

catch和throw能够在正常处理过程期间从一些深度嵌套的结构中跳转出来,例如:

catch (:done) do
  while line = gets
    throw :done unless fields = line.split(/\t/)
  end
end

catch定义了以给定名称为标签的block,这个block会正常执行直到遇到throw为止。当Ruby碰到throw,它回溯调用栈,用匹配的符号寻找catch代码块,当发现它之后,Ruby将栈清退到这个位置并终止该block。