重构:仅在没有抛出异常时执行代码块

时间:2011-01-24 12:26:21

标签: ruby-on-rails ruby

我有一段代码,只有在前一段代码没有引发错误的情况下才能运行。我已经实现了一个看似hacky的解决方案,我确信在Ruby中有更好的方法。

这是我到目前为止所拥有的:

existing_comments = Comment.all
catch(:creation_failure) do
    begin
        ActiveRecord::Base.transaction do
            results.each do |row|
                Comment.create!(row)
            end
        end
    rescue
        throw(:creation_failure)
    end
    existing_comments.destroy_all
end

要做到这一点还有更好的方法。

6 个答案:

答案 0 :(得分:2)

很难弄清楚你要做的究竟是什么。正如@ehabkost已经指出的那样,如果引发异常,无论如何执行都会中止,所以你无需做任何事情。在引发异常的代码之后出现的任何内容都不会被执行,毕竟那是异常的整点

这样做你想要的吗?

existing_comments = Comment.all
begin
  ActiveRecord::Base.transaction do
    results.each do |row|
      Comment.create!(row)
    end
  end
rescue # You should *never* do this!
else
  existing_comments.destroy_all
end

顺便说一句:你应该永远,在任何情况下,只是盲目地拯救所有例外。你应该只拯救完全你想要的那些。你真的认为盲目地吞下一个ThreadError而没有注意到它是一个好主意吗?仅有ActiveRecordError的39个直接子类,或许其中一个比仅仅拯救所有异常(或至少所有StandardError例外)更合适。

答案 1 :(得分:1)

我建议使用这个重构器:

Comment.transaction do
  Comment.destroy_all
  results.each do |row| 
    comment = Comment.new(row)
    raise ActiveRecord::Rollback unless comment.save 
  end
end

我已将评论销毁移至顶部。虽然它不完全相同(现在新评论不会与现有评论发生冲突),但我认为这更有意义。

请注意throw/catch - 在某些情况下使用它们 - 不应该用于正常编码,否则你最终会得到不可分割的意大利面条代码。

答案 2 :(得分:0)

尝试(我没有机会测试它):

existing_comments = Comment.all
rescue Exception => e do 
  ActiveRecord::Base.transaction do
    results.each do |row|
      Comment.create!(row)
    end
    #you can log the failure messages, etc… by doing something with e
    existing_comments.destroy_all
  end
end

答案 3 :(得分:0)

事务 - http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html - 确保块作为原子操作执行。如果引发异常,则回滚整个事务。

答案 4 :(得分:0)

我会做这样的事情:

existing_comments = Comment.all
begin
  ActiveRecord::Base.transaction do
    results.each do |row|
      Comment.create!(row)
    end
  end
  existing_comments.destroy_all
rescue Exception => e
  # do something with the exception??
end

在你的代码中,通过混合引发异常(由rescue处理)和抛出某些东西(由catch处理),你很难(对我而言)。大多数时候只使用raise

理论:raise / rescue用于处理异常,throw / catch用于控制流,你可以抛出任何东西(不只是异常)。除非确实需要,否则我会避免捕获/扔掉。

因为当引发异常时,流程被中断,我们可以很容易地从这个事实中获利,并且只有在没有异常发生的情况下才会这样做。

希望这会有所帮助:)

答案 5 :(得分:-1)

当我读到你的问题时,我想“等等,这是微不足道的:只需像往常一样编写你的代码”,例如:

def my_method
  do_something
  do_something_else
end

这样,do_something_else仅在do_something未引发异常时才会运行。你不需要做任何特别的事情。

但是,看起来你真正想要做的就是不运行do_something_else 忽略do_something引发的异常,对吗?

我必须注意,如果你打算忽略异常,你应该非常小心,但如果你真的想这样做,一个简单的方法来忽略代码块中的异常而不运行任何其他东西将是只需从方法返回。像这样:

def my_method
    existing_comments = Comment.all
    begin
        ActiveRecord::Base.transaction do
            results.each do |row|
                Comment.create!(row)
            end
        end
    rescue
        # just return. ignore any exceptions from the block above
        return
    end
    # this will run only if the above doesn't raise any exception:
    existing_comments.destroy_all
end