54

我在rails中的模型上定义了一个自定义异常作为一种包装异常:(begin[code]rescue[raise custom exception]end

当我引发异常时,我想向它传递一些关于 a) 其内部函数引发错误的模型实例的信息,以及 b) 被捕获的错误。

这是一个模型的自动导入方法,该模型由来自外部数据源的 POST 请求填充。

tldr; 鉴于您自己定义了异常,如何将参数传递给异常?我对该异常有一个初始化方法,但raise语法似乎只接受异常类和消息,没有传递到实例化过程的可选参数。

4

7 回答 7

80

使用 new 创建一个异常实例:

class CustomException < StandardError
  def initialize(data)
    @data = data
  end
end
# => nil 
raise CustomException.new(bla: "blupp")
# CustomException: CustomException
于 2012-07-24T18:25:13.960 回答
26

解决方案:

class FooError < StandardError
  attr_reader :foo

  def initialize(foo)
   super
   @foo = foo
  end
end

如果您遵循Rubocop 样式指南并始终将您的消息作为第二个参数传递给:这是最好的方法raise

raise FooError.new('foo'), 'bar'

你可以得到foo这样的:

rescue FooError => error
  error.foo     # => 'foo'
  error.message # => 'bar'

如果要自定义错误消息,请编写:

class FooError < StandardError
  attr_reader :foo

  def initialize(foo)
   super
   @foo = foo
  end

  def message
    "The foo is: #{foo}"
  end
end

如果foo需要,这很有效。如果您想foo成为可选参数,请继续阅读。


解释:

将您的消息作为第二个参数传递给raise

正如Rubocop 风格指南所说,消息和异常类应该作为单独的参数提供,因为如果你写:

raise FooError.new('bar')

并且想要将回溯传递给raise,没有两次传递消息就没有办法做到这一点:

raise FooError.new('bar'), 'bar', other_error.backtrace

正如这个答案所说,如果要将异常重新引发为具有相同回溯和不同消息或数据的新实例,则需要传递回溯。

实施FooError

问题的症结在于 iffoo是一个可选参数,有两种不同的引发异常的方式:

raise FooError.new('foo'), 'bar', backtrace # case 1

raise FooError, 'bar', backtrace # case 2

我们希望FooError与两者合作。

在案例 1中,由于您提供了错误实例而不是类,因此raise设置'bar'为错误实例的消息。

在案例 2中,为您raise实例化FooError并作为唯一参数传递'bar',但它不会像案例 1 那样在初始化后设置消息。要设置消息,您必须使用消息作为唯一参数进行调用superFooError#initialize

因此,在情况 1 中,FooError#initialize收到'foo',在情况 2 中,收到'bar'。它已超载,通常无法区分这些情况。这是 Ruby 的设计缺陷。所以 iffoo是一个可选参数,你有三个选择:

(a) 接受传递给的值FooError#initialize可能是foo或者是消息。

(b) 仅使用案例 1 或案例 2 样式,raise但不能同时使用。

(c) 做foo一个关键字参数。

如果您不想foo成为关键字参数,我建议 (a) 并且我的FooError上述实现旨在以这种方式工作。

如果您raiseFooError用例 2 样式,则 的值foo是消息,它被隐式传递给super. 如果您super(foo)FooError#initialize.

