Наследование исключений Ruby с динамически генерируемыми классами

StackOverflow https://stackoverflow.com/questions/70074

  •  09-06-2019
  •  | 
  •  

Вопрос

Я новичок в Ruby, поэтому у меня возникли некоторые проблемы с пониманием этой странной проблемы с исключениями, с которой я столкнулся.Я использую драгоценный камень ruby-aaws для доступа к 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, это создаст (в своей переменной exception) новый класс 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 from - это сгенерированные типы исключений, которые наследуются только от 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 из случайных других исключений из других источников?

Это было полезно?

Решение

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

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

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

(редактировать - приведенный выше код не работает как есть :имя включает в себя префикс модуля, который не относится к массивам констант.Вам обязательно следует обратиться к сопровождающему библиотеки, класс 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 для заблокированных платформ)

Другие советы

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

Class.new( StandardError )

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

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

Обновить:Кстати, из документации ruby:

obj.kind_of?(класс) => истина или ложь

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

Просто хотел вмешаться:Я бы согласился, что это ошибка в коде библиотеки.Вероятно, это должно гласить:

      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 wrong").

В данном случае, AWSError имеет exception настроен как считыватель атрибутов, значение для которого он определяет при инициализации примерно так Amazon::AWS::Error::SOME_ERROR.Это означает, что когда вы звоните raise Amazon::AWS::Error::AWSError.new(SOME_XML) Руби заканчивает тем, что звонит Amazon::AWS::Error::AWSError.new(SOME_XML).exception который вернет экземпляр Amazon::AWS::Error::SOME_ERROR.Как было указано одним из других респондентов, этот класс является прямым подклассом StandardError вместо того, чтобы быть подклассом распространенной ошибки Amazon.Пока это не будет исправлено, решение Джин, вероятно, будет вашим лучшим выбором.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top