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更适合用来跳出作用域

results matching ""

    No results matching ""