Exceptions, Catch, and Throw

2024-06-09 09:38
文章标签 catch exceptions throw

本文主要是介绍Exceptions, Catch, and Throw,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们已经开发了一些代码,比较完美的是暂时还没有出现错误。每个库都可以成功调用,用户从不输入无效的数据,并且资源丰富且易获得。但事事无常。欢迎来到真实的世界。

在真实的世界中错误时常发生。好的程序和程序会预计到它们的出现并且合理地处理它们。不过要做到这点并是如同想像的那么简单。通常一段发现错误的代码是没有相应的上下文指导其如何进行下一步的。比如,当尝试打开一个不存在的文件时,有些环境是可以接受的,对另外一些环境却是重大的错误。你的文件处理模块是如何做的呢?

传统方式都是返回错误码。open
方法会返回指定值用于表达当前操作的失败。这个值通过调用过程传递回去,直到某人想对其做出反应为止。

这种处理方式的问题在于管理过多的错误代码会十分痛苦。如果一个函数按顺序分别调用了
openreadclose,每个方法都可能返回错误信息,这个函数的调用者应该如何从返回值中区分这些错误代码?

异常最大程度地解决了这个问题。异常可以让你将错误信息打包成一个对象。异常对象会自动传回调用栈,直到运行时系统发现它,并且能明确这种类型的异常如何处理时为止。

异常类

Exception 类及其子类的对象都可以将异常信息包装起来。Ruby
预定义了一个整洁的异常体系,详情可见在 91 页的表
8.1。如同稍后我们要了解的一样,这个体系让我们更容易考虑对异常的处理。

当你需要抛出异常时,你可以用预定义的 Exception 类,也可以用自己定义的。如果想自定义异常,需要将其作为 SandardError 的子类,或继承 SandardError 的子类。如果不这样做,自定义的异常默认是不会被捕捉的。

每个 Exception
都关联着信息字符串和栈内回溯信息。如果是你自定义的异常,你也可以添加额外信息。

处理异常

我们的点唱机通过 TCP
接口从网络下载歌曲。最简单的代码可以这样写:

opFile = File.open(opName, "w")
while data = socket.read(512)opFile.write(data)
end

如果在下载中途遇到重大的报错应该怎么办?可以确认的是,我们不希望在歌单中存储未传输完成的歌曲。

让我们添加一些异常处理的代码并看看对我们是否有帮助。我们用 begin/end
将可能会抛出异常的代码包围起来,并用 rescue 语法告诉 Ruby
我们需要处理的异常类型。在这个例子中,我们主要关注
SystemCallError 异常(当然,这也暗含了 SystemCallError
的异常子类),这就是需要通过 rescue
处理的事情。在错误处理的代码块中,我们不仅要报告错误,关闭和删除输出文件,还需要将异常抛出。

opFile = File.open(opName, "w")
begin# Exceptions raised by this code will# be caught by the following rescue clausewhile data = socket.read(512)opFile.write(data)endrescue SystemCallError$stderr.print "IO failed: " + $!opFile.closeFile.delete(opName)raise
end

当异常被抛出时,包括随后处理的异常,Ruby 都会将它们的 Exception
对象引用关联至全局变量 $!
(这个感叹号反应出我们面对代码出现的错误时表示的惊讶)。在之前的例子中,我们通过变量对错误信息进行格式化。

在关闭及删除文件之后,我们直接调用 raise
并且没有传递参数,它会通过 $!
抛出异常。这是一个有用的技巧,它允许你写的代码对异常进行过滤,将当前无法处理的异常抛出至更高层级。不过对于错误流程它也喜欢实现一个继承体系。

begin 代码块中你也会有多个 rescue 句式,每个 rescue
都可以指定多个异常抛出。在每个 rescue 语句的结尾你都会给
Ruby 一个局部变量名以接收匹配的异常。许多人发现这样比 $!
全局替换的方式可读性更高。

begineval string
rescue SyntaxError, NameError => boomprint "String doesn't compile: " + boom
rescue StandardError => bangprint "Error running script: " + bang
end"'"

Ruby 是如何决定哪个 rescue 语句应该执行的?它的执行方式和
case 声明十分相似。对 begin 代码块中的每个 rescue 语句而言,Ruby
依次将引发的异常与每个参数进行比较。如果引发的异常与参数匹配,Ruby
将执行 rescue 体的代码并停止查找。匹配操作是通过
$!.kind_of?(parameter)
方法实现,因此无论异常与参数是相同的类还是参数是异常的祖先类都将匹配成功。如果 rescue 是无参语句,会默认参数是 StandardError

