使用动态生成的类进行Ruby异常继承

时间:2008-09-16 07:29:11

标签: ruby exception metaprogramming

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

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

所以我期待rescue的异常是生成的异常类型,它们只继承自StandardError。

澄清一下,我有两个问题:

  1. 为什么NameError是一个内置异常的Ruby,kind_of?(Amazon::AWS::Error),它是一个模块?
    答案:我在文件的顶部说include Amazon::AWS::Error,认为它有点像Java导入或C ++包含。这实际上做的是将Amazon::AWS::Error(现在和将来)中定义的所有内容添加到隐式内核类,它是每个类的祖先。这意味着任何都会通过kind_of?(Amazon::AWS::Error)

  2. 如何才能最好地区分Amazon::AWS::Error中动态创建的异常与其他地方的随机异常?

4 个答案:

答案 0 :(得分:5)

好的,我会尽力帮助:

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

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]

kind_of?告诉你是的,错误确实拥有All 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

(编辑 - 上面的代码不能正常工作:name包含模块前缀,这不是常量数组的情况。你肯定应该联系lib维护者,AWSError类对我来说看起来更像工厂类:/ )

我这里没有ruby-aws,并且校准站点被公司的防火墙阻止,所以我无法进一步测试。

关于include:可能是在StandardError层次结构上进行猴子修补的事情。我不确定,但最有可能在每个上下文之外的文件的根目录下执行它包括Object上的模块或Object元类。 (这是在IRB中会发生的情况,其中默认上下文是Object,在文件中不确定)

来自pickaxe on modules

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.

(编辑 - 我似乎无法使用此浏览器发表评论:/ yay for locked in platforms)

答案 1 :(得分:1)

嗯,从我所知道的:

Class.new( StandardError )

创建一个以StandardError为基类的新类,因此它根本不会是Amazon :: AWS :: Error。它只是在那个模块中定义,这可能是为什么它是kind_of?亚马逊AWS :: ::错误。它可能不是一种善意的? Amazon :: AWS因为可能为了kind_of而没有嵌套模块? ?

很抱歉,我不熟悉Ruby中的模块,但绝大多数基类都是StandardError。

更新:顺便提一句,from the ruby docs

  

obj.kind_of?(class)=>真或假

     

如果class是obj的类,或者如果class是obj中包含的obj或模块的超类之一,则返回true。

答案 2 :(得分:1)

只是想插入:我同意这是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

答案 3 :(得分:0)

您遇到的一个问题是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错误的子类。在这个问题得到纠正之前,Jean的解决方案可能是你最好的选择。

我希望这有助于解释更多幕后实际发生的事情。