从实战项目总结的Ruby小技巧(第三部分)

这是ruby小技巧系列的第三部分,这些技巧是我们从过去两年的实战经验中所收获的。第一部分涵盖了代码块(blocks)和范围对象(ranges)第二部分讨论了拆分重构以(destructuring)及类型转换(type conversions)

异常(Exceptions)

处理异常是富有技巧性的,你非常容易掉入自己挖的坑内并且难以走出。下面有一下规则可以借鉴,它会让你的代码更加容易调试,虽然你得付出一些小小的努力,但是你会得到很大的回报。

首先,不要使用rescue修饰符。这里有一个例子,当你在获取数组中的一个元素时,如果发生发生错误,将返回nil。

但是我们不知道它隐藏了怎样的错误。参数array有可能为nil,参数index可能为nil,参数index有可能为string,你有可能将参数array的名字写错,或者任何你可能想象到的任何错误。

这意味着我们不能从阅读代码得到代码的意图,同时意料之外的错误不可能展现出来。

但是,如果在执行时输入nil将会发生意料之外的错误。这种情况下,我们很难发现导致错误的原因是错误的传入了nil。

使用标准形式的rescue可以减少你很多痛苦,通过捕获我们期望的异常类型,或者是输出异常信息。

rescue => erescue StandardError => e的简写形式,就像单独的rescuerescue StandardError的简写形式。这意味着只能捕获StandardError或StandardError的子类(subclasses)。通常,你应该按照Ruby的惯例,只捕获StandardError以及它的子类(subclasses),永远不要去捕获Exception。捕获Exception意味着你将捕获到你不想要去捕获的异常,例如SystemExit,当你的程序请求退出的时候它将被抛出(raise)。如果你捕获这个异常,那么当你结束程序的时候将不能正常退出。

同样的,当你抛出异常的时候应该抛出StandardError的子类(subclasses),否则你的异常将不能被异常处理机制正确的处理。

在你的项目中定义一种通用的错误类是一种很好的做法,这种做法在gem中也得到了很好的应用,其他的错误类从通用的错误类继承而来。

通过这种方式,你可以使用rescue MyProject::Error捕获所有的异常,或者你可以指定特定的异常。

一种更加友好,更加易于阅读异常列表的方式是利用class.new方法来定义异常。Class.new通过继承作为参数的类来定义一个新类。生成的新类赋给一个常量时,常量将作为类的名称。

使用这种方式你要面对的一个问题是,并不是在你代码中所产生的所有异常都是通过代码直接抛出的。例如,当你进行HTTP请求的时候,可能在连接的时候抛出异常。

现在,你可以在代码中捕获这些异常,同时抛出你自己定义类型的异常。但是,你会丢失有价值的调试信息:产生异常异常的原始类,异常信息和调用堆栈。

Ruby允许你实现一个类(class)或模块(module)应用于rescue关键字作为期待的异常类型。rescue关键字使用case相等性操作符#===去比价传递的参数和期待的异常类型。对于类(class),当参数是异常类型的实例对象或者子类的实例对象则返回true 。对于模块(module),当参数混入(include)了模块或者参数扩展(extend)了模块则返回true。

因此,如果你将基本异常定义为一个模块(module),同时将它混入(include)到特定的异常类中,这些异常类将具有像上面所讲到的异常类的行为。你也可以将代码中引发异常的任意异常类通过扩展基本异常(extend)打上“标签”,这样这些异常就可以通过rescue关键字和基本异常进行捕获,同时跟踪它的原始异常类,异常信息以及调用堆栈。(你将不能引发基本异常,但这通常是一种很好的做法,你应该抛出特定的异常类)

这种方式的另一种用法是作为两种不同服务的适配器类(例如数据库客户端),映射不同种类的异常到相同的分类。因此,当在适配器下切换不同的数据库客户端后,rescue仍然工作。

然而这种技术有一个小的缺点。#extend方法调用将会使Ruby的全局方法缓存失效,在每次调用的时候会降低程序的速度。我们将会在Ruby2.1中得到一个更加优秀的方法缓存失效,让我们不用去担心任何事情。但即使是现在这仍是一项有效和强大的技术。

 

模块(Module)

模块在Ruby中有多种用途。或许最常见的就是作为命名空间(name-spacing),另外一种主要的用途是作为混入(mixin)。

像Enumerable和Comparabel一样的模块是非常神奇的,可以很容易的让你的类增加复杂的行为,通过在类中定义一些简单的方法同时混入(mixin)这些模块。另外,模块也拥有一些其他的用途。

有时候,你有一系列的方法非常接近函数,它们接受输入,返回结果,它们不会对当前对象self做任何操作。模块可以作为一种很好的方式可以将这些方法集中在一起。

这里有一个在我们项目中的例子(这里还有一些其他更多的方法,由于特殊原因不太适合和大家分享)。

在模块开始的module_function声明是一个方法可见性修饰符,像publicprivateprotected。它使方法直接调用时成为模块的类方法(class method),当模块被混入时成为一个私有实例方法(private instance method)。

module_function改为extend self可以得到相同的效果。

这里,模块的实例方法也同时作为类方法。不同的是你可以使用其他方法可见性修饰符,使方法在模块中为私有(private)的,在实例方法中为公有(public)的(当使用module_function修饰符时,所有的方法作为模块为公有的,作为实例方法为私有的)。

模块对于处理单例对象来说也是非常方便的,你不用去浪费时间去阻止多余一个对象被创建,或者考虑怎样去获取引用,在Ruby中这些都是内置的。

让我们进入第四部分。

1 2 收藏 评论

关于作者:geekerzp

Ruby Geek (新浪微博:@geekerzp) 个人主页 · 我的文章

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部