【问题标题】:Is it good practice to use exceptions for control flow in Ruby or Ruby on Rails?在 Ruby 或 Ruby on Rails 中为控制流使用异常是一种好习惯吗?
【发布时间】:2011-06-11 16:40:19
【问题描述】:

我正在阅读使用 Rails 进行敏捷 Web 开发(第 4 版),我找到了以下代码

class ApplicationController < ActionController::Base
  protect_from_forgery

  private

  def current_cart
    Cart.find(session[:cart_id])
  rescue ActiveRecord::RecordNotFound
    cart = Cart.create
    session[:cart_id] = cart.id
    cart
  end
end

由于我是一名Java开发人员,我对那部分代码的理解大致如下:

private Cart currentCard(){
  try{
    return CartManager.get_cart_from_session(cartId)
  }catch(RecordNotFoundEx e){
    Cart c = CartManager.create_cart_and_add_to_session(new Cart())
    return c;    
  }
}

让我印象深刻的是异常处理用于控制正常的应用程序流程(当用户第一次访问 Depot 应用程序时,缺少 Cart 是完全正常的行为)。

如果有人看任何 Java 书籍,他们会说这是一件非常糟糕的事情 - 并且有一个很好的理由:错误处理不应该被用来代替控制语句,这对那些阅读过的人来说是一种误导代码。

是否有充分的理由说明这种做法在 Ruby(Rails)中是合理的?这是 Ruby 中的常见做法吗?

【问题讨论】:

  • 我不认为有任何特殊原因可以证明这一点,我的感觉是 Ruby 社区对“不要对控制流使用异常处理”规则存在分歧。很多人告诉你不要这样做;许多其他人无论如何都会这样做。 (有些人同意该规则,但并不总是在意;有些人认为该规则很愚蠢(或只是错误);有些人只是没有多想。)无论哪种方式,我认为您对该代码中发生的事情的感觉是正确的。如果你不喜欢它,你当然可以使用显式控制流重写它。
  • @Telemachus:这是一个很好的答案,所以发布吧!
  • @Telemachus 你的评论很有趣。我在 JRuby 中发现,构建异常回溯通常位于分析器报告的 CPU 使用率的顶部。 MRI ruby​​ 中的基准测试显示使用异常对性能的影响不那么剧烈,但仍然可见。我的猜测是,因为异常包括回溯和其他数据,所以构建一个非常昂贵。 Ruby 还具有与救援/引发(异常)分开的 catch/throw(流控制),而且我听说 catch/throw 在 JRuby 中不是性能问题。
  • 我觉得这里的用词有微妙的暗示。 “异常处理”与“错误处理”。错误是异常(希望如此),但并非每个异常都是错误。这看起来像是其中之一,exception 处理机制具有向下传播堆栈并在重要的地方得到解决的优势。

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


【解决方案1】:

我想我会做以下事情(包括缓存当前购物车,以便每次调用该方法时都不会从数据库中加载它):

def current_cart
  @current_cart ||= begin
    unless cart = Cart.find_by_id(session[:cart_id])
      cart = Cart.create
      session[:cart_id] = cart.id
    end
    cart
  end
end

【讨论】:

    【解决方案2】:

    Rails 在使用异常方面并不一致。 find 如果没有找到对象会引发异常,但是为了保存,你可以选择你想要的行为。最常见的形式是这样的:

    if something.save
      # formulate a reply
    else
      # formulate an error reply, or redirect back to a form, or whatever
    end
    

    save 返回真或假。但也有save! 引发异常(在方法名称的末尾添加感叹号是 Ruby 主义,用于表明方法是“危险的”,或破坏性的,或者只是它具有副作用,确切意义取决于上下文)。

    find 引发异常是有正当理由的:如果 RecordNotFound 异常冒泡到顶层,它将触发 404 页面的呈现。由于您通常不会手动捕获这些异常(在 Rails 应用程序中很少看到 rescue ActiveRecord::RecordNotFound),因此您可以免费获得此功能。但在某些情况下,您想在对象不存在时执行某些操作,在这些情况下您必须捕获异常。

    我认为“最佳实践”一词实际上没有任何意义,但根据我的经验,Ruby 中的异常不再用于控制流,而不是 Java 或我使用的任何其他语言。鉴于 Ruby 没有已检查的异常,您通常会更少处理异常。

    归根结底是解释。由于find 最常见的用例是检索对象以显示它,并且该对象的 URL 将由应用程序生成,因此很可能是无法找到对象的例外情况。这意味着应用程序正在生成指向不存在的对象的链接,或者用户手动编辑了 URL。也可能是对象已被删除,但指向它的链接仍然存在于缓存中,或者通过搜索引擎,我会说这也是一种例外情况。

    当在您的示例中使用时,该参数适用于 find,即带有 ID。还有其他形式的find(包括许多find_by_* 变体)可以实际搜索,并且不会引发异常(然后在Rails 3 中有where,它取代了find 的许多用途在 Rails 2) 中。

    我并不是说使用异常作为流控制是一件好事,只是find 引发异常不一定是错误的,并且您的特定用例不是常见的情况。

    【讨论】:

    • 也许你的意思是一致而不是简洁?
    【解决方案3】:

    对于具体的用例,你可以简单地做

    def current_cart
      cart = Cart.find_or_create_by_id(session[:cart_id])
      session[:cart_id] = cart.id
      cart
    end
    

    这似乎会为其创建的新记录设置特定的id,但由于id 始终是受保护的属性,因此不会为新记录设置它。您将获得具有指定id 的记录,或者如果该记录不存在,则获得具有新id 的新记录。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-02
      • 2012-08-22
      • 1970-01-01
      相关资源
      最近更新 更多