Object +--Exception +--NoMemoryError +--ScriptError | +--LoadError | +--NotImplementedError | +--SyntaxError +--SecurityError # Ruby1.8中的标准异常 +--SignalException | +--Interrupt +--SystemExit +--SystemStackError # Ruby1.8中的标准异常 +--StandardError +--ArgumentError +--FiberError # Ruby 1.9中的新增异常 +--IOError | +--EOFError +--IndexError | +--KeyError # New in 1.9 | +--StopIteration # New in 1.9 +--LocalJumpError +--NameError | +--NoMethodError +--RangeError | +--FloatDomainError +--RegexpError +--RuntimeError +--SystemCallError +--ThreadError +--TypeError +--ZeroDivisionErrorException类定义了两个方法用于返回异常信息,message方法用于返回“可读”的异常信息字符串,如果Ruby程序出现了一个未捕获的一场,这个消息会显示给最终用户,通常用于程序员调试代码。另一个方法是backtrace,这个方法返回一个数组,数组中包含了调用的堆栈信息:
filename : linenumber in methodname
以上的代码中filename说明出错的文件位置,linenumer说明那一行出现了错误,methodname说明由那个方法引起的异常。如果你要定义自己的异常类,可以通过set_backtrace来自定义这个信息。你可以通过扩展当前异常类来定义自己的异常类:
class MyError < StandardError; end
你可以通过raise或者fail来抛出一个异常,有以下几种方式:
- 如果raise没有参数,那么Ruby会创建一个RuntimeError(不带任何消息)的异常类,并抛出它。
- 如果raise有一个参数,并且这个参数是一个Exception对象,那么Ruby会直接抛出这个异常对象,通常我们不这样作 。
- 如果raise有一个参数,并且这个参数是一个字符串,那么Ruby会创建一个RuntimeError对象并将字符串的内容作为异常的消息,我们经常这样使用。
- 如果raise的第一个参数是一个对象,且这个对象有一个exception方法,那么Ruby会调用这个方法并抛出这个方法返回的异常对象。第二个参数可以是一个字符串,这个字符串会传递给第一个对象的exception方法的第一个参数,作为异常消息。
def factorial(n) # 定义一个函数 raise "bad argument" if n < 1 # 如果条件不满足就抛出异常 return 1 if n == 1 # factorial(1) 为 1 n * factorial(n-1) # 继续计算 end上面的例子中raise只有一个字符串参数,下面是用其它方式抛出的异常:
raise RuntimeError, "bad argument" if n < 1 raise RuntimeError.new("bad argument") if n < 1 raise RuntimeError.exception("bad argument") if n < 1就像上面的代码那样,你可以通过new或者exception关键字来创建你自己的异常,这两个方法的字符串参数是异常显示的消息。
Ruby中捕获异常通过rescue关键字,通常一个rescue位于begin和end之间,如下例:
begin # 这里放执行代码. # 通常他们可以正常的执行 rescue # 这里是rescue子句,如果上面的代码出现异常,那么就跳转到这里,
# 可以在这里放置其他代码来处理异常 . end
当然我们可以命名异常对象,如果定义全局变量$!并且require 'English',那么你可以使用$ERROR_INFO 来命名异常,当是一个更好的方法是使用变量:
begin # 在这个块中捕获异常
x = factorial(-1) # 这里我们故意加入了非法的参数 rescue => ex # 将异常对象存储在一个变量ex中 puts "#{ex.class}: #{ex.message}" # 捕获这个异常并打印异常消息 end # 结束块注意这里声明的异常变量不仅仅在异常处理范围,一旦定义了这个变量,你可以在块的外部使用它。
还要注意的是上面这种方式只能捕获StandardError。如果你要捕获StandardError以外的异常,或者你要捕获一个你自定义的异常类型,你必须声明这个异常,或者使用rescue Exception的方式捕获任何类型的异常:
rescue Exception #捕获任何类型的异常
rescue ArgumentError => e #捕获ArgumentError异常
rescue ArgumentError, TypeError => error #将多个异常赋值到一个异常变量中
我们同样可以以一种分支的方式分别处理不同的异常:
rescue Exception #捕获任何类型的异常 rescue ArgumentError => e #捕获ArgumentError异常 rescue ArgumentError, TypeError => error #将多个异常赋值到一个异常变量中我们前面讲过的retry关键字可以在rescue中使用,这样使用的目的是,有的时候引发的异常只是短暂异常,可能过一会儿就可以正常运行了,比如,当前服务器可能无法访问但是过一会儿就正常了,下面是一个访问网络的例子,它遇到网络异常后尝试再次访问:
require 'open-uri' tries = 0 # 访问一个URL等待的时间 begin # 块开始部分
tries += 1 # 尝试连接 URL并打印连接信息 open('http://www.example.com/') {|f| puts f.readlines } rescue OpenURI::HTTPError => e # 如果得到一个HTTP error puts e.message # 打印异常信息 if (tries < 4) # 判断尝试的次数... sleep(2**tries) # 等待 2, 4, 或者 8 秒钟 retry # 然后再试一次! end end这里还有一个else子句,你可能认为else是在rescue执行失败后执行的语句,其实不然,实际上else子句部分的代码是在当begin部分的代码可以没有异常的正常执行后执行的代码。老实说else在rescue中并不经常使用,如果begin中没有rescue的话使用else是没有意义的。
另外一个重要的关键字是ensure,在这个子句中的代码无论如何都会被执行,比如你要关闭一个文件、一个数据库连接或者提交数据库执行语句。如果ensure在方法中,那么它的值不会作为方法的返回值,也就是说,在你调用一个方法的时候实际上会先执行ensure中的语句然后再执行begin中的语句。不过,如果你在ensure中使用了return关键字那么方法的返回值会被替换为ensure的返回值。
begin return 1 # 方法的返回值 ensure return 2 # 最终会返回这个值 end事实上rescue,ensure这些关键字不仅可以用于begin/end之间,还可以用于def定义的模块、类、方法。
下面是resuce子句的一个变体,如果resuce前面的代码出现异常或者raise一个异常,那么resuce后面的代码就会执行:
y = factorial(x) rescue 0
上面的代码等同于:y = begin factorial(x) rescue 0 end这种写法的好处是可以省略begin/end,但必须在一行里完成,而且这种变态中只能处理StandardError异常,而不能出来其它类型的异常。
No comments:
Post a Comment