如果没有任何 rescue 语句可以匹配,或异常是在 begin/end
代码块外被引发,Ruby
将移动堆栈并在调用者中寻找异常处理器,如果没有找到将延着调用链一直查找。

尽管 rescue 语句的参数代表 Exception
类的名字,但实际上这些参数可以是任意返回 Exception
类的表达式(也包括方法调用)。

打扫房间

有时你需要保证无论是否有异常被引起,某些流程都在代码块结束后再运行。比如,你可能需要在进入代码时开启文件,并且退出代码块时它会被关闭。

ensure 就可以实现这个功能。ensure 在最后一个 rescue
和代码块调用当前代码块完成之后调用。是否正常退出代码块并不影响它,无论是引发异常并被捕捉,还是被未捕捉的异常停掉,ensure
代码块都将继续运行。

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

else 语句也是类似地使用,尽管很少用到。如果使用了
else,它将会在 rescue 之后,在 ensure
之前运行。else
体中的代码只会在主体代码没有引起异常的情况下执行。

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

再做一次

有时你也许需要确认异常的原因。在这些例子中,你能够在 rescue 中使用
retry 声明重复整个 begin/end
代码块。我们清楚的是,当前这是一块巨大区域的无限循环,所以使用这个功能时需要当心(要将手指轻置于停止键)。

作为重试异常的样例,可以看看接下来这段改编至 Minero Aoki net/smtp.rb
库的代码。

@esmtp = truebegin# First try an extended login. If it fails because the# server doesn't support it, fall back to a normal loginif @esmtp then@command.ehlo(helodom)else@command.helo(helodom)endrescue ProtocolErrorif @esmtp then@esmtp = falseretryelseraiseend
end'

例子中的代码首先尝试使用 HELO 命令连接 SMTP
服务器,HELO
命令一般情况机器都是支持的。如果连接失败,代码将把 @estmp
变量设置为 false
并且再次尝试连接服务器。如果再次失将会向调用者抛出异常。

抛出异常

我们已经在其他地方抛出的异常处理上了解了许多。也是时候扭转局势主动进攻了。

你可以使用 Kernel::raise 方法抛出异常。

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

例子中的第一种形式是简单地将异常抛出(如果抛出时没有将异常作为参数默认是
RuntimeError)。这种方式通常在传递异常前拦截异常的处理器中使用。

第二种形式会创建新 RuntimeError
异常,通过传递的字符串设置它的信息。并且异常会被抛出到调用栈。

第三种形式会使用第一个参数创建异常,将第二个参数设置为异常信息,并根据堆栈追踪返回抛出给第三个参数。一般情况下第一个参数都是
Exception
体系中一个类的名字,也可以是这些类对象的引用。堆栈追踪一般通过
Kernel::caller 方法产生。

这有一些 raise 实际的例子。

raiseraise "Missing name" if name.nil?if i >= myNames.sizeraise IndexError, "#{i} >= size (#{myNames.size})"
endraise ArgumentError, "Name too big", caller

在最后的例子中,我们将当前例程从栈回溯中移除了,栈回溯通常在库模块中有用。我们可以更进一步,下面的代码会将两个例程从回溯中移除。

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

向异常中添加信息

你也可以定义自己的异常,它可以存储你需要从错误端暴露的信息。例如,确切的网络错误类型在环境中转瞬即逝。如果类似错误出现时环境没有问题,你便可以通过异常中的标识告知处理器,它应该再次尝试相同的操作。

class RetryException < RuntimeErrorattr :okToRetrydef initialize(okToRetry)@okToRetry = okToRetryend
end

可能有某处代码如下一样,一个转瞬即逝的错误发生了。

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

在调用栈的更高层级中我们将对异常进行处理。

beginstuff = readData(socket)# .. process stuff
rescue RetryException => detailretry if detail.okToRetryraise
end

捕捉和抛出

当程序运行出错时通过 raiserescue
机制放弃执行是种良好的方式,它可以很好地从深层次嵌套结构从跳出。这也是
catchthrow 迟早用到的地方。

catch (:done)  dowhile getsthrow :done unless fields = split(/\t/)songList.add(Song.new(*fields))endsongList.play
end

catch 通过预设的名字定义了一个代码块(也许是一个 Symbol
也可以是一个字符串)。除非有 throw
的发生,否则这个代码块将正常运行。

当 Ruby 运行至 throw
时,它将把调用栈压缩,以寻找有相同匹配符号的 catch
代码块。当寻找到后,Ruby
会将调用栈解压至目标点并暂停代码块。如果调用 throw
时选填了第二个参数,此参数将作为 catch
的值返回。因此,在之前的例子中如果输入不包含指定格式的内容,throw
将跳转至相同的 catch 结尾,不止是结束掉 while
循环同时也会跳过歌单的播放。

