Ereditarietà delle eccezioni Ruby con classi generate dinamicamente
-
09-06-2019 - |
Domanda
Sono nuovo in Ruby, quindi ho qualche difficoltà a comprendere questo strano problema di eccezioni che sto riscontrando.Sto utilizzando la gemma ruby-aaws per accedere ad Amazon ECS: http://www.caliban.org/ruby/ruby-aws/.Questo definisce una 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
Ciò significa che se ricevi un codice di errore simile AWS.InvalidParameterValue
, questo produrrà (nella sua variabile di eccezione) una nuova classe Amazon::AWS::Error::InvalidParameterValue
che è una sottoclasse di StandardError
.
Ora ecco dove diventa strano.Ho un codice che assomiglia a questo:
begin
do_aws_stuff
rescue Amazon::AWS::Error => error
puts "Got an AWS error"
end
Ora se do_aws_stuff
lancia a NameError
, il mio blocco di salvataggio viene attivato.Sembra che Amazon::AWS::Error non sia la superclasse dell'errore generato - immagino che, dato che è un modulo, tutto ne sia una sottoclasse?Certamente se lo faccio:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error)
=> true
Dice true
, che trovo confuso, soprattutto considerando questo:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS)
=> false
Cosa sta succedendo e come dovrei separare gli errori AWS da altri tipi di errori?Dovrei fare qualcosa del tipo:
begin
do_aws_stuff
rescue => error
if error.class.to_s =~ /^Amazon::AWS::Error/
puts "Got an AWS error"
else
raise error
end
end
Sembra eccezionalmente strano.Anche gli errori lanciati non appartengono alla classe AWSError: vengono sollevati in questo modo:
error = Amazon::AWS::Error::AWSError.new( xml )
raise error.exception
Quindi le eccezioni che sto cercando rescue
from sono i tipi di eccezione generati che ereditano solo da StandardError.
Per fare chiarezza avrei due domande:
Perché NameError, un'eccezione incorporata in Ruby, a
kind_of?(Amazon::AWS::Error)
, che è un modulo?
Risposta: avevo dettoinclude Amazon::AWS::Error
nella parte superiore del mio file, pensando che fosse una specie di importazione Java o inclusione C++.Ciò che in realtà ha fatto è stato aggiungere tutto ciò che è definito inAmazon::AWS::Error
(presente e futuro) alla classe Kernel implicita, che è un antenato di ogni classe.Questo significa nulla passerebbekind_of?(Amazon::AWS::Error)
.Come posso distinguere al meglio le eccezioni create dinamicamente in
Amazon::AWS::Error
da altre eccezioni casuali provenienti da altrove?
Soluzione
Ok, provo ad aiutarti qui:
Innanzitutto un modulo non è una classe, ti consente di mescolare i comportamenti in una classe.secondo vedere il seguente esempio:
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]
tipo?ti dice che sì, Error possiede tutto il comportamento di A::B::Error (il che è normale poiché include A::B::Error) tuttavia non include tutto il comportamento di A::B e quindi non lo è del tipo A::B.(anatra digitando)
Ora ci sono ottime possibilità che ruby-aws riapra una delle superclassi di NameError e includa Amazon::AWS:Error al suo interno.(rattoppo della scimmia)
Puoi scoprire a livello di programmazione dove il modulo è incluso nella gerarchia con quanto segue:
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)
Per quanto riguarda la tua seconda domanda, non riesco a vedere niente di meglio di
begin
#do AWS error prone stuff
rescue Exception => e
if Amazon::AWS::Error.constants.include?(e.class.name)
#awsError
else
whatever
end
end
(modifica: il codice sopra non funziona così com'è:name include il prefisso del modulo che non è il caso degli array di costanti.Dovresti assolutamente contattare il manutentore della libreria, la classe AWSError mi sembra più una classe factory :/)
Non ho Ruby-aws qui e il sito Caliban è bloccato dal firewall dell'azienda, quindi non posso testare molto ulteriormente.
Per quanto riguarda l'inclusione:questa potrebbe essere la cosa che fa il patching della scimmia sulla gerarchia StandardError.Non ne sono più sicuro, ma molto probabilmente farlo alla radice di un file al di fuori di ogni contesto include il modulo su Object o sulla metaclasse Object.(questo è ciò che accadrebbe in IRB, dove il contesto predefinito è Oggetto, non sono sicuro in un file)
dal piccone sui moduli :
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.
(modifica: non riesco a commentare utilizzando questo browser:/ yay per le piattaforme bloccate)
Altri suggerimenti
Beh, da quello che posso dire:
Class.new( StandardError )
Sta creando una nuova classe con StandardError come classe base, quindi non sarà affatto un Amazon::AWS::Error.È semplicemente definito in quel modulo, motivo per cui è probabilmente un kind_of?Amazon::AWS::Errore.Probabilmente non è una specie_di?Amazon::AWS perché forse i moduli non si annidano per scopi di kind_of??
Mi dispiace, non conosco molto bene i moduli in Ruby, ma sicuramente la classe base sarà StandardError.
AGGIORNAMENTO:A proposito, dai documenti Ruby:
obj.kind_of?(class) => vero o falso
Restituisce vero se class è la classe di obj o se class è una delle superclassi di obj o dei moduli inclusi in obj.
Volevo solo intervenire:Sono d'accordo che si tratta di un bug nel codice lib.Probabilmente dovrebbe leggere:
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 problema che stai riscontrando è questo Amazon::AWS::Error::AWSError
non è in realtà un'eccezione.Quando raise
viene chiamato, controlla se il primo parametro risponde al exception
metodo e utilizzerà invece il risultato di quello.Tutto ciò che è una sottoclasse di Exception
tornerà da solo quando exception
si chiama così puoi fare cose del tipo raise Exception.new("Something is wrong")
.
In questo caso, AWSError
ha exception
impostato come lettore di attributi che definisce il valore all'inizializzazione in qualcosa di simile Amazon::AWS::Error::SOME_ERROR
.Ciò significa che quando chiami raise Amazon::AWS::Error::AWSError.new(SOME_XML)
Ruby finisce per chiamare Amazon::AWS::Error::AWSError.new(SOME_XML).exception
che restituirà un'istanza di Amazon::AWS::Error::SOME_ERROR
.Come è stato sottolineato da uno degli altri intervistati, questa classe è una sottoclasse diretta di StandardError
invece di essere una sottoclasse di un errore comune di Amazon.Fino a quando questo problema non verrà risolto, la soluzione di Jean è probabilmente la soluzione migliore.
Spero che questo abbia contribuito a spiegare meglio cosa sta realmente accadendo dietro le quinte.