2010-04-09 79 views
24

我想知道是否有人会分享他们的最佳做法/策略处理异常&错误。现在我不问什么时候抛出异常(这里已经得到了严格的回答:SO: When to throw an Exception)。而且我没有将它用于我的应用程序流 - 但是有合法的例外情况一直发生。例如,最流行的将是ActiveRecord :: RecordNotFound。什么是处理它的最好方法?干的方式?在Rails中处理异常和错误的最佳策略是什么?

现在我正在我的控制器内进行很多检查,所以如果Post.find(5)返回无 - 我检查并发出一条消息。然而,尽管这非常精细 - 但从某种意义上说,我需要检查每个控制器中的异常,但它们中的大多数基本上都是相同的,并且与未找到的记录未找到或相关记录有关,因此有点麻烦 - 例如因为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 

而且这将在情况Post.find(5)工作没有找到,但怎么样Post.find(5).comments[0].created_at - 我的意思是,如果后存在,但还没有评论,对我不能把一个完全成熟的异常?

总结到目前为止,我做了大量的手动检查,使用if/else /除非或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 

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

在这种情况下怎么处理呢? 比方说,你想在视图中显示注释创建日期:

Last comment for this post at : <%= @post.comments[0].created_at %> 

和这个职位不会有任何意见。 你可以做

Last comment for this post at : <%= @post.comments.last.created_at unless @post.comments.empty? %> 

你可以做一个检查控制器。等等。有几种方法可以做到这一点。但是,处理这个问题的“最佳”方法是什么?

回答

14

事实上,你做很多手动检查异常表明你只是没有使用它们的权利。事实上,你的例子都不例外。

至于不存在的文章 - 你应该期待你的API用户(例如,一个用户通过浏览器使用你的网页)询问不存在的文章。

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

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

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

这样说,并不意味着你不能DRY封装这些情况的代码。至少在某种程度上你可以做到这一点的可能性很大,因为这些都是很常见的情况。

那么,例外情况如何?那么,第一条规则确实是:尽可能稀疏地使用它们。

如果你真的需要使用它们有两种例外的一般(在我看来):

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

  2. 排除用户根本无法使用应用程序的例外情况。这些是最后的手段,应该通过Web应用程序中的500内部服务器错误来处理。

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

- 编辑 -

的建设性作用:

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

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

这远不那么健谈。

DRYing这种情况的另一个常见习惯是使用控制器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 

至于第二种情况(不存在最后的评论),这个问题的一个显而易见的解决方案是把整个事情进入一个帮手:

# 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 

然后,在你的意见,你只需要调用

<%= last_comment_datetime(post) %> 

这样的边缘的情况下(后没有任何评论)将在它自己的地方进行处理,并不会弄乱的观点。

我知道,这些都没有提出处理Rails中的错误的任何模式,但也许有一些这样的重构,你会发现很多需要某种异常/错误处理策略消失。

+0

请 - 见上面的回答我的答复 - 我不能评论后进行适当的缩进代码:-) – konung 2010-04-09 21:52:53

+0

嘿尼克,对不起,听起来如此ranty。我只是没有足够的时间为我的答案添加任何建设性的部分。现在,它在那里。希望整个答案现在更有帮助。 – 2010-04-09 23:04:26

+0

没问题 - 没有冒犯,我的问题也不是很清楚 - 直到我清理它。你给了我几个好的指针 - 我只是稍微等一下,也许有人有更多的建议,然后我会将其标记为已回答。谢谢你的详细解答。 :-) – konung 2010-04-09 23:19:24

1

例外情况适用于特殊情况。糟糕的用户输入通常不是特例;如果有的话,这很常见。当你确实有特殊情况时,你想给自己尽可能多的信息。根据我的经验,最好的方法就是根据调试经验虔诚地改进异常处理。当遇到异常时,你应该做的第一件事就是编写一个单元测试。你应该做的第二件事是确定是否有更多的信息可以添加到异常中。在这种情况下,更多的信息通常采用捕获堆栈中较高层的异常并处理它或者抛出一个新的,更具信息性的例外的形式,这种异常有其他上下文的好处。我个人的规则是,我不喜欢从堆叠中的三个级别捕获异常。如果一个异常需要进一步传播,你需要尽早赶上。

至于暴露用户界面中的错误,if/case声明是完全没问题的,只要你不太深。那时候这种代码难以维护。如果它成为问题,你可以抽象出来。

例如:

def flash_assert(conditional, message) 
    return true if conditional 
    flash[:error] = message 
    return false 
end 

flash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return