98 votes

Classes d'erreurs personnalisées Ruby : héritage de l'attribut message

Je n'arrive pas à trouver beaucoup d'informations sur les classes d'exceptions personnalisées.

Ce que je sais

Vous pouvez déclarer votre classe d'erreur personnalisée et la laisser hériter de StandardError donc il peut être rescue d :

class MyCustomError < StandardError
end

Cela vous permet de l'élever en utilisant :

raise MyCustomError, "A message"

et plus tard, obtenir ce message lors d'un sauvetage

rescue MyCustomError => e
  puts e.message # => "A message"

Ce que je ne sais pas

Je veux donner à mon exception des champs personnalisés, mais je veux hériter de l'interface de l'exception. message de la classe parente. J'ai découvert en lisant sur ce sujet que @message n'est pas une variable d'instance de la classe d'exception. J'ai donc peur que mon héritage ne fonctionne pas.

Quelqu'un peut-il me donner plus de détails à ce sujet ? Comment mettre en œuvre une classe d'erreur personnalisée avec une classe de type object attribut ? Le texte suivant est-il correct ?

class MyCustomError < StandardError
  attr_reader :object
  def initialize(message, object)
    super(message)
    @object = object
  end
end

Et puis :

raise MyCustomError.new(anObject), "A message"

pour obtenir :

rescue MyCustomError => e
  puts e.message # => "A message"
  puts e.object # => anObject

Est-ce que ça va marcher, et si oui, est-ce que c'est la bonne façon de faire les choses ?

128voto

Stefan Points 23363

raise définit déjà le message, vous n'avez donc pas besoin de le passer au constructeur :

class MyCustomError < StandardError
  attr_reader :object

  def initialize(object)
    @object = object
  end
end

begin
  raise MyCustomError.new("an object"), "a message"
rescue MyCustomError => e
  puts e.message # => "a message"
  puts e.object # => "an object"
end

J'ai remplacé rescue Exception con rescue MyCustomError voir Pourquoi est-ce un mauvais style de "sauver l'exception => e" en Ruby ? .

11voto

Chad M Points 127

Compte tenu de ce que la documentation de ruby core de Exception de laquelle toutes les autres erreurs héritent, déclare environ #message

Renvoie le résultat de l'invocation de exception.to_s. Normalement, cela renvoie le message ou le nom de l'exception. En fournissant une méthode to_str, les exceptions sont acceptées pour être utilisées là où les chaînes de caractères sont attendues.

http://ruby-doc.org/core-1.9.3/Exception.html#method-i-message

Je choisirais de redéfinir to_s / to_str ou l'initialisateur. Voici un exemple où nous voulons savoir, d'une manière généralement lisible par l'homme, quand un service externe n'a pas réussi à faire quelque chose.

REMARQUE : La deuxième stratégie ci-dessous utilise les méthodes "pretty string" des rails, telles que demodualize ce qui peut être un peu compliqué et donc potentiellement peu judicieux à faire dans une exception. Vous pouvez également ajouter des arguments supplémentaires à la signature de la méthode, si nécessaire.

Remplacer la stratégie #to_s pas #to_str, cela fonctionne différemment

module ExternalService

  class FailedCRUDError < ::StandardError
    def to_s
      'failed to crud with external service'
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

Sortie de console

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end
# => "failed to crud with external service"

begin; raise ExternalService::FailedToCreateError, 'custom message'; rescue => e; e.message; end
# => "failed to crud with external service"

begin; raise ExternalService::FailedToCreateError.new('custom message'); rescue => e; e.message; end
# => "failed to crud with external service"

raise ExternalService::FailedToCreateError
# ExternalService::FailedToCreateError: failed to crud with external service

Surcharge de la stratégie #initialize

C'est la stratégie la plus proche des implémentations que j'ai utilisées dans les rails. Comme indiqué ci-dessus, elle utilise l'option demodualize , underscore y humanize ActiveSupport méthodes. Mais cela pourrait être facilement supprimé, comme dans la stratégie précédente.

