ruby异常
背景
有一个通知服务,需要使用Mandrill的提供的邮件服务给客户发送邮件。开发时使用了mandrill_mailer这个GEM来实现和Mandrill服务的交互。
今天监控程序报警提及昨天的邮件发送量比平时低了不少。上线查了一下日志,发现是代码中一处exception没处理好,导致程序崩溃了。
分析
Ruby 中捕获非指定类型的异常,会使用如下方式来捕获异常。
1 | begin |
根据官方文档的说明,不指定exception类型时,rescue默认捕获的是StandardError类型的exception。
在我这个问题中,由于Mandrill的服务出现了问题,发送请求后,没有返回正常的json格式,而是返回了504 Gateway Time-out的一段HTML, Gem mandrill_mailer解析JSON失败,抛出了自定义的Mandrill::Error的异常。
Gem中抛异常的代码
1 | begin |
Error异常定义的代码
1 | module Mandrill |
可以看到,Mandrill::Error这个类型,是直接继承自Exception的。而在原有的代码中,没有指定exception的类型,捕获不到这个exception,导致程序崩溃了。
在代码中指定固定类型的exception,解决该问题
1 | begin |
深挖
Ruby中关于如何throw和catch exception,stackoverflow和ruby-doc.org上的这几个网页值得细读
- Why is it bad style to `rescue Exception => e` in Ruby?
- What is the difference between `raise “foo”` and `raise Exception.new(“foo”)`?
- Exception
梳理了一下,罗列如下:
- 三种常用的exception类
RuntimeError是StandardError的一个子类,而StandardError又是Exception的一个子类。- 直接raise,抛出的是
RuntimeError的异常 - 默认的rescue,捕获的是
StandardError的异常 Exception包含了全部的异常,ruby中全部异常都可以使用rescue Exception => e这种写法来捕获。- 但强烈建议不要直接粗暴的指定基类
Exception来捕获异常rescue Exception时,会捕获所有的异常,包括SyntaxError,LoadError和SignalException等- 捕获了
SignalException,会导致除了kill -9的其他信号量失效,包括Ctrl + C - 捕获
SyntaxError,调用eval时,即使失败了也不会有提示
- 编写库和Gem的自定义异常时,强烈反对直接继承自基类
Exception,因为这会导致使用者在没有指定异常类型时,无法成功捕获异常,导致程序崩溃。
小实验
raise字符串,rescue不指定异常类型
代码:
1
2
3
4
5begin
raise "error occur"
rescue
puts "here is string exception"
end运行结果:
1
here is string exception
raise Exception, rescue Exception
代码:
1
2
3
4
5begin
raise Exception.new("error occur")
rescue Exception
puts "here is Exception exception"
end运行结果:
1
here is Exception exception
raise字符串,rescue Exception
代码:
1
2
3
4
5begin
raise "error occur"
rescue Exception
puts "here is exception exception"
end运行结果:
1
2# rescue Exception可以捕获任何异常
here is exception exceptionraise Exception, rescue不指定异常类型
代码:
1
2
3
4
5begin
raise Exception.new("error occur")
rescue
puts "here is string exception"
end运行结果:
1
2
3
4# 没有捕获到异常,程序崩溃了
Exception: error occur
from (irb):39
from /Users/carlshen/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'