在Rails中处理异常和错误的最佳策略是什么?

kon*_*ung 25 exception-handling ruby-on-rails exception

我想知道人们是否会分享他们处理异常和错误的最佳实践/策略.现在我不是在问什么时候抛出异常(这里已经得到了很好的回答:所以 :什么时候抛出异常).我并没有将它用于我的应用程序流程 - 但是有合理的例外情况一直在发生.例如,最受欢迎的是ActiveRecord :: RecordNotFound.处理它的最佳方法是什么?干嘛?

现在我在我的控制器中做了很多检查,所以如果Post.find(5)返回Nil - 我检查并发出一条flash消息.然而,虽然这是非常精细的 - 在某种意义上我需要检查每个控制器中的异常,但它们中的大多数基本相同并且与未找到的记录或未找到的相关记录有关 - 这样因为无论是Post.find(5)没有找到,或者如果你试图显示发布不存在的相关评论,这将抛出一个异常(像 Post.find(5).comments[0].created_at)

我知道你可以在ApplicationController中执行类似的操作,稍后在特定的控制器/方法中覆盖它以获得更细粒度的支持,但这是否是一种正确的方法呢?

class ApplicationController < ActionController::Base
    rescue_from ActiveRecord::RecordInvalid do |exception|
        render :action => (exception.record.new_record? ? :new : :edit)
    end
end
Run Code Online (Sandbox Code Playgroud)

这也适用于Post.find(5)未找到的情况,但是Post.find(5).comments[0].created_at- 我的意思是如果帖子存在但是没有评论,我不能抛出一个完整的异常,对吧?

总结到目前为止,我正在使用if/else/except或者case/when(我偶尔承认开始/救援)并检查nil进行了大量的手动检查?或空?等等,但似乎必须有更好的方式.

回复:

@Milan:您好米兰感谢您的回复 - 我同意您所说的话,我认为我误用了例外.我的意思是,现在我做了很多事情,比如:

if Post.exists?(params[:post_id])
    @p = Post.find(params[:post_id])
else
    flash[:error] = " Can't find Blog Post"
end
Run Code Online (Sandbox Code Playgroud)

我做了很多这种"异常处理",我尽量避免使用开始/救援.但在我看来,这是一个足够普遍的结果/验证/情况,应该有一个DRYer方法来做到这一点,不是吗?你会怎么做这种检查?

在这种情况下如何处理呢?假设您要在视图中显示评论创建日期:

Last comment for this post at : <%= @post.comments[0].created_at %>
Run Code Online (Sandbox Code Playgroud)

这篇文章没有任何评论.你可以做

Last comment for this post at : <%= @post.comments.last.created_at unless @post.comments.empty? %>
Run Code Online (Sandbox Code Playgroud)

你可以办理入住手续.等等.有几种方法可以做到这一点.但处理这个问题的"最佳"方法是什么?

Mil*_*ota 14

您对异常进行大量手动检查的事实表明您没有正确使用它们.事实上,你的例子都不例外.

对于不存在的帖子 - 您应该期望您的API用户(例如,通过浏览器使用您的网站的用户)要求不存在的帖子.

你的第二个例子(Post.find(5).comments [0] .created_at)也不例外.有些帖子只是没有评论,你事先就知道了.那么为什么要抛出异常呢?

ActiveRecord :: RecordInvalid示例的情况也是如此.没有理由通过例外来处理这种情况.用户将一些无效数据输入表单是很平常的事情,并没有什么特别之处.

在某些情况下,对这些情况使用异常机制可能非常方便,但由于上述原因,它是不正确的.

话虽如此,但这并不意味着你不能干掉封装这些情况的代码.由于这些是非常常见的情况,因此至少在某种程度上你可以做到这一点的可能性非常大.

那么,例外呢?嗯,第一个规则确实是:尽可能稀疏地使用它们.

如果你真的需要使用它们,一般有两种例外(我认为):

  1. 不会破坏用户在应用程序内部的一般工作流程的异常(想象一下您的个人资料图片缩略图生成例程中的异常),您可以将其隐藏在用户之外,或者只是在需要时通知他有关问题及其后果的问题

  2. 阻止用户完全使用该应用程序的异常.这是最后的手段,应该通过Web应用程序中的500内部服务器错误来处理.

我倾向于rescue_from仅在后者中使用ApplicationController中的方法,因为第一种类型的适当位置和ApplicationController作为控制器类的最顶层似乎是在这种情况下回归的正确位置(尽管如今)某种Rack中间件可能更适合放置这样的东西).

- 编辑 -

建设性的部分:

至于第一件事,我的建议是开始使用find_by_id而不是find,因为它不会抛出异常,但如果不成功则返回nil.您的代码看起来像这样:

unless @p = Post.find_by_id(params[:id])
  flash[:error] = "Can't find Blog Post"
end
Run Code Online (Sandbox Code Playgroud)

这不是很健谈.

干这种情况的另一个常见习惯是使用控制器before_filters来设置常用变量(在本例中为@p).之后,您的控制器可能如下所示

controller PostsController
  before_filter :set_post, :only => [:create, :show, :destroy, :update]

  def show
      flash[:error] = "Can't find Blog Post" unless @p
  end 

private

  def set_post
    @p = Post.find_by_id(params[:id]) 
  end

end
Run Code Online (Sandbox Code Playgroud)

至于第二种情况(不存在的最后评论),这个问题的一个明显的解决方案是将整个事情转变为帮助者:

# This is just your way of finding out the time of the last comment moved into a 
# helper. I'm not saying it's the best one ;)
def last_comment_datetime(post)
  comments = post.comments
  if comments.empty?
    "No comments, yet."
  else
    "Last comment for this post at: #{comments.last.created_at}"
  end
end
Run Code Online (Sandbox Code Playgroud)

然后,在你的观点中,你只需要打电话

<%= last_comment_datetime(post) %>
Run Code Online (Sandbox Code Playgroud)

通过这种方式,边缘情况(没有任何注释的帖子)将在它自己的位置处理,并且不会使视图混乱.

我知道,这些都没有表明在Rails中处理错误的任何模式,但也许有一些重构,比如这些,你会发现对异常/错误处理的某种策略的大量需求消失了.