module ExternalService
  class FailedCRUDError < ::StandardError
    def initialize(service_model=nil)
      super("#{self.class.name.demodulize.underscore.humanize} using #{service_model.class}")
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

Sortie de console

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end
# => "Failed to create error using NilClass"

begin; raise ExternalService::FailedToCreateError, Object.new; rescue => e; e.message; end
# => "Failed to create error using Object"

begin; raise ExternalService::FailedToCreateError.new(Object.new); rescue => e; e.message; end
# => "Failed to create error using Object"

raise ExternalService::FailedCRUDError
# ExternalService::FailedCRUDError: Failed crud error using NilClass

raise ExternalService::FailedCRUDError.new(Object.new)
# RuntimeError: ExternalService::FailedCRUDError using Object

Outil de démonstration

Ceci est une démo pour montrer le sauvetage et la messagerie de l'implémentation ci-dessus. La classe qui lève les exceptions est une fausse API pour Cloudinary. Il suffit de déposer une des stratégies ci-dessus dans votre console Rails, suivie de ceci.

require 'rails' # only needed for second strategy 

module ExternalService
  class FailedCRUDError < ::StandardError
    def initialize(service_model=nil)
      @service_model = service_model
      super("#{self.class.name.demodulize.underscore.humanize} using #{@service_model.class}")
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

# Stub service representing 3rd party cloud storage
class Cloudinary

  def initialize(*error_args)
    @error_args = error_args.flatten
  end

  def create_read_update_or_delete
    begin
      try_and_fail
    rescue ExternalService::FailedCRUDError => e
      e.message
    end
  end

  private def try_and_fail
    raise *@error_args
  end
end

errors_map = [
  # Without an arg
  ExternalService::FailedCRUDError,
  ExternalService::FailedToCreateError,
  ExternalService::FailedToReadError,
  ExternalService::FailedToUpdateError,
  ExternalService::FailedToDeleteError,
  # Instantiated without an arg
  ExternalService::FailedCRUDError.new,
  ExternalService::FailedToCreateError.new,
  ExternalService::FailedToReadError.new,
  ExternalService::FailedToUpdateError.new,
  ExternalService::FailedToDeleteError.new,
  # With an arg
  [ExternalService::FailedCRUDError, Object.new],
  [ExternalService::FailedToCreateError, Object.new],
  [ExternalService::FailedToReadError, Object.new],
  [ExternalService::FailedToUpdateError, Object.new],
  [ExternalService::FailedToDeleteError, Object.new],
  # Instantiated with an arg
  ExternalService::FailedCRUDError.new(Object.new),
  ExternalService::FailedToCreateError.new(Object.new),
  ExternalService::FailedToReadError.new(Object.new),
  ExternalService::FailedToUpdateError.new(Object.new),
  ExternalService::FailedToDeleteError.new(Object.new),
].inject({}) do |errors, args|
  begin 
    errors.merge!( args => Cloudinary.new(args).create_read_update_or_delete)
  rescue => e
    binding.pry
  end
end

if defined?(pp) || require('pp')
  pp errors_map
else
  errors_map.each{ |set| puts set.inspect }
end

6voto

sawa Points 62592

Votre idée est bonne, mais la façon dont vous l'appelez est mauvaise. Elle devrait être

raise MyCustomError.new(an_object, "A message")

5voto

Huliax Points 168

Je voulais faire quelque chose de similaire. Je voulais passer un objet à #new et faire en sorte que le message soit défini en fonction du traitement de l'objet passé. Ce qui suit fonctionne.

class FooError < StandardError
  attr_accessor :message # this is critical!
  def initialize(stuff)
    @message = stuff.reverse
  end
end

begin
  raise FooError.new("!dlroW olleH")
rescue FooError => e
  puts e.message #=> Hello World!
end

Notez que si vous ne déclarez pas attr_accessor :message alors il ne fonctionnera pas. Pour répondre au problème de l'OP, vous pouvez également passer le message comme argument supplémentaire et stocker ce que vous voulez. La partie cruciale semble être le remplacement de #message.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X