如果您使用关键字参数(h/t Lemon Cat's answer),则代码如下所示:

class FooError < StandardError
  attr_reader :foo

  def initialize(message, foo: nil)
   super(message)
   @foo = foo
  end
end

提高看起来像:

raise FooError, 'bar', backtrace
raise FooError(foo: 'foo'), 'bar', backtrace
于 2015-09-09T13:55:52.803 回答
9

以下是向错误添加代码的示例代码:

class MyCustomError < StandardError
    attr_reader :code

    def initialize(code)
        @code = code
    end

    def to_s
        "[#{code}] #{super}"
    end
end

并提出: raise MyCustomError.new(code), message

于 2015-11-03T14:02:31.990 回答
6

TL; DR 提出这个问题 7 年后,我相信正确的答案是:

class CustomException < StandardError
  attr_reader :extra
  def initialize(message=nil, extra: nil)
    super(message)
    @extra = extra
  end
end
# => nil 
raise CustomException.new('some message', extra: "blupp")

警告:您将获得相同的结果:

raise CustomException.new(extra: 'blupp'), 'some message'

但那是因为Exception#exception(string)a #rb_obj_cloneon self,然后调用exc_initialize(它不调用CustomException#initialize. 从error.c

static VALUE
exc_exception(int argc, VALUE *argv, VALUE self)
{
    VALUE exc;

    if (argc == 0) return self;
    if (argc == 1 && self == argv[0]) return self;
    exc = rb_obj_clone(self);
    exc_initialize(argc, argv, exc);

    return exc;
}

在上面的后一个示例中#raise, aCustomExceptionraisedmessage设置为“a message”并extra设置为“blupp”(因为它是一个克隆),但实际上创建了两个 对象:第一个是由,第二个是通过调用其中第一个实例创建了第二个 cloned 。CustomExceptionCustomException.new#raise#exceptionCustomExceptionCustomException

为什么我的扩展舞蹈混音版本位于:https ://stackoverflow.com/a/56371923/5299483

于 2019-05-30T19:59:24.693 回答
0

带有附加信息的自定义错误的简单模式

如果您要传递的额外信息只是带有消息的类型,则效果很好:

# define custom error class
class MyCustomError < StandardError; end

# raise error with extra information
raise MyCustomError, 'Extra Information'

结果(在 IRB 中):

Traceback (most recent call last):
        2: from (irb):22
        1: from (irb):22:in `rescue in irb_binding'
MyCustomError (Extra Information)

课堂上的例子

下面的模式对我来说非常有用(双关语)。它很干净,可以很容易地模块化,并且错误是有表现力的。在我的类中,我定义了继承自 的新错误StandardError,并通过消息(例如,与错误关联的对象)引发它们。

这是一个简单的示例,类似于 OP 的原始问题,它在类中引发自定义错误并在错误消息中捕获方法名称:

class MyUser
  # class errors
  class MyUserInitializationError < StandardError; end

  # instance methods
  def simulate_failure
    raise MyUserInitializationError, "method failed: #{__method__}"
  end
end

# example usage: 
MyUser.new.simulate_failure

# => MyUser::MyUserInitializationError (method failed: simulate_failure)
于 2020-02-25T10:06:58.437 回答
-1

对于来自 Java 之类的程序员来说,这是违反直觉的,但最有效的方法不是编写自定义初始化程序,而是编写自己的Exception::exception类方法替换。

根据Kernel#raise文档:

第一个参数应该是一个 Exception 类(或另一个Exception在发送exception消息时返回对象的对象)。[强调补充。]

class MyException < StandardError

  class << self
    def exception(arg)
      # per `Exception::exception` docs
      return self if arg.nil? || self.equal?(arg)
      return MyException.new(arg.to_s) unless arg.is_a?(MyModel)

      # $! is a magic global variable holding the last raised
      # exception; Kernel#raise will also inject it as the 
      # cause attribute of the exception we construct here
      error_caught = $!
      msg = custom_message_for(arg, error_caught)

      ex = MyException.new(msg)
      # … any additional initialization goes here
      ex
    end

    private

    def custom_message_for(my_model_instance, error_caught)
      # …
    end
  end
end

这样,您可以正常引发自定义异常,使用模型实例而不是字符串消息,而不必记得new显式调用和扰乱 RuboCop,也不必让 Ruby 程序员感到困惑,这些程序员稍后会期待标准语法。

begin
  my_model.frob
rescue => e
  raise MyException.new, my_model # works
end

raise MyException.new, 'some other reason' # also works

来自的消息和初始化逻辑MyException#exception也可以进入自定义初始化程序,让您只需编写MyException.new(arg, $!),但在这种情况下,请确保初始化程序足够聪明,也可以处理纯字符串消息,并确保它在某些时候super使用字符串消息调用.

于 2021-11-09T01:30:00.557 回答
-2

您可以创建Exception子类的新实例,然后引发它。例如:

begin
  # do something
rescue => e
  error = MyException.new(e, 'some info')
  raise error
end
于 2012-07-24T18:25:25.817 回答