关于异常:Ruby自定义错误类:message属性的继承

Ruby custom error classes: inheritance of the message attribute

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

我所知道的

您可以声明您的自定义错误类并让它从StandardError继承,所以它可以是rescued:

1
2
class MyCustomError < StandardError
end

这允许您使用以下方法提升它:

1
raise MyCustomError,"A message"

稍后,在救援时得到这个信息

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

我不知道的

我想给我的异常一些自定义字段,但我想从父类继承message属性。我在阅读这个主题时发现,@message不是异常类的实例变量,所以我担心我的继承将不起作用。

有人能告诉我更多细节吗?如何使用object属性实现自定义错误类?以下是否正确:

1
2
3
4
5
6
7
class MyCustomError < StandardError
  attr_reader :object
  def initialize(message, object)
    super(message)
    @object = object
  end
end

然后:

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

得到:

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

它能工作吗?如果能,这是正确的做事方式吗?


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 MyCustomError替换了rescue Exception,明白为什么在ruby中"rescue exception=>e"是一种不好的风格吗?.


考虑到Exception的Ruby核心文档(所有其他错误都是从该文档继承的)所描述的关于#message的内容。

Returns the result of invoking exception.to_s. Normally this returns
the exception’s message or name. By supplying a to_str method,
exceptions are agreeing to be used where Strings are expected.

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

我会选择重新定义to_s/to_str或初始值设定项。下面是一个例子,我们希望以一种大多数人都能读懂的方式,知道当一个外部服务未能完成某些事情时。

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

凌驾于战略之上,而不是凌驾于战略之上,它的工作方式不同

1
2
3
4
5
6
7
8
9
10
11
12
13
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

控制台输出

1
2
3
4
5
6
7
8
9
10
11
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中使用的实现的策略。如上所述,它使用demodualizeunderscorehumanizeActiveSupport方法。但这可以很容易地消除,就像以前的策略一样。

1
2
3
4
5
6
7
8
9
10
11
12
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

控制台输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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控制台中,然后执行此操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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


你的想法是对的,但你称之为错误的方式。应该是

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


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

1
2
3
4
5
6
7
8
9
10
11
12
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的问题,您还可以将消息作为附加参数传递,并存储您喜欢的任何内容。关键的部分似乎是压倒性的信息。