【问题标题】:Ruby exception inheritance with dynamically generated classesRuby 异常继承与动态生成的类
【发布时间】:2010-09-09 08:43:05
【问题描述】:

我是 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

所以我要查找的异常来自 rescue 是仅从 StandardError 继承的生成异常类型。

为了澄清,我有两个问题:

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

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

【问题讨论】:

  • 很难理解您的问题,因为类型 Amazon::AWS::Error 似乎与您在问题顶部提供的源代码无关。可能是粘贴错误?
  • 糟糕,我忘记了“AWS”模块。我剪掉了文件中很多不相关的部分,我一定会错过它。所以我们有一个 Amazon 模块,然后是一个 AWS 模块,然后是一个 Error 模块,然后是一个 AWSError 类,它在 Amazon::AWS::Error 模块中设置常量。

标签: ruby exception metaprogramming


【解决方案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。

【讨论】:

  • 是的,我知道基类是StandardError。让我抓狂的是NameError.new.kind_of?(Amazon::AWS::Error) 是真的。为什么会这样?
  • 不,我不相信。 ;-) Amazon::AWS::Error 是一个模块。它与 NameError 不是同一个类。它不是 NameError 的超类。据我所知,NameError 不包含 Amazon::AWS::Error 模块。
  • 但它是在该模块中创建的,因此可能会隐式包含该模块
  • 不,NameError 是一个内置的 Ruby 异常,不是在 Amazon::AWS::Error 中创建的。
  • 嗯,是的,我在那里有点困惑......另外我做了一些测试,但无法重现这种行为......非常奇怪......也许其他地方的猴子修补可能会导致它?那或者我看到明显的为时已晚;-)
【解决方案2】:

好的,我会尽力在这里提供帮助:

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

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 类在我看来更像是一个工厂类:/ )

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

关于 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.

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

【讨论】:

  • 我认为你是对的......我尝试了问题中提供的确切代码,但它没有产生真实......所以必须有猴子补丁......跨度>
  • 嗯,不完全是。因此,AWS 的东西绝对是 everything 的继承者。我怀疑我在文件顶部说“包括 Amazon::AWS::Error”搞砸了。这是如何运作的?它只是将该模块包含在所有内容的基础中?
  • 不幸的是(正如我刚刚在我的问题中澄清的那样)库抛出了生成的异常,而不是 AWSError。所以出现的异常看起来像 Amazon::AWS::Error::InvalidParameterValue。
  • 感谢所有帮助。我将给库作者发邮件,询问他是否可以切换到使用通用基类,并在此之前使用您提出的代码。
【解决方案3】:

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

【讨论】:

  • 最好有一个 AWSException 类,它是 StandardError 的子类,即 Amazon::AWS::AWSException
【解决方案4】:

您遇到的一个问题是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 的直接子类,而不是亚马逊常见错误的子类。在此问题得到纠正之前,Jean 的解决方案可能是您最好的选择。

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

【讨论】:

    猜你喜欢
    • 2018-11-30
    • 2016-05-28
    • 1970-01-01
    • 2019-01-02
    • 2016-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-11
    相关资源
    最近更新 更多