ruby异常
背景
有一个通知服务,需要使用Mandrill的提供的邮件服务给客户发送邮件。开发时使用了mandrill_mailer这个GEM来实现和Mandrill服务的交互。
今天监控程序报警提及昨天的邮件发送量比平时低了不少。上线查了一下日志,发现是代码中一处exception没处理好,导致程序崩溃了。
分析
Ruby 中捕获非指定类型的异常,会使用如下方式来捕获异常。
|
|
根据官方文档的说明,不指定exception类型时,rescue默认捕获的是StandardError
类型的exception。
在我这个问题中,由于Mandrill的服务出现了问题,发送请求后,没有返回正常的json格式,而是返回了504 Gateway Time-out
的一段HTML, Gem mandrill_mailer解析JSON失败,抛出了自定义的Mandrill::Error
的异常。
Gem中抛异常的代码
|
|
Error异常定义的代码
|
|
可以看到,Mandrill::Error这个类型,是直接继承自Exception
的。而在原有的代码中,没有指定exception的类型,捕获不到这个exception,导致程序崩溃了。
在代码中指定固定类型的exception,解决该问题
|
|
深挖
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不指定异常类型
代码:
12345beginraise "error occur"rescueputs "here is string exception"end运行结果:
1here is string exceptionraise Exception, rescue Exception
代码:
12345beginraise Exception.new("error occur")rescue Exceptionputs "here is Exception exception"end运行结果:
1here is Exception exceptionraise字符串,rescue Exception
代码:
12345beginraise "error occur"rescue Exceptionputs "here is exception exception"end运行结果:
12# rescue Exception可以捕获任何异常here is exception exceptionraise Exception, rescue不指定异常类型
代码:
12345beginraise Exception.new("error occur")rescueputs "here is string exception"end运行结果:
1234# 没有捕获到异常,程序崩溃了Exception: error occurfrom (irb):39from /Users/carlshen/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'