Наследование исключений Ruby с динамически генерируемыми классами
-
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 .
Чтобы внести ясность, у меня есть два вопроса:
Почему NameError, встроенное в Ruby исключение, является
kind_of?(Amazon::AWS::Error)
, что такое модуль?
Ответ: Я сказалinclude Amazon::AWS::Error
в верхней части моего файла, думая, что это что-то вроде импорта Java или включения C ++.Что это на самом деле сделало, так это добавило все, определенное вAmazon::AWS::Error
(настоящее и будущее) к неявному классу ядра, который является предком каждого класса.Это означает что угодно прошел быkind_of?(Amazon::AWS::Error)
.Как я могу наилучшим образом отличить динамически создаваемые исключения в
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.Пока это не будет исправлено, решение Джин, вероятно, будет вашим лучшим выбором.
Я надеюсь, что это помогло лучше объяснить, что на самом деле происходит за кулисами.