第1章 错误处理与例外
当没有例外处理时,程序出错,就会显示出下面这样的错误消息并结束:
> ruby test.rb test.rb:2:in ‘open’: No such file or directory – “no/file” (Errno: ENOENT) from test.rb:2:in ‘foo’ from test.rb:6:in ‘bar’ from test.rb:10 |
消息格式如下: 文件名:行号:in ‘方法名’: 错误消息 (例外类) from 调用方法的位置 … |
例外处理的写法
begin 有可能发生例外的处理动作 rescue 例外发生时的处理措施 ensure 无论例外发生与否都坚持要执行的动作 end
|
begin 有可能发生例外的处理动作 rescue => 用来存放例外对象的变量 例外发生时的处理措施 ensure 无论例外发生与否都坚持要执行的动作 end |
即使没有指定变量名称,例外对象也会自动存放到下表中所列出的变量里,不过明确指定变量名称程序会比较好懂。
例外发生时会自动设定的变量
变量 |
意义 |
$! |
最后发生的例外(例外对象) |
$@ |
最后例外所发生的位置相关信息 |
例外对象的方法
方法名 |
意义 |
class |
例外类别 |
message |
例外的消息 |
backtrace |
例外的发生位置信息($@等同于$!.backtrace) |
重新执行
在rescue语句块里可以使用retry语句重新执行begin语句块的动作。
file = ARGV[0] begin io = open(file) rescue sleep 10 retry end data = io.read io.close |
每隔10秒重新尝试open方法,成功时则返回文件内容。但若文件一直打不开,会形成无穷循环。 |
rescue修饰符
运算式1 rescue 运算式2 |
n = Integer(val) rescue 0 |
当Integer方法接收数字字符串时,会将该字符串所代表的数值返回。如果val不是数值的话,就会发生例外,而“=”的右侧整体的值会返回0。 像这样,对于不需要特别复杂的处理,经常用来设定默认值。 |
当例外处理的begin~end范围就是整个方法本身时,可以省略begin和end,只写rescue与ensure语句块。
在类定义里也可以写rescue和ensure语句块。但是在类定义发生例外时,例外发生位置之后的所有方法定义会被跳过,所以一般的程序不会这样用。
当不只一种例外可能发生,而想要分开处理这些例外时,可以使用多个rescue语句块来处理。
begin 有可能发生例外的处理动作 rescue Exception1, Exception2 => 变量 Exception1或Exception2例外的处理措施 rescue Exception3 => 变量 Exception3例外的处理措施 rescue => 变量 其他例外的处理措施 end |
指定类名称,就可以捕捉预想的例外。 |
file1 = ARGV[0] file2 = ARGV[1] io = nil begin io = open(file1) rescue Errno::ENOENT, Errno::EACCES io = open(file2) end |
在begin~end内部才开始使用的变量,无法在begin~end之外访问,所以应当事先将nil存进去。 |
所有例外都是Exception类的子类。
rescue语句块所指定的例外种类,是例外的类名称。
在rescue语句块没有指定例外类时,会捕捉StandardError与其子类。
rescue语句块会捕捉例外类,实际上还会捕捉所有该类的子类。所以自己要定义各种例外类时,一般都会先继承StandardError类,接着再往下继承各种例外类。
class MyError < StandardError; end class MyError1 < MyError; end class MyError2 < MyError; end |
自己想要让例外发生时,则使用raise方法。这个方法用在自己想要在某些条件下主动产生出新的例外,或者再次引发之前捕捉到的例外,将例外交给调用来源时。
raise有下面4种形式:
raise 消息 |
引发RuntimeError例外。指定的字符串会被当作新的例外对象的消息。 |
raise 例外类 |
引发指定的例外。 |
raise 例外类, 消息 |
引发指定的例外,并且指定的字符串会被当作新的例外对象的消息。 |
raise |
写在rescue语句块外部,则引发RuntimeError例外;写在rescue语句块内,则可以自动再次引发最后发生的例外($!)。 |
catch与throw
虽然不是例外,不过还有catch和throw方法可以用来控制程序执行的流程。
指定throw的第2个实参时,这会成为catch的返回值。
def test_throw throw :test end puts “test start” catch (:test){ puts “before test_throw()” test_throw() puts “after test_throw()” } puts “test end” |
输出: test start before test_throw() test end
|