代码之家  ›  专栏  ›  技术社区  ›  Andrew Grimm Alex Wayne

如何在Ruby中向异常消息添加信息?

  •  53
  • Andrew Grimm Alex Wayne  · 技术社区  · 15 年前

    如何在不更改Ruby中的类的情况下向异常消息添加信息?

    我目前使用的方法是

    strings.each_with_index do |string, i|
      begin
        do_risky_operation(string)
      rescue
        raise $!.class, "Problem with string number #{i}: #{$!}"
      end
    end
    

    理想情况下,我还希望保留回溯。

    有更好的方法吗?

    6 回复  |  直到 10 年前
        1
  •  91
  •   Ryenski    10 年前

    要重新发出异常并修改消息,同时保留异常类及其回溯,只需执行以下操作:

    strings.each_with_index do |string, i|
      begin
        do_risky_operation(string)
      rescue Exception => e
        raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace
      end
    end
    

    这将产生:

    # RuntimeError: Problem with string number 0: Original error message here
    #     backtrace...
    
        2
  •  17
  •   Chuck    15 年前

    这并不是更好,但是你可以用一条新的信息来重新判断例外:

    raise $!, "Problem with string number #{i}: #{$!}"
    

    您还可以使用 exception 方法:

    new_exception = $!.exception "Problem with string number #{i}: #{$!}"
    raise new_exception
    
        3
  •  6
  •   AlexChaffee    11 年前

    另一种方法是:

    class Exception
      def with_extra_message extra
        exception "#{message} - #{extra}"
      end
    end
    
    begin
      1/0
    rescue => e
      raise e.with_extra_message "you fool"
    end
    
    # raises an exception "ZeroDivisionError: divided by 0 - you fool" with original backtrace
    

    (修订为使用 exception 方法内部,谢谢@chack)

        4
  •  4
  •   Mark Rushakoff    15 年前

    我的方法是 extend 这个 rescue 匿名模块扩展错误的 message 方法:

    def make_extended_message(msg)
        Module.new do
          @@msg = msg
          def message
            super + @@msg
          end
        end
    end
    
    begin
      begin
          raise "this is a test"
      rescue
          raise($!.extend(make_extended_message(" that has been extended")))
      end
    rescue
        puts $! # just says "this is a test"
        puts $!.message # says extended message
    end
    

    这样,您就不会删除任何其他例外信息(即 backtrace )

        5
  •  2
  •   Community Mohan Dere    8 年前

    我投了我的票 Ryan Heneise's 答案应该是被接受的。

    这是复杂应用程序中的一个常见问题,保留原始回溯通常非常关键,因此我们在 ErrorHandling 帮助器模块。

    我们发现的一个问题是,当系统处于混乱状态时,有时试图生成更有意义的消息会导致异常处理程序内部生成异常,这导致我们强化了实用程序功能,如下所示:

    def raise_with_new_message(*args)
      ex = args.first.kind_of?(Exception) ? args.shift : $!
      msg = begin
        sprintf args.shift, *args
      rescue Exception => e
        "internal error modifying exception message for #{ex}: #{e}"
      end
      raise ex, msg, ex.backtrace
    end
    

    当一切顺利的时候

    begin
      1/0
    rescue => e
      raise_with_new_message "error dividing %d by %d: %s", 1, 0, e
    end
    

    你得到一条修改得很好的信息

    ZeroDivisionError: error dividing 1 by 0: divided by 0
        from (irb):19:in `/'
        from (irb):19
        from /Users/sim/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'
    

    当情况恶化时

    begin
      1/0
    rescue => e
      # Oops, not passing enough arguments here...
      raise_with_new_message "error dividing %d by %d: %s", e
    end
    

    你仍然不会忘记大局

    ZeroDivisionError: internal error modifying exception message for divided by 0: can't convert ZeroDivisionError into Integer
        from (irb):25:in `/'
        from (irb):25
        from /Users/sim/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'
    
        6
  •  0
  •   Community Mohan Dere    8 年前

    我最后做的是:

    Exception.class_eval do
      def prepend_message(message)
        mod = Module.new do
          define_method :to_s do
            message + super()
          end
        end
        self.extend mod
      end
    
      def append_message(message)
        mod = Module.new do
          define_method :to_s do
            super() + message
          end
        end
        self.extend mod
      end
    end
    

    实例:

    strings = %w[a b c]
    strings.each_with_index do |string, i|
      begin
        do_risky_operation(string)
      rescue
        raise $!.prepend_message "Problem with string number #{i}:"
      end
    end
    => NoMethodError: Problem with string number 0:undefined method `do_risky_operation' for main:Object
    

    还有:

    pry(main)> exception = 0/0 rescue $!
    => #<ZeroDivisionError: divided by 0>
    pry(main)> exception = exception.append_message('. With additional info!')
    => #<ZeroDivisionError: divided by 0. With additional info!>
    pry(main)> exception.message
    => "divided by 0. With additional info!"
    pry(main)> exception.to_s
    => "divided by 0. With additional info!"
    pry(main)> exception.inspect
    => "#<ZeroDivisionError: divided by 0. With additional info!>"
    

    这和 Mark Rushakoff 答案是:

    1. 重写 to_s 而不是 message 因为默认 消息 定义为 托斯 (至少在我测试过的Ruby2.0和2.2中)
    2. 电话 extend 而不是让来电者做额外的步骤。
    3. 使用 define_method 和一个闭包,使局部变量 消息 可以被引用。当我试着用一门课的时候 variable @@message ,它警告,“警告:从顶层访问类变量”(请参见 question :“由于不使用class关键字创建类,因此类变量被设置为 Object ,而不是[您的匿名模块]“)

    特征:

    • 易于使用
    • 重用同一对象(而不是创建类的新实例),因此保留对象标识、类和回溯等内容
    • 托斯 , 消息 inspect 所有人都做出适当的回应
    • 可以与已存储在变量中的异常一起使用;不需要重新引发任何内容(例如,涉及传递backtrace以引发的解决方案: raise $!, …, $!.backtrace )这对我很重要,因为异常被传递到我的日志记录方法中,而不是我自己挽救的东西。
    推荐文章