Наследование исключений в Ruby с динамически генерируемыми классами - PullRequest
3 голосов
/ 16 сентября 2008

Я новичок в Ruby, поэтому у меня возникли проблемы с пониманием этой странной проблемы исключений, с которой я столкнулся. Я использую гем ruby-aaws для доступа к 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 ++ include. На самом деле это было добавление всего, что определено в Amazon::AWS::Error (настоящее и будущее), к неявному классу ядра, который является предком каждого класса. Это означает, что что-нибудь пройдет kind_of?(Amazon::AWS::Error).

  2. Как лучше отличить динамически создаваемые исключения в Amazon::AWS::Error от случайных других исключений из других мест?

Ответы [ 4 ]

5 голосов
/ 16 сентября 2008

Хорошо, я постараюсь помочь здесь:

Во-первых, модуль не является классом, он позволяет смешивать поведение в классе. второй см. следующий пример:

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? говорит, что да, Error обладает поведением All A :: B :: Error (что является нормальным, поскольку оно включает A :: B :: Error), однако оно не включает в себя все поведение из A :: B и, следовательно, не является типа A :: B. (утка печатает)

Теперь есть очень хороший шанс, что ruby-aws повторно откроет один из суперклассов NameError и включит Amazon :: AWS: там есть Ошибка. (обезьяна исправления)

Вы можете программно узнать, где модуль включен в иерархию, с помощью следующего:

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

(edit - приведенный выше код не работает как есть: имя включает префикс модуля, который не относится к массивам констант. Вам определенно следует обратиться к сопровождающему lib, класс AWSError для меня больше похож на класс фабрики: / )

У меня нет ruby-aws, и сайт caliban заблокирован брандмауэром компании, поэтому я не могу тестировать дальше.

Относительно включения: это может быть то, что делает обезьяны, исправляющие иерархию 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.

(редактировать - я не могу комментировать с помощью этого браузера: / yay для заблокированных на платформах)

1 голос
/ 16 сентября 2008

Просто хотел вмешаться: я бы согласился, что это ошибка в коде 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
1 голос
/ 16 сентября 2008

Ну, из того, что я могу сказать:

Class.new( StandardError )

Создает новый класс с StandardError в качестве базового класса, так что это вовсе не будет Amazon :: AWS :: Error. Он только что определен в этом модуле, что, вероятно, поэтому является своего рода kind_of? Amazon :: AWS :: Ошибка. Это, вероятно, не добрый? Amazon :: AWS, потому что, возможно, модули не вкладываются для целей kind_of?

Извините, я не очень хорошо знаю модули в Ruby, но наиболее определенно базовый класс будет StandardError.

ОБНОВЛЕНИЕ : Кстати, из рубиновых документов :

obj.kind_of? (Class) => true или false

Возвращает true, если class является классом obj, или если class является одним из суперклассов obj или модулей, включенных в obj.

0 голосов
/ 05 октября 2009

Одна проблема, с которой вы сталкиваетесь, заключается в том, что Amazon::AWS::Error::AWSError на самом деле не является исключением. Когда вызывается raise, он проверяет, отвечает ли первый параметр методу exception, и будет использовать результат этого вместо этого. Все, что является подклассом Exception, вернется само, когда вызывается exception, так что вы можете делать такие вещи, как raise Exception.new("Something is wrong").

В этом случае 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. До тех пор, пока это не будет исправлено, решение Джин, вероятно, будет вашим лучшим выбором.

Надеюсь, это помогло объяснить больше того, что на самом деле происходит за кулисами.

...