Ruby异常继承与动态生成的类

时间:2020-03-05 18:55:19  来源:igfitidea点击:

我是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错误的子类。在纠正此问题之前,让的解决方案可能是我们最好的选择。

我希望这有助于解释更多幕后实际情况。