22.使用定制的异常而不是抛出字符串
23.捕获可能的具体的异常
24.通过块和ensure管理资源
25.通过临近的end退出ensure语句[problem]
26.限制retry次数,改变重试频率并记录异常信息
27.throw比raise更适合用来跳出作用域
22.使用定制的异常而不是抛出字符串
下面介绍raise的用法:
1.直接抛出字符串
raise "hello world"
2.参数分别为异常类和字符串
raise RuntimeError, "hello world"
第一种中raise直接抛出参数的形式,和下面的是第一参数是RuntimeError是等价的,直接抛出参数的错误类型就是RuntimeError类型。
3.raise自定义的异常类
class TemperatureError < StandardError
end
raise TemperatureError
#raise TemperatureError, "hello world"
4.raise自定义的异常对象和字符信息
class TemperatureError < StandardError
attr_reader(:temperature)
def initialize(temperature)
@temperature = temperature
super("invalid temperature: #{@temperature}")
end
end
raise TemperatureError.new(100) #invalid temperature: 100,相当于执行super()方法,不带参数进入父类
raise TemperatureError.new(100), "hello world" #hello world,相当于执行了super方法,带参数进入父类
5.异常类的定制和使用
#1.必须继承标准的异常类,一般是继承StandardError
#2.在initialize方法中,要求实现了super方法
#代码示例
class TemperatureError < StandardError
attr_reader(:temperature)
def initialize(temperature)
@temperature = temperature
super("invalid temperature: #{@temperature}")
end
end
6.对raise抛出的类进行捕获
begin
raise RuntimeError, "runtime error"
rescue =>e
puts "#{e}"
end #runtime error
23.捕获可能的具体的异常
在第22章节中讲述了raise的用法,raise讲述的是抛出异常,这里讲述的是如何捕获异常。在raise中抛出的都是RuntimeError异常,参考上一个章节,对raise异常进行捕获
begin
raise RuntimeError, "runtime error"
rescue =>e
puts "#{e}"
end #runtime error
其中rescue是捕获StandardError类和其子类的方法,其他类不捕获,如果直接写了“=>e”,那么就是认为捕获了StandardError类的对象,e是相应的错误信息。但是在rescue中一般是“从小到大”的原则,先捕获具体的异常,然后再去捕获相关的父类,比如
#如果StandardError排在第一位,则会先捕获StandardError异常。
begin
raise RuntimeError, "runtime error"
rescue RuntimeError => e
puts "runtime error #{e}"
rescue StandardError => e
puts "standard error #{e}"
end
处理异常中出现异常的情况,比如说,执行的代码中是数据库出现了异常,rescue捕获这个异常,同是发起请求,将这个异常发送给支持部门,如果这个发起的网络请求出错,那么最开始的数据库异常会被抛弃,rescue反而接受捕获数据库请求异常。处理这个问题的方法在异常中捕获异常,就是说将原始的异常作为参数传递,用以发起网络请求,同时捕获这个异常。
#e是最先捕获的数据库异常,将e作为参数发送,同时抛出这个异常
def send_to_support_staff(e)
..
rescue
raise(e)
end
24.通过块和ensure管理资源
应用场景:如果打开一个文件,中间出错,那么这个文件并没有关闭,那么这个文件打开的资源可能会被一直暂用,用词有了ensure关键词,不管是否出现bug,ensure中的代码都会被执行。
#代码示例
begin
file = File.open(file_name, "w")
..
rescue
..
else
..
ensure
#有可能异常发生时,file文件并没有初始化,所以要先进行判断
file.close if file
总结和说明
1.无论正常和异常与否,ensure中的代码都会被执行
2.ensure和begin中代码共享作用域,begin中定义的变量在ensure中也可以使用
3.使用ensure的时候需要确定变量是否被初始化,就是上面的if file语句
File类中打开文件的方法其实可以通过块的方式来执行,如果使用块的方式来执行,块结束之后被打开的块文件就会被关闭
File.open(file_name, "w") do |file|
..
end
应用:通过Lock类来模拟FIle类,即可以接受块,也可以接受参数
class Lock
def self.acquire
lock = new #initialize
lock.exclusive_lock!
if block_given?
yield(blocl)
else
lock
end
ensure
if block_given?
lock.unlock if lock
end
end
end
25.通过临近的end退出ensure语句
在rescue和ensure中分别使用return会隐藏异常,返回值会作为方法的返回值
#ensure中return,异常会被抛弃
def demo
1/0
ensure
return "hello world"
end
#rescue中return,模式接受StandardError但是不处理
def demo
1/0
rescue
return "hello world"
end
26.限制retry次数,改变重试频率并记录异常信息
retries = 0
begin
1/0
rescue ZeroDivisionError => e
raise if retries >= 3
retries += 1
puts retries
#需要记录错误的原因,下面的logger.warn方法在rails项目中进行使用
#logger.warn("do some writing")
sleep 3 #retry之前使用延时,一般在查找数据库中进行,一般使用指数退避算法5**retries
retry
end
不要无条件使用retry,这本质上是一个隐式循环,在代码块外面定义重试次数,当超出最大重试次数时重新抛出异常
while true
begin
1/0
rescue ZeroDivisionError => e
sleep
else
break
end
end
27.throw比raise更适合用来跳出作用域