我想知道人们是否会分享他们处理例外情况的最佳做法/策略。错误。现在我不是在问什么时候抛出异常(这里已经得到了很好的回答:SO: When to throw an 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
这也适用于Post.find(5)
未找到的情况,但Post.find(5).comments[0].created_at
怎么办?我的意思是如果帖子存在但是没有评论,我就不能抛出一个完整的异常,对吧?“ p>
总结到目前为止,我正在使用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
我做了很多这种“异常处理”,我尽量避免使用开始/救援。但在我看来,这是一个足够普遍的结果/验证/情况,应该有一个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? %>
您可以在控制器中进行检查。等等。有几种方法可以做到这一点。但是处理这个问题的“最佳”方法是什么?
答案 0 :(得分:14)
您大量手动检查异常的事实表明您没有正确使用它们。事实上,你的例子都不例外。
对于不存在的帖子 - 您应该期望您的API用户(例如,通过浏览器使用您的网站的用户)要求不存在的帖子。
你的第二个例子(Post.find(5).comments [0] .created_at)也不例外。有些帖子没有评论,你事先就知道了。那么为什么要抛出异常呢?
ActiveRecord :: RecordInvalid示例的情况也是如此。没有理由通过例外来处理这种情况。用户将一些无效数据输入表单是很平常的事情,并没有什么特别之处。
在某些情况下,对这些情况使用异常机制可能非常方便,但由于上述原因,它是不正确的。
话虽如此,但这并不意味着你不能干掉封装这些情况的代码。因为这些是非常常见的情况,所以至少在某种程度上你可以做到这一点的可能性非常大。
那么,例外呢?嗯,第一个规则确实是:尽可能稀疏地使用它们。
如果你真的需要使用它们,一般有两种例外(我认为):
不会破坏用户在应用内部的一般工作流程的异常(想象一下您的个人资料图片缩略图生成例程中的异常),您可以将其隐藏在用户之外,或者只是通知他有关问题及其问题必要时的后果
阻止用户完全使用该应用的例外情况。这是最后的手段,应该通过Web应用程序中的500内部服务器错误来处理。
我倾向于仅在后者中使用ApplicationController中的rescue_from
方法,因为第一种类型的适当位置和ApplicationController作为控制器类的最顶层似乎是正确的位置回到这种情况下(虽然现在某些Rack中间件可能更适合放置这样的东西)。
- 编辑 -
建设性的部分:
至于第一件事,我的建议是开始使用find_by_id而不是find,因为它不会抛出异常,但如果不成功则返回nil。您的代码看起来像这样:
unless @p = Post.find_by_id(params[:id])
flash[:error] = "Can't find Blog Post"
end
远不那么健谈。
干这种情况的另一个常见习惯是使用控制器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中处理错误的任何模式,但是也许通过一些重构这样的重构你会发现对异常/错误处理的某种策略的大量需求就会消失。
答案 1 :(得分:1)
例外情况适用于特殊情况。糟糕的用户输入通常不是例外;如果有的话,这很常见。当您遇到特殊情况时,您希望尽可能多地提供自己的信息。根据我的经验,最好的方法是根据调试经验虔诚地改进异常处理。遇到异常时,首先应该为它编写一个单元测试。您应该做的第二件事是确定是否有更多信息可以添加到例外。在这种情况下,更多信息通常采取以下形式:在堆栈上方捕获异常并处理它或抛出一个新的,更具信息性的异常,这种异常具有额外的上下文。我的个人规则是,我不喜欢从堆栈中的三个级别捕获异常。如果一个例外必须比这更远,你需要提前捕获它。
至于在UI中暴露错误,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