Ruby异常继承与动态生成的类
我是Ruby的新手,所以在理解我遇到的这个奇怪的异常问题时遇到了一些麻烦。我正在使用ruby-aaws gem访问Amazon ECS:http://www.caliban.org/ruby/ruby-aws/。这定义了一个类Amazon :: AWS:Error:
module Amazon module AWS # All dynamically generated exceptions occur within this namespace. # module Error # An exception generator class. # class AWSError attr_reader :exception def initialize(xml) err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' ) err_msg = xml.elements['Message'].text unless Amazon::AWS::Error.const_defined?( err_class ) Amazon::AWS::Error.const_set( err_class, Class.new( StandardError ) ) end ex_class = Amazon::AWS::Error.const_get( err_class ) @exception = ex_class.new( err_msg ) end end end end end
这意味着,如果我们收到类似" AWS.InvalidParameterValue"的错误代码,则会(在其异常变量中)产生一个新类" Amazon :: AWS :: Error :: InvalidParameterValue",它是StandardError的子类。
现在这是奇怪的地方。我有一些看起来像这样的代码:
begin do_aws_stuff rescue Amazon::AWS::Error => error puts "Got an AWS error" end
现在,如果do_aws_stuff抛出NameError,我的救援块就会被触发。似乎Amazon :: AWS :: Error不是生成的错误的超类,我猜是因为它是一个模块,所以一切都是它的子类吗?当然可以,如果我这样做:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error) => true
它说的是" true",我感到困惑,尤其是考虑到以下情况:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS) => false
这是怎么回事,我应该如何将AWS错误与其他类型的错误分开?我应该做类似的事情:
begin do_aws_stuff rescue => error if error.class.to_s =~ /^Amazon::AWS::Error/ puts "Got an AWS error" else raise error end end
这似乎异常怪异。引发的错误不是类AWSError,而是这样引发的:
error = Amazon::AWS::Error::AWSError.new( xml ) raise error.exception
因此,我希望从中抢救的异常是仅从StandardError继承的生成的异常类型。
为了澄清,我有两个问题:
- 为什么NameError(一个内置的Ruby异常)" kind_of?(Amazon :: AWS :: Error)"是一个模块?答:我在文件顶部说了" include Amazon :: AWS :: Error",认为它有点像Java导入或者C ++ include。这实际上是将" Amazon :: AWS :: Error"(现在和将来)中定义的所有内容添加到隐式Kernel类中,该类是每个类的祖先。这意味着任何事物都会通过
kind_of?(Amazon :: AWS :: Error)
。 - 如何最好地将" Amazon :: AWS :: Error"中动态创建的异常与其他地方的随机其他异常区分开来?
解决方案
回答
好吧,据我所知:
Class.new( StandardError )
正在创建一个以StandardError作为基类的新类,因此它根本不会成为Amazon :: AWS :: Error。它只是在该模块中定义的,这可能就是为什么它是kind_of?亚马逊:: AWS ::错误。可能不是kind_of? Amazon :: AWS,因为可能不是出于kind_of的目的而嵌套模块? ?
抱歉,我不太了解Ruby中的模块,但是最肯定的是,基类将是StandardError。
更新:顺便说一句,从ruby文档:
obj.kind_of?(class) => true or false Returns true if class is the class of obj, or if class is one of the superclasses of obj or modules included in obj.
回答
好的,我会在这里:
首先,模块不是类,它允许我们混合类中的行为。第二看下面的例子:
module A module B module Error def foobar puts "foo" end end end end class StandardError include A::B::Error end StandardError.new.kind_of?(A::B::Error) StandardError.new.kind_of?(A::B) StandardError.included_modules #=> [A::B::Error,Kernel]
有点儿?告诉我们,是的,Error确实具有所有A :: B :: Error行为(这是正常的,因为它包含A :: B :: Error),但是它不包括A :: B的所有行为,因此不是属于A :: B类型。 (鸭子打字)
现在,ruby-aws非常有可能重新打开NameError的超类之一,并在其中包含Amazon :: AWS:Error。 (猴子修补)
我们可以通过以下方式以编程方式找到模块在层次结构中的位置:
class Class def has_module?(module_ref) if self.included_modules.include?(module_ref) and not self.superclass.included_modules.include?(module_ref) puts self.name+" has module "+ module_ref.name else self.superclass.nil? ? false : self.superclass.has_module?(module_ref) end end end StandardError.has_module?(A::B::Error) NameError.has_module?(A::B::Error)
关于你的第二个问题,我没有比这更好的了
begin #do AWS error prone stuff rescue Exception => e if Amazon::AWS::Error.constants.include?(e.class.name) #awsError else whatever end end
(上面的代码无法按原样工作:名称包含模块前缀,而常量数组则不然。我们绝对应该联系lib维护者,AWSError类对我来说更像是工厂类:/)
我在这里没有红宝石偏光,并且caliban网站被公司的防火墙阻止了,所以我无法进行进一步的测试。
关于include:可能是在StandardError层次结构上进行猴子修补的事情。我现在不确定,但是最有可能在每个上下文之外的文件根目录中执行此操作,包括在Object或者Object元类上包含模块。 (这就是在IRB中会发生的情况,其中默认上下文是Object,不确定文件中是不是)
从模块上的镐:
在继续之前,关于include语句有几点要点。首先,它与文件无关。 C程序员使用称为#include的预处理程序指令在编译过程中将一个文件的内容插入另一个文件。 Ruby的include语句仅引用命名模块。如果该模块在单独的文件中,则在使用include之前,我们必须使用require将该文件拖入其中。
(编辑-我似乎无法使用此浏览器发表评论:/是,锁定平台)
回答
只是想插话:我同意这是lib代码中的错误。它可能应该显示为:
unless Amazon::AWS::Error.const_defined?( err_class ) kls = Class.new( StandardError ) Amazon::AWS::Error.const_set(err_class, kls) kls.include Amazon::AWS::Error end
回答
我们遇到的一个问题是,Amazon :: AWS :: Error :: AWSError
实际上并不是一个例外。调用raise
时,它会查看第一个参数是否响应exception
方法,并将使用该结果。调用exception
时,任何属于'Exception'子类的东西都将返回自身,因此我们可以执行`raise Exception.new(" Something is错误"))之类的事情。
在这种情况下," AWSError"将" exception"设置为属性读取器,它在初始化时将值定义为" Amazon :: AWS :: Error :: SOME_ERROR"。这意味着当我们调用raise Amazon :: AWS :: Error :: AWSError.new(SOME_XML)
Ruby最终会调用Amazon :: AWS :: Error :: AWSError.new(SOME_XML).exception将返回Amazon :: AWS :: Error :: SOME_ERROR
的实例。正如其他响应者之一所指出的那样,此类是StandardError的直接子类,而不是常见的Amazon错误的子类。在纠正此问题之前,让的解决方案可能是我们最好的选择。
我希望这有助于解释更多幕后实际情况。