Ruby自定义错误类:message属性的继承


95

我似乎找不到有关自定义异常类的更多信息。

我所知道的

您可以声明您的自定义错误类,并使其继承自StandardError,因此可以使用rescued:

class MyCustomError < StandardError
end

这使您可以使用以下方法提高它:

raise MyCustomError, "A message"

然后在救援时得到该消息

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

我不知道

我想给我的异常一些自定义字段,但我想message从父类继承属性。我发现阅读关于这个主题@message不是异常类的实例变量,所以我担心我的产业将无法正常工作。

谁能给我更多细节吗?如何实现带有object属性的自定义错误类?以下是正确的:

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

然后:

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

要得到:

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

它会起作用吗?如果起作用,这是正确的做事方式吗?


3
不要rescue Exception => e。它比rescue => e从扩展的默认值更广泛StandardError,并且可以捕获包括Ctrl + C在内的所有内容。我会的rescue MyCustomError => e
瑞安·泰勒

1
@RyanTaylor我编辑了问题以寻求更合适的方法。
MarioDS 2014年

Answers:


121

raise 已经设置了消息,因此您不必将其传递给构造函数:

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

我已替换rescue Exceptionrescue MyCustomError,请参见为什么在Ruby中“救援异常=> e”的样式不好?


我会接受您的回答,因为您向我展示了整个语法。谢谢!
MarioDS

1
我们在这里做rescue Exception,但是为什么不rescue MyCustomError呢?
Dfr

仅供参考,如果第一个参数object是一个选项,并且raise MyCustomError, "a message"不带new,则不会设置“消息”。
hiroshi

有没有办法以某种方式在我们的自定义异常类中获取引发的消息?
Cyber​​Mew

@Cyber​​Mew是什么意思?你想让我做什么?
斯特凡

10

鉴于Exception所有其他错误都继承自的ruby核心文档,陈述了以下内容:#message

返回调用exception.to_s的结果。通常,这将返回异常的消息或名称。通过提供to_str方法,同意在需要字符串的地方使用异常。

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

我会选择重新定义to_s/ to_str或初始化程序。这是一个示例,我们希望以一种易于理解的方式知道何时外部服务无法执行某些操作。

注意:下面的第二种策略使用rails pretty string方法,例如demodualize,这可能有点复杂,因此在异常情况下可能不明智。如果需要,您还可以在方法签名中添加更多参数。

覆盖#to_s策略 而不是#to_str,它的工作方式有所不同

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

控制台输出

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

覆盖#初始化策略

这是最接近我在Rails中使用的实现的策略。如上所述,它使用demodualizeunderscore以及humanize ActiveSupport方法。但是,就像以前的策略一样,可以轻松删除它。

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

控制台输出

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

演示工具

这是演示上述实施的救援和消息传递的演示。引发异常的类是Cloudinary的虚假API。只需将上述策略之一转储到Rails控制台中,然后执行此操作即可。

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

6

您的想法是正确的,但是您所说的方式是错误的。它应该是

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

好的,我认为您给出的消息是raise关键字或其他内容的第二个参数。
MarioDS 2013年

您重新定义initialize了两个参数。new将参数传递给initialize
sawa 2013年

或者,您可以省略括号。
sawa 2013年

我了解这一点,但是我在问题中链接到的主题的发布者确实是这样的:raise(BillRowError.new(:roamingcalls, @index), "Roaming Calls field missing")。因此,他raise使用两个参数进行调用:一个新BillRowError对象和他的消息。我只是对语法感到困惑...在其他教程上,我总是这样看:raise Error, message
MarioDS

1
问题不在于您传递了多少参数raise。这非常灵活。问题在于您定义initialize了两个参数,而只给出了一个。看你的例子。BillRowError.new(:roamingcalls, @index)有两个参数。
sawa 2013年

4

我想做类似的事情。我想将一个对象传递给#new并根据传递的对象的某些处理来设置消息。以下作品。

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

请注意,如果不声明attr_accessor :message,它将无法正常工作。解决OP的问题,您还可以将消息作为附加参数传递并存储您喜欢的任何内容。关键部分似乎是覆盖#message。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.