【问题标题】:Rescue exceptions for all methods in a class拯救类中所有方法的异常
【发布时间】:2018-11-27 02:27:00
【问题描述】:

基本上,这是正常的代码:

class Foo
   def hi
      # your code here....
   rescue => e
      # Raise error here
   end

   def hello
      # your code here...
   rescue => e
      # Raise error here
   end
end

但在 PHP 中,我可以使用__call 魔术方法创建抽象类,如下所示:

class FooAbstract {
    public function __call($name, $args) {
       # Try catch in here...
    }
}

class Foo extends FooAbstract {
   public function hi() {
     # Code with try catch...
   }
}

如何在 Ruby 类中使​​用 __call 方法???

【问题讨论】:

  • 如果你想定义一个类,除非是子类,否则它不能被实例化,你可以在 FooAbstract#initialize 中做一个fail "Trying to instantiate abstract class" if self.class == FooAbstract
  • 这看起来像是一个 XY 问题。你想完成什么?
  • ruby 中没有与__call 等效的direct;最接近的可能是method_missing。 (同样,在 PHP 中没有与 method_missing 等效的 direct,但据我所知,__call 可能是最接近的。)但正如 Stefan 所说,你实际上想在这里实现什么?在不知道您要解决什么问题的情况下,我无法真正建议如何构建代码。
  • 我的猜测是完成类似rails类级别rescue_from这样的事情,这样您就不必将相同的rescue块复制粘贴到每个方法中。
  • 不确定你想用那个 PHP 代码说明什么,但是方法 hi 不会以任何方式调用 __call__call 应该做什么,为子类的所有方法添加异常处理?

标签: ruby


【解决方案1】:

我不确定你想在这里实现什么,但 PHP 的 __call() 的 Ruby 等效项是 method_missing

默认情况下,当你尝试调用一个不存在的方法时,你会得到一个异常。但是如果你想实现一个“抽象类”。你也可以试试这个解决方案:https://stackoverflow.com/a/512505/185870

【讨论】:

    【解决方案2】:

    您可以定义一个模块,在包含该模块时定义一个 method_added 钩子,该钩子将所有新方法包装在 begin..rescue 块中:

    require 'set'
    
    module ExceptionHandler
    
      def self.included(klass)
        super
        klass.send(:extend, ClassMethods)
      end
    
      module ClassMethods
        def exception_handler(&block)
          @__exception_handler = block.to_proc
        end
    
        def handle_exception(exception)
          defined?(@__exception_handler) ? @__exception_handler.call(exception) : super
        end
    
        def handle_method_exceptions(method_name)
          old_method = instance_method(method_name)
          return if (@__hooked_methods ||= Set.new).include?(method_name)
    
          @__ignoring_added_methods = true # avoid infinite define_method/method_added loop
          define_method method_name do |*args, &block|
            begin
              old_method.bind(self).(*args, &block)
            rescue => ex
              self.class.handle_exception(ex)
            end
          end
          @__ignoring_added_methods = false
    
          @__hooked_methods << method_name
        end
    
        def method_added(method_name)
          super
          unless @__ignoring_added_methods
            handle_method_exceptions(method_name)
          end
        end
      end
    end
    

    这会像这样使用:

    class Foo
      include ExceptionHandler
    
      exception_handler do |exception|
        puts "Catched an exception:"
        puts "---------------------"
        puts "Exception class: #{exception.class}"
        puts "Message: #{exception.message}"
        puts "Backtrace:"
        puts exception.backtrace.join("\n  ")
        puts
        puts "reraising.."
        puts
        raise exception
      end
    
      def this_raises
        raise "somebody set up us the bomb"
      end
    end
    
    Foo.new.this_raises
    

    这将输出:

    Catched an exception:
    ---------------------
    Exception class: RuntimeError
    Message: somebody set up us the bomb
    Backtrace:
    errorhandler.rb:62:in `this_raises'
      errorhandler.rb:26:in `call'
      errorhandler.rb:26:in `block in handle_exceptions'
      errorhandler.rb:67:in `<main>'
    
    reraising..
    

    我不确定这是否是个好主意。

    您可以取出 method_added 部分,它看起来像:

    class Foo
      with_rescue def foofoo(arg)
        puts arg.inspect
      end
    end
    

    (您只需将handle_method_exceptions 重命名为with_rescue 并删除所有@__ignoring_added_methods 诡计和method_added 方法,它应该可以按描述工作)。

    【讨论】:

    • 这太棒了!但是如何使用实例方法作为exception_handler?我希望能够以任何方法将异常记录到我的实例特定记录器。
    • @meesern 未经测试,但您可以尝试将self.class.handle_exception(ex) 替换为(respond_to?(:handle_exception) ? self : self.class).send(:handle_exception, ex) 并将异常处理程序定义为实例方法:def handle_exception(ex) ... end
    猜你喜欢
    • 2011-06-15
    • 2013-06-28
    • 2017-08-19
    • 1970-01-01
    • 2017-01-30
    • 1970-01-01
    • 1970-01-01
    • 2012-07-12
    • 1970-01-01
    相关资源
    最近更新 更多