【问题标题】:Ruby rescue exception during multiple methods多个方法期间的 Ruby 救援异常
【发布时间】:2015-07-13 15:36:42
【问题描述】:

我已经构建了一个简单的银行应用程序,它能够执行通常的操作;充值、提现等

我的控制器方法执行这些操作并挽救帐户或其他实体引发的异常。

以下是控制器代码中使用的其中一些方法:

def open(type, with:)
    account = create type, (holders.find with)
    add account
    init_yearly_interest_for account
    boundary.render AccountSuccessMessage.new(account)
  rescue ItemExistError => message
    boundary.render message
  end

  def deposit(amount, into:)
    account = find into
    account.deposit amount
    boundary.render DepositSuccessMessage.new(amount)
  rescue ItemExistError => message
    boundary.render message
  end

  def withdraw(amount, from:)
    account = find from
    init_limit_reset_for account unless account.breached?
    account.withdraw amount
    boundary.render WithdrawSuccessMessage.new(amount)
  rescue ItemExistError, OverLimit, InsufficientFunds => message
    boundary.render message
  end

  def get_balance_of(id)
    account = find id
    boundary.render BalanceMessage.new(account)
  rescue ItemExistError => message
    boundary.render message
  end

  def transfer(amount, from:, to:)
    donar = find from
    recipitent = find to
    init_limit_reset_for donar unless donar.breached?
    donar.withdraw amount
    recipitent.deposit amount
    boundary.render TransferSuccessMessage.new(amount)
  rescue ItemExistError, OverLimit, InsufficientFunds => message
    boundary.render message
  end

  def add_holder(id, to:)
    holder = holders.find id
    account = find to
    account.add_holder holder
    boundary.render AddHolderSuccessMessage.new(holder, account)
  rescue ItemExistError, HolderOnAccount => message
    boundary.render message
  end

  def get_transactions_of(id)
    transactions = (find id).transactions
    boundary.render TransactionsMessage.new(transactions)
  rescue ItemExistError => message
    boundary.render message
  end

  def get_accounts_of(id)
    holder = holders.find id
    accounts = store.select { |_, a| a.holder? holder }.values
    boundary.render DisplayAccountsMessage.new(accounts)
  rescue ItemExistError => message
    boundary.render message
  end

如您所见,我在多个方法中拯救了多个错误,通常会处理相同的错误。

虽然这可行,但我想知道是否可以在调用控制器中的任何方法时重构和处理这些异常。

例如:

during: 
  open, deposit, withdraw
rescue ItemExistError => message
  boundary.render message

任何帮助将不胜感激。

【问题讨论】:

    标签: ruby rescue


    【解决方案1】:

    您可以通过定义一个包装您想要从中拯救的每个方法的方法来使用元编程来做到这一点。无论这是否实际上是更清晰的代码,都由您决定。

    class MyController
      # define a unified exception handler for some methods
      def self.rescue_from *meths, exception, &handler
        meths.each do |meth|
          # store the previous implementation
          old = instance_method(meth)
          # wrap it
          define_method(meth) do |*args|
            begin
              old.bind(self).call(*args)
            rescue exception => e
              handler.call(e)
            end
          end
        end
      end
    
      rescue_from :open, :deposit, :withdraw, ItemExistError do |message|
        boundary.render message
      end
    end
    

    如果您不打算重用该方法(即,如果您只想要一组方法和一个异常类的统一处理程序),我将删除 rescue_from 定义并将元编程代码直接放入班级。

    【讨论】:

    • 私下调用rescue_from是个好习惯吗?有关系吗?
    • 我认为这是一种很好的做法。实际上,您可能不希望类之外的代码添加新的rescue_from 案例。
    【解决方案2】:

    您可以尝试编写这样的方法:

    def call_and_rescue
      yield if block_given?
    rescue ItemExistError => message
      boundary.render message
    end
    

    那就用吧:call_and_rescue { open(type, with) }

    【讨论】:

    • 不幸的是不是一个 Rails 应用程序,但我希望有类似的东西
    • 这不只是将救援代码从在每个方法定义中重复到在每个方法调用中重复吗?这对我来说似乎没有什么进步。
    • @PhilBrockwell 我认为这都是纯红宝石,不是吗? ruby-doc.org/core-2.4.1/Kernel.html#method-i-block_given-3F
    • @max 对我来说这是一个改进,因为它消除了关于正在挽救什么错误以及如何挽救的知识。考虑如果要挽救的特定错误发生变化,或者需要挽救额外的错误会发生什么:这些变化现在可能发生在一个地方,而不是每个方法一个变化。
    【解决方案3】:

    样式回调之前和之后会有帮助吗?

    用于存储具有回调的操作列表的变量

    @before_actions = Hash.new {|hash,key| hash[key] = Array.new}
    @after_actions = Hash.new {|hash,key| hash[key] = Array.new}
    

    定义 before_action 方法 action 是要执行的回调,methods 是一个分配了回调的 action 数组

    def before_action(action, methods)  
      methods.each do |m|
        @before_actions[m] << action
      end
    end
    
    def after_action(action, methods)  
      methods.each do |m|
        @after_actions[m] << action
      end
    end
    

    在我们的方法中使用的块允许我们使用这些回调

    def execute_callbacks  
      @before_actions.each {|k,v| v.each {|v| send(v)}}
      yield
      @after_actions.each {|k,v| v.each {|v| send(v)}}
    end
    

    声明我们希望发生的之前和之后的操作(也许将像 #first_do_this 和 #lastly_do_this 这样的回调作为私有方法?)

    before_action :first_do_this, [:do_something]  
    after_action :lastly_do_this, [:do_something]
    
    private
    def first_do_this  
      puts "this occurs first"
    end
    
    def lastly_do_this  
      puts "this occurs last"
    end
    

    我们想要解决的方法。 (我知道它不是那么优雅,它可以改进)

    def do_something  
      execute_callbacks do
        puts "hello world"
      end
    end
    

    do_something 被调用时

    do_something # =>
      this occurs first
      hello world
      this occurs last
    

    【讨论】:

    • 我认为这是正确的,但我不确定它是否会起作用,因为救援需要在代码运行时监听异常,而不是之前或之后。虽然也许我误解了你的解决方案......
    • 我还没有在生产中尝试过这样的事情。这更像是玩弄,你可能有一个非常有效的观点。这可能值得一试,如果它不起作用,至少我们尝试过,我相信这里有人可以提供帮助!
    • 我真的很喜欢@Piotr Kruczek 的解决方案,这样的方法对你有用吗?
    • 它可以工作,但是因为我将一个块传递给控制器​​,这意味着我必须编写更多代码来评估方法,然后才能进入我的边界类中的那个块......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 2021-10-24
    • 1970-01-01
    • 2020-08-26
    • 2012-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多