动态生成类的Ruby异常继承

动态生成类的Ruby异常继承

Ruby exception inheritance with dynamically generated classes

我是Ruby的新手,所以在理解我遇到的这个奇怪的异常问题时遇到了一些麻烦。我正在使用ruby-aaws gem访问Amazon ECS:http://www.caliban.org/ruby/ruby-aws/。这定义了一个类Amazon :: AWS:Error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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的子类。

现在这是奇怪的地方。我有一些看起来像这样的代码:

1
2
3
4
5
begin
  do_aws_stuff
rescue Amazon::AWS::Error => error
  puts"Got an AWS error"
end

现在,如果do_aws_stuff抛出NameError,我的救援块将被触发。似乎Amazon :: AWS :: Error不是生成的错误的超类-我想因为它是一个模块,所以它是它的子类吗?当然,如果我这样做:

1
2
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error)
=> true

它说true,我感到困惑,尤其是考虑到以下情况:

1
2
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS)
=> false

这是怎么回事,我应该如何将AWS错误与其他类型的错误分开?我应该做类似的事情:

1
2
3
4
5
6
7
8
9
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类-像这样引发:

1
2
error = Amazon::AWS::Error::AWSError.new( xml )
raise error.exception

因此,我要从中查找rescue的异常是仅从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中动态创建的异常与其他地方的随机其他异常?


  • 好的,我会在这里帮助您:

    首先,模块不是类,它允许您混合类中的行为。第二看下面的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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。 (猴子修补)

    您可以通过以下方式以编程方式找到模块在层次结构中的位置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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)

    关于你的第二个问题,我没有比这更好的了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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,不确定文件中是否存在)

    从模块上的镐:

    A couple of points about the include statement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called #include to insert the contents of one file into another during compilation. The Ruby include statement simply makes a reference to a named module. If that module is in a separate file, you must use require to drag that file in before using include.

    (编辑-我似乎无法使用此浏览器发表评论:/是,锁定平台)


    只是想插话:我同意这是lib代码中的错误。它可能应该显示为:

    1
    2
    3
    4
    5
          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

    好吧,据我所知:

    1
    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.


    您遇到的一个问题是Amazon::AWS::Error::AWSError实际上并不是一个例外。调用raise时,它将查看第一个参数是否响应exception方法,并将使用该结果。当exception被调用时,任何属于exception子类的东西都将返回自身,因此您可以执行raise Exception.new("Something is wrong")之类的事情。

    在这种情况下,AWSErrorexception设置为属性读取器,它在初始化时将值定义为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错误的子类。在纠正此问题之前,让的解决方案可能是您最好的选择。

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


    推荐阅读