Héritage d'exception Ruby avec des classes générées dynamiquement
-
09-06-2019 - |
Question
Je suis nouveau dans Ruby, alors j’ai du mal à comprendre ce problème d’exception bizarre que je rencontre. J'utilise la gemme ruby-aaws pour accéder à Amazon ECS: http: //www.caliban .org / ruby ??/ ruby-aws / . Ceci définit une classe 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
Cela signifie que si vous obtenez un code d'erreur tel que AWS.InvalidParameterValue
, cela produira (dans sa variable d'exception) une nouvelle classe Amazon :: AWS :: Error :: InvalidParameterValue
qui est une sous-classe de StandardError
.
Maintenant, voici où ça devient bizarre. J'ai du code qui ressemble à ceci:
begin
do_aws_stuff
rescue Amazon::AWS::Error => error
puts "Got an AWS error"
end
Maintenant, si do_aws_stuff
lève un NameError
, mon bloc de secours est déclenché. Il semble qu'Amazon :: AWS :: Error ne soit pas la superclasse de l'erreur générée - je suppose que puisque c'est un module, tout en est une sous-classe? Certainement si je le fais:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error)
=> true
Il est écrit true
, ce qui me semble source de confusion, surtout compte tenu de ceci:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS)
=> false
Que se passe-t-il et comment suis-je censé séparer les erreurs AWS des autres types d'erreurs? Devrais-je faire quelque chose comme:
begin
do_aws_stuff
rescue => error
if error.class.to_s =~ /^Amazon::AWS::Error/
puts "Got an AWS error"
else
raise error
end
end
Cela semble exceptionnellement janky. Les erreurs renvoyées ne sont pas non plus de la classe AWSError - elles sont soulevées comme suit:
error = Amazon::AWS::Error::AWSError.new( xml )
raise error.exception
Par conséquent, les exceptions sur lesquelles je cherche à rescue
sont les types d'exception générés qui héritent uniquement de StandardError.
Pour clarifier, j'ai deux questions:
-
Pourquoi NameError, une exception Ruby intégrée, est-il un
kind_of? (Amazon :: AWS :: Error)
, qui est un module?
Réponse : j'avais indiqué queincluait Amazon :: AWS :: Error
en haut de mon fichier, pensant qu'il s'agissait en quelque sorte d'un import Java ou C ++. En fait, tout ce qui était défini dansAmazon :: AWS :: Error
(présent et futur) a été ajouté à la classe implicite du noyau, qui est un ancêtre de chaque classe. Cela signifie que n'importe quoi transmettraitkind_of? (Amazon :: AWS :: Error)
. -
Comment puis-je distinguer les exceptions créées de manière dynamique dans
Amazon :: AWS :: Error
des autres exceptions aléatoires provenant d'ailleurs?
La solution
Ok, je vais essayer d'aider ici:
Tout d'abord, un module n'est pas une classe, il vous permet de mélanger les comportements dans une classe. en second lieu, voir l'exemple suivant:
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? vous dit que oui, Error possède tous les comportements A :: B :: Error (ce qui est normal car il inclut A :: B :: Error), mais il n'inclut pas tous les comportements de A :: B et n'est donc pas du genre A :: B. (dactylographie)
Maintenant, il y a de très bonnes chances que ruby-aws rouvre l'une des superclasses de NameError et inclue Amazon :: AWS: Error. (correction de singe)
Vous pouvez rechercher par programmation où le module est inclus dans la hiérarchie avec les éléments suivants:
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)
En ce qui concerne votre deuxième question, je ne vois rien de mieux que
begin
#do AWS error prone stuff
rescue Exception => e
if Amazon::AWS::Error.constants.include?(e.class.name)
#awsError
else
whatever
end
end
(edit - le code ci-dessus ne fonctionne pas tel quel: nom inclut le préfixe de module, ce qui n'est pas le cas des tableaux de constantes. Vous devez absolument contacter le mainteneur de la bibliothèque. La classe AWSError ressemble plus à une classe usine: / )
Je n'ai pas de ruby-aws ici et le site de caliban est bloqué par le pare-feu de la société, je ne peux donc pas en tester davantage.
En ce qui concerne l’inclusion: il s’agit peut-être de la correction du singe sur la hiérarchie StandardError. Je ne suis plus sûr de le savoir, mais le faire à la racine d'un fichier en dehors de chaque contexte inclut probablement le module sur Object ou sur la métaclasse Object. (C’est ce qui se produirait dans IRB, où le contexte par défaut est Object, ce qui n’est pas sûr dans un fichier)
sur le pioche sur les modules :
Quelques remarques sur la déclaration include avant de poursuivre. Premièrement, cela n’a rien à voir avec des fichiers. Les programmeurs C utilisent une directive de préprocesseur appelée #include pour insérer le contenu d’un fichier dans un autre pendant la compilation. L'instruction Ruby include fait simplement référence à un module nommé. Si ce module se trouve dans un fichier séparé, vous devez utiliser require pour faire glisser ce fichier avant d'utiliser include.
(modifier - il semble que je ne puisse pas commenter à l'aide de ce navigateur: / yay pour les plateformes bloquées)
Autres conseils
Eh bien, d'après ce que je peux dire:
Class.new( StandardError )
est en train de créer une nouvelle classe avec StandardError comme classe de base, donc ce ne sera pas du tout Amazon :: AWS :: Error. Il est juste défini dans ce module, ce qui explique probablement pourquoi c'est un kind_of? Amazon :: AWS :: Error. Ce n'est probablement pas un genre de? Amazon :: AWS car les modules ne sont peut-être pas imbriqués à des fins de kind_of? ?
Désolé, je ne connais pas très bien les modules en Ruby, mais la classe de base sera certainement StandardError.
UPDATE : à propos, des ruby ??docs :
obj.kind_of? (class) = > vrai ou faux
Renvoie true si class est la classe d'obj ou si class est l'une des superclasses obj ou des modules inclus dans obj.
Je voulais juste ajouter quelque chose: je conviens que c'est un bogue dans le code de la bibliothèque. Il convient probablement de lire:
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
Un des problèmes que vous rencontrez est que Amazon :: AWS :: Error :: AWSError
n'est pas réellement une exception. Lorsque raise
est appelé, il cherche à savoir si le premier paramètre répond à la méthode exception
et utilisera le résultat de celui-ci à la place. Tout ce qui est une sous-classe de Exception
se retournera lui-même lorsque exception
sera appelé afin que vous puissiez faire des choses comme lever Exception.new ("Quelque chose ne va pas")
.
Dans ce cas, AWSError
a exception
configuré en tant que lecteur d'attribut dont il définit la valeur lors de l'initialisation à quelque chose comme Amazon :: AWS ::. Error :: SOME_ERROR
. Cela signifie que lorsque vous appelez augmentez Amazon :: AWS :: Error :: AWSError.new (SOME_XML)
, Ruby finit par appeler Amazon :: AWS :: Error :: AWSError.new (SOME_XML ) .exception
qui retournera une instance de Amazon :: AWS :: Error :: SOME_ERROR
. Comme l’a souligné l’un des autres répondants, cette classe est une sous-classe directe de StandardError
au lieu d’être une sous-classe d’une erreur Amazon commune. En attendant que cela soit corrigé, la solution de Jean est probablement votre meilleur choix.
J'espère que cela a permis d'expliquer davantage ce qui se passe réellement dans les coulisses.