`
andyhu1007
  • 浏览: 199458 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Rails每周一题(十二): ruby的异常机制

阅读更多

在现实世界中,所有程序都会出错。一个优秀的程序可以预期错误的发生,并且优雅地处理它们。

 

一种错误处理的方法是:使用返回码。举个例子,我们在使用open方法打开文件,文件不存在时就会出错。我们可以使用一个特殊的返回码来标识这个错误。

 

但这种处理方式的问题是:管理这些错误代码会显得非常复杂。比如,我们调用了open,read并最终调用close方法,每一个方法都会返回不同的错误代码,我们需要在调用的外层次使用复杂难懂的代码来管理和区分这些不同的错误代码。

 

异常机制很好地解决了上述错误处理方法的问题:异常把错误信息打包进一个类中,在抛出一个异常之后,异常会自动在调用栈中‘上浮’,直到遇见声明可以处理相应类型异常的代码。

 

让我们来看看Ruby中的异常机制有何新鲜之处吧。

 

Ruby的异常类型层级

 

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit
 fatal
 

当需要抛出一个异常时,可以使用一个内置的异常类,也可以通过继承StandardError类来实现自己的异常类。

 

 

异常处理

 

先看一个简单例子:

 

opFile = File.open(opName, "w")
begin
  # Exceptions raised by this code will
  # be caught by the following rescue clause
  while data = socket.read(512)
    opFile.write(data)
  end

rescue SystemCallError
  $stderr.print "IO failed: " + $!
  opFile.close
  File.delete(opName)
  raise
end
 

我们用begin,rescue,end把可能抛出异常的代码以及处理异常的代码包围起来,特别是rescue语句声明了它可以处理的异常类型,rescue之后的代码就是处理异常的代码。

 

异常对象被抛出之后,存于一个全局变量:$1中。可以看到异常处理代码最后调用了raise,它代表把同一个异常再次抛出。

 

在一个异常处理块中,可以有多个rescue语句以拥有对不同异常的不同处理,同时一个rescue语句也可以声明捕获多个异常类型。如下面的代码一样:

 

begin
  eval string
rescue SyntaxError, NameError => boom
  print "String doesn't compile: " + boom
rescue StandardError => bang
  print "Error running script: " + bang
end

 

到处使用$1不是个好办法,我们可以像上面的代码一样把异常赋予一个变量。

 

rescue可以不带参数,它的默认参数是StandardError。

 

begin
  eval string
rescue
  print "Error running script: " + $1
end

 

rescue后面不仅仅可以带异常类型参数,也可以是任意的表达式,只要这个表达式返回一个异常类型即可。

 

这个异常处理是如何找到匹配的异常处理块的呢?其实这个原理跟case语句差不多,它通过$1.kind_of?(parameter)语句来识别和匹配异常处理块。

 

清理

 

很多时候,无论程序抛异常与否,我们都得保证在最后做一些清理工作。比如一段从文件读取数据的代码,无论读取是否成功,我们都得保证最后关闭这个文件。我们可以通过在任何的退出点关闭文件来解决这个问题,但这样做非常繁琐,也不能保证照顾到了每个退出点。所以,我们需要一种方法来保证。这就是ensure的作用:

 

f = File.open("testfile")
begin
  # .. process
rescue
  # .. handle error
ensure
  f.close unless f.nil?
end
 

在上面代码中,可以加入一个else以在没有异常抛出的情况下做一些处理,比如:

 

f = File.open("testfile")
begin
  # .. process
rescue
  # .. handle error
else
  puts "Congratulations-- no errors!"
ensure
  f.close unless f.nil?
end
 

重试

 

有些时候,异常抛出之后我们希望它可以重试一次或者采用另外一种方式重新尝试。这就是retry的作用:

 

@esmtp = true


begin
  # First try an extended login. If it fails because the
  # server doesn't support it, fall back to a normal login


  if @esmtp then
    @command.ehlo(helodom)
  else
    @command.helo(helodom)
  end


rescue ProtocolError
  if @esmtp then
    @esmtp = false
    retry
  else
    raise
  end
end

 

抛出异常

 

之前我们都是在处理别人抛出的异常,接下来看看如何抛出我们自己的异常。

 

raise
raise "bad mp3 encoding"
raise InterfaceException, "Keyboard failure", caller

 

第一种方式:重新抛出当前的异常,或者如果没有当前异常,则抛出一个RuntimeError。

 

第二种方式:抛出一个以参数为异常信息的RuntimeError。

 

第三种方式:抛出一个第一个参数类型的异常,以第二个参数为异常的信息,最后一个参数指定堆栈轨迹。可以看到,这种方式在堆栈轨迹中忽略了当前处。我们也可以在堆栈轨迹中去除更多的调用点,比如:

 

raise ArgumentError, "Name too big", caller[1..-1]

 

去除了当前、以及当前的调用者两个调用点。

 

赋予异常更多的信息

 

有时候,我们需要赋予异常除了message之外的一些信息,以便根据这些信息的不同来处理异常。

 

比如:

 

class RetryException < RuntimeError
  attr :okToRetry
  def initialize(okToRetry)
    @okToRetry = okToRetry
  end
end

def readData(socket)
  data = socket.read(512)
  if data.nil?
    raise RetryException.new(true), "transient read error"
  end
  # .. normal processing
end

begin
  stuff = readData(socket)
  # .. process stuff
rescue RetryException => detail
  retry if detail.okToRetry
  raise
end
 

Catch和Throw

 

在有些时候,rescue、raise机制并不能很好地满足需求,比如当我们需要跳出一个很深的嵌套结构时。这时候,就是catch和throw发挥作用的时候。

 

catch (:done)  do
  while gets
    throw :done unless fields = split(/\t/)
    songList.add(Song.new(*fields))
  end
  songList.play
end
 

Catch定义了一个block,这个block以一个symbol或者一个字符串为名字。这个block正常执行,直到遇见一个throw。遇见throw之后,程序会一直回朔,直到找到一个匹配symbol的catch,并结束这个catch block。

 

在上面这个例子中,当遇到throw时,程序会跳出while循环,并且跳过songList.play等其它语句直到catch block的结尾。

 

throw方法还可以附带一个可选的参数,此参数会作为catch block的返回值。

 

再来看一个例子:

 

def promptAndGet(prompt)
  print prompt
  res = readline.chomp
  throw :quitRequested if res == "!"
  return res
end


catch :quitRequested do
  name = promptAndGet("Name: ")
  age  = promptAndGet("Age:  ")
  sex  = promptAndGet("Sex:  ")
  # ..
  # process information
end

 

上面代码会处理用户输入,直到遇到一个!符号。

 

更多请参见:http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_exceptions.html

分享到:
评论
1 楼 netfork 2009-06-25  
顶。楼主的系列文章都写的不错。。。。收藏了。

相关推荐

    Web开发:Ruby on Rails.pdf

    Web开发:Ruby on Rails.pdf

    rails-docker-compose:Ruby on Rails的Docker开发环境

    截至2018年2月2日在当前Ruby和Rails上进行了测试:Ruby 2.5.0 ,Rails 5.1.4 在MacOS和Fedora Linux上进行了测试,因为这正是我碰巧使用的。 这是我用于客户工作和自己的项目的个人配置。 这是几个深夜时间使一切...

    Ruby on Rails入门经典代码

    Ruby on Rails,简称Rails,是基于Ruby语言的一个开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在使Web开发过程更加高效、简洁。本压缩包中的"Ruby on Rails入门经典代码"提供了新手学习...

    Ruby on Rails Guides v2 - Ruby on Rails 4.2.5

    ### Ruby on Rails Guides v2 - Ruby on Rails 4.2.5 #### 一、重要概念及基础假设 - **重要概念**:本指南旨在帮助读者深入理解Ruby on Rails(以下简称Rails)4.2.5版本的核心功能与最佳实践。 - **基础假设**:...

    rails_exception_handler:Ruby on Rails的异常处理

    您可以将此异常处理程序挂接到所有Rails应用程序中,并将异常报告收集到一个位置。 异常处理程序仅包含后端,您必须创建自己的前端才能查看和管理错误报告。 Rails Engine的管理界面是界面的简单添加,或用作构建您...

    Ruby on Rails入门例子

    Ruby on Rails,简称Rails,是一种基于Ruby语言的开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在使Web开发过程更加高效、简洁。本篇将通过一个入门实例,深入探讨Rails的基本概念和核心...

    Ruby on Rails安装指南(Ruby 1.8.6+Rails 2.0.2)

    知识点1:Ruby 安装 * 下载 Ruby One-Click Installer 版本 * 安装 Ruby * 检查 Ruby 版本 知识点2:Rails 安装 * 下载 Rails 2.0.2 版本 * 安装 Rails * 检查 Rails 版本 知识点3:Mongrel 安装 * 下载 ...

    从零到英雄:一步步部署你的Ruby on Rails应用

    3. **自动内存管理**:Ruby 有垃圾回收机制,可以自动管理内存使用。 4. **闭包和迭代器**:Ruby 支持闭包(blocks)和迭代器(iterators),使得循环和迭代操作更加灵活。 5. **Mixin模块**:Ruby 允许使用 mixin ...

    rails-reverse-proxy:Ruby on Rails的反向代理

    Rails-反向代理Ruby on Rails的反向代理。 反向代理接受来自客户端的请求,将其转发到可以满足该请求的服务器,然后将服务器的响应返回给客户端安装你知道该怎么做。 在您的Gemfile中gem 'rails-reverse-proxy' 然后...

    Ruby的垃圾处理大师:深入垃圾回收机制

    Ruby是一种高级、面向对象的编程语言,由日本开发者松本行弘(Yukihiro "Matz" Matsumoto)在1995年...7. **Web开发框架**:Ruby on Rails是一个流行的服务器端Web应用框架,它遵循MVC(模型-视图-控制器)架构模式。

    ruby on rails最新版

    Ruby on Rails,简称Rails,是基于Ruby编程语言的一个开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,旨在提高开发效率和代码的可读性。Rails以其“约定优于配置”(Convention over Configuration)...

    NetBeans Ruby and Rails IDE with JRuby 2009

    总结来说,《NetBeans Ruby and Rails IDE with JRuby 2009》是一本详尽介绍了如何使用NetBeans作为Ruby和Rails开发工具的指南。它不仅覆盖了从安装到日常开发的所有方面,还深入探讨了如何利用NetBeans的各种高级...

    Ruby On Rails中文教材(PDF)

    Ruby on Rails,简称Rails,是一款基于Ruby语言的开源Web应用框架,它遵循MVC(Model-View-Controller)架构模式,旨在简化Web应用程序的开发。Rails由David Heinemeier Hansson于2004年创建,它提倡“约定优于配置...

    vim-rails:rails.vim:Ruby on Rails电动工具

    这是一个庞大的(很好的方式)Vim插件,用于编辑Ruby on Rails应用程序。 轻松浏览Rails目录结构。 gf考虑上下文,并且知道部分信息,固定装置等等。 有两个命令:A (备用)和:R (相关),可在文件之间轻松跳转,...

    ruby rails demo

    ruby rails demo, rails 简单demo。 (1)到ruby官网:http://www.ruby-lang.org/en/下载window安装包,并安装, ruby版本: ruby -v (2)安装Rails3 gem install rails (3)安装sqlite3 gem install sqlite3-ruby 安装...

    ruby on rails 开发环境包(ruby1.8.7,rails2.2.3)

    Ruby on Rails,简称Rails,是由David Heinemeier Hansson创建的一种基于Ruby语言的开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,旨在提高开发效率和可读性。在这个开发环境包中,我们拥有Ruby ...

    Ruby+for+Rails

    **Ruby for Rails** Ruby是一种面向对象的动态编程语言,它以其简洁、优雅的语法和强大的元编程能力而闻名。在Web开发领域,Ruby与Rails框架的结合,即Ruby on Rails(RoR),开创了Web应用的新纪元。Ruby on Rails...

    rails_sample_app:Ruby on Rails 教程

    Ruby on Rails(简称Rails)是一种基于Ruby编程语言的开源Web开发框架,它遵循MVC(Model-View-Controller)架构模式,强调“约定优于配置”和DRY(Don't Repeat Yourself)原则,使得开发过程更加高效和简洁。...

    深入Ruby on Rails的心脏:MVC架构模式全解析

    3. **自动内存管理**:Ruby具有垃圾回收机制,帮助管理内存使用,减少内存泄漏的风险。 4. **反射能力**:Ruby支持反射操作,可以在运行时检查和修改对象的状态。 5. **灵活的语法**:Ruby的语法设计允许开发者以...

Global site tag (gtag.js) - Google Analytics