接下来的例子中,如果用户输入「!」我们将通过 throw
停止与用户的互动。

def promptAndGet(prompt)print promptres = readline.chompthrow :quitRequested if res == "!"return res
endcatch :quitRequested doname = promptAndGet("Name: ")age  = promptAndGet("Age:  ")sex  = promptAndGet("Sex:  ")# ..# process information
end

如同这个例子表明的一样,throw 不一定需要出现在 catch 的静态域中。


本文翻译自《Programming Ruby》,主要目的是自己学习使用,文中翻译不到位之处烦请指正,如需转载请注明出处

本章原文为 Exceptions, Catch, and
Throw

这篇关于Exceptions, Catch, and Throw的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1044816

相关文章

C++第四十七弹---深入理解异常机制:try, catch, throw全面解析

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】 目录 1.C语言传统的处理错误的方式 2.C++异常概念 3. 异常的使用 3.1 异常的抛出和捕获 3.2 异常的重新抛出 3.3 异常安全 3.4 异常规范 4.自定义异常体系 5.C++标准库的异常体系 1.C语言传统的处理错误的方式 传统的错误处理机制:

try -catch-finally的理解,同时在try-catch-finally中含有return和throws的理解

在没有try-catch或try-catch-finally的情况下,程序正常执行到某行,在这行报错后,这行后面的代码就不执行了,程序就停止了,中断了。 例如   在有try-catch或try-catch-finally 情况上,在某行执行错误,在try中这行下的代码不执行,try外的代码执行。当然是catch在没有做处理的情况下。如果catch中做了处理,在不影响当前程序下,try

C++异常处理: try,catch,throw,finally的用法

写在前面 所谓异常处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异常,希望调用者可以发现处理问题. 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使用过异常,但是你习惯使用异常了吗? 现在很多软件都是n*365*24小时运行,软件的健壮性至关重要.  内容导读本文包括2个大的异常实现概念:C++的标准异常和SEH异常. C++标

[置顶]C++异常处理:try,catch,throw,finally的用法

写在前面 所谓异常处理,即让一个程序运行时遇到自己无法处理的错误时抛出一个异常,希望调用者可以发现处理问题. 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制. 也许我们已经使用过异常,但是你习惯使用异常了吗? 现在很多软件都是n*365*24小时运行,软件的健壮性至关重要.  内容导读本文包括2个大的异常实现概念:C++的标准异常和SEH异常. C++标

BFS —— POJ 3278 Catch That Cow

对应POJ题目:点击打开链接 Catch That Cow Time Limit: 2000MS Memory Limit: 65536KTotal Submissions: 54686 Accepted: 17096 Description Farmer John has been informed of the location of a fugitive cow

ES6中try-catch

在ES6(ECMAScript 2015)中,try-catch 语句的语法和使用方式与在之前的ECMAScript版本中是一样的。try-catch 语句用于处理代码中可能发生的错误,确保程序的健壮性和用户体验。 基本语法 try { // 尝试执行的代码块 // 如果发生错误,则执行 catch 块中的代码 } catch (error) { // 处理错误 // error

【C++】try 语句捕获异常,catch子句处理异常

#include <iostream>#include <stdexcept>using namespace std;int main(){int i1, i2;while(cin >> i1 >> i2){try{if (i2 == 0)throw runtime_error("分母为0!");cout << "i1除以i2的结果是: " << i1/i2 << endl;}catch(ru

Promise中使用reject和throw Error的异同点

在JavaScript的异步编程中,Promise 是一个非常重要的概念。它代表了异步操作的最终完成(或失败)及其结果值。在这篇文章中,我们将探讨Promise的几种状态,以及reject和throw Error的异同,最后讨论在Promise中使用reject后代码的执行情况。 1. Promise的几种状态 Promise 对象有三种状态,分别是: Pending(进行中):这是初始状态

try-catch-finally中finally的使用

1.finally时可选的 2.finally中声明的时一定会被执行的代码,即使catch中又出现异常了,try中有return语句,catch中有return语句的情况 3.像数据库连接,输入输出流,网络编程Socket等资源,JVM时不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。

HTML:throw 声明

<html> <body> <script type="text/javascript"> var x=prompt("请输入 0 至 10 之间的数:") try {  if(x>10)    throw "Err1"  else if(x<0)   throw "Err2" else if(isNaN(x))   throw "Err3" }