在Ruby或Ruby on Rails中使用控制流的异常是一种好习惯吗?

时间:2011-01-19 11:07:26

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

我正在阅读使用Rails的Agile Web Development(第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中的常见做法吗?

3 个答案:

答案 0 :(得分:9)

Rails在使用异常时并不一致。如果没有找到对象,find将引发异常,但是为了保存,您可以选择所需的行为。最常见的形式是:

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

即。 save返回true或false。但也有save!引发异常(在方法名称的末尾添加感叹号是一种Rubyism,用于显示方法是“危险的”,或者是破坏性的,或者仅仅是它有副作用,确切的含义取决于上下文)。

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

我认为术语“最佳实践”实际上并不意味着任何东西,但根据我的经验,异常不再用于控制Ruby中的流程,而不是Java或我使用的任何其他语言。鉴于Ruby没有检查异常,你通常会处理异常。

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

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

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

答案 1 :(得分: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的新记录。

答案 2 :(得分:0)

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

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