【问题标题】:Raise custom Exception with arguments使用参数引发自定义异常
【发布时间】:2012-07-23 03:01:05
【问题描述】:

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

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

这是通过 POST 请求从外部数据源填充模型的自动导入方法。

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

【问题讨论】:

    标签: ruby-on-rails ruby exception exception-handling


    【解决方案1】:

    用 new 创建你的异常实例:

    class CustomException < StandardError
      def initialize(data)
        @data = data
      end
    end
    # => nil 
    raise CustomException.new(bla: "blupp")
    # CustomException: CustomException
    

    【讨论】:

    • 我已经使用了一年了,我想补充一下:现在每次我想这样做而忘记如何做时,我都会偷看cancan's exceptions来提醒自己。对于更复杂的异常,最后一个错误遵循非常好的形式。
    • @vladCovaliov 为什么会失败?消息是空的
    • 您应该始终将message = nil 添加为您的第一个参数并调用super(message),否则raise CustomError, :some_message 之类的内容将无法正确设置消息。
    • bla的值是怎么得到的? (假设您挽救了异常,e):e.bla 会起作用吗?
    • 例外只是一个普通的 ruby​​ 类。为了获得:bla 的值,您需要有一个@data 的getter,然后访问哈希键。
    【解决方案2】:

    解决方案:

    class FooError < StandardError
      attr_reader :foo
    
      def initialize(foo)
       super
       @foo = foo
      end
    end
    

    如果您遵循Rubocop Style Guide 并始终将您的消息作为第二个参数传递给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 Style Guide 所说,消息和异常类应该作为单独的参数提供,因为如果你写:

    raise FooError.new('bar')
    

    又想给raise传一个回溯,不传两次消息就没办法了:

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

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

    实现FooError

    问题的症结在于,如果foo是可选参数,那么引发异常有两种不同的方式:

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

    raise FooError, 'bar', backtrace # case 2
    

    我们希望FooError 与两者一起工作。

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

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

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

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

    (b) 仅将 case 1 或 case 2 样式与 raise 一起使用,但不能同时使用两者。

    (c) 将foo 设为关键字参数。

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

    如果你 raiseFooError 使用 case 2 样式,foo 的值就是消息,它被隐式传递给 super。如果您向FooError#initialize 添加更多参数,您将需要一个明确的super(foo)

    如果您使用关键字参数 (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
    

    【讨论】:

    • 我遇到了同样的问题,&当我做这样的事情时,@foo 保持为零,而 foo 在类 FooError 中的 self.message 之类的消息中,例如自定义错误类... :`(
    • 我认为SuperWithMessageError 中的message 设置不正确,因为在initialize 方法中您调用super(message) 其中messagenil,在这种情况下StandardError 的行为是将message 值设置为类名。这解释了为什么 _ex_.message 在您的示例中是“SuperWithMessageError”。如果您将对super 的调用更改为super(foo),则message 将设置为所提供的foo 的任何值。
    • @LemonCat 没错,但您的解决方案不是 OP 想要的。引用:“raise 语法似乎只接受一个异常类和消息,没有传递到实例化过程的可选参数。”换句话说,OP 对不是消息的可选参数感兴趣。将调用更改为 super(foo) 将无济于事,因为目标是拥有一个与消息分开的参数。
    • @MaxWallace 我跟着你。我试图说明的一点是,当我阅读“在第二种情况下,SuperWithMessageError...message 未正确设置”时具有误导性:message 未正确设置,因为它永远不会在#initialize(foo) 中初始化。更多上下文,请参阅stackoverflow.com/a/56371923/5299483
    • @MaxWallace,感谢您纠正我的错误。我阅读文档的理解是,如果message 值未提供给#initialize,则#message 被设置为类名,但正如您正确指出的那样,即使在Exception 子类的#initialize 方法中, #message 将返回类名,直到调用 super(message) 来设置(嗯,真的要覆盖)默认消息。直到。
    【解决方案3】:

    这是一个向错误添加代码的示例代码:

    class MyCustomError < StandardError
        attr_reader :code
    
        def initialize(code)
            @code = code
        end
    
        def to_s
            "[#{code}] #{super}"
        end
    end
    

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

    【讨论】:

      【解决方案4】:

      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)self 上执行#rb_obj_clone,然后调用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 的后一个示例中,CustomException 将是raised,message 设置为“消息”,extra 设置为“blupp”(因为它是克隆)但是两个 CustomException 对象实际上是创建的:第一个由CustomException.new 创建,第二个由#raiseCustomException 的第一个实例上调用#exception 创建第二个克隆的@987654341 @。

      我的why的加长舞蹈混音版本位于:https://stackoverflow.com/a/56371923/5299483

      【讨论】:

        【解决方案5】:

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

        如果您要传递的额外信息只是一个带有消息的类型,这很好用:

        # 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)
        

        【讨论】:

          【解决方案6】:

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

          根据Kernel#raise 文档:

          第一个参数应该是一个异常类(或另一个在发送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 and upset 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

          【讨论】:

            【解决方案7】:

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

            begin
              # do something
            rescue => e
              error = MyException.new(e, 'some info')
              raise error
            end
            

            【讨论】:

            猜你喜欢
            • 2014-06-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-07-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多