【问题标题】:ruby: continue in same loop iteration after an exceptionruby:异常后在同一个循环迭代中继续
【发布时间】:2017-08-24 15:21:03
【问题描述】:

我觉得有点奇怪,因为有很多问题有点像这样,但不够接近,或者没有答案。

[编辑:我正在重新表述问题以使其更清楚]

我有一个循环可以做很多事情,并且正在使用它来查看各种选项是否可以使事情变得更易读、更清晰。最后,我搞砸了一些东西,它抛出了异常,被救援抓住了。到现在为止还挺好。但是,我无法找到任何方法让 ruby​​ 在循环的同一迭代中继续执行以下语句 - 异常总是会导致以下语句并继续进行下一次迭代 - 默认行为。重做或重试将毫无意义并导致无限循环,再次遇到相同的错误和异常等,

我有什么办法可以强制 ruby​​ 以某种方式处理错误,然后从中断的地方继续?

这是一个样本,从我不知道从哪里偷来并改编的。

3.times do |i|
  begin
    # first bunch of stuff to do
    # second bunch of stuff to do
    # third bunch of stuff to do
    # fourth bunch of stuff to do
  rescue => e
    p e
  end
end

基本上,在这个例子中,我希望在继续循环的下一次迭代之前执行所有四组东西,即使其中一个会导致错误。我不是要处理错误,让我看到异常结果等,而是有效地忽略它并继续做所有其他的事情。

有什么想法吗?这可能是我以前从未见过的非常明显的事情。

【问题讨论】:

  • 无论是否发生异常,是否都应该执行puts
  • 是的。这模拟了我正在搞砸的主程序中的字符串操作等。
  • 如果您故意使用异常来进行流控制,那可能是个错误。为什么不只是一个if
  • 感谢您的编辑,顺便说一句 - 我只是在您编辑时添加了“”。 :)
  • “我有一个循环可以做很多事情”——也许你应该提供一个更详细的例子。很难根据循环中的单个 begin-rescue 块给出建议 :-)

标签: ruby-on-rails ruby


【解决方案1】:

您正在使用 rescue 子句定义 begin-end 块。如果块引发任何异常并且存在匹配的救援子句,则块将停止执行并执行救援子句。如果没有匹配的救援块,错误将冒泡(并希望由另一个块处理,否则它将未被处理并且您的脚本将停止!)如果有一个 ensure 子句,即使它也会运行有一个例外。

那么这会给我们带来什么影响呢?如果您想防止单个步骤失败并继续进行,每个步骤都需要自己的块:

3.times do |i|
  begin
    first_thing
  rescue => e
    puts "The first thing blew up! #{e.inspect}"
    puts "I'll carry on anyway ¯\\_(ツ)_/¯"
  end

  begin
    second_thing
    third_thing
  rescue => e
    puts "Either the second or third thing blew up... #{e.inspect}"
    puts "Straight on to the fourth thing!"
  end

  begin
    fourth_thing
  rescue => e
    puts "Fourth thing blew up! #{e.inspect}"
  end
end

这样的块有点不寻常,如果出现问题,它应该继续执行 - 这通常也是让后续步骤之一出错的好方法!您可能需要确保在每个点上仍然具有数据完整性,并且后面的步骤不依赖于前面步骤中可能未能发生的事情。

【讨论】:

  • 谢谢大家 - 我想我有我的答案。实际上,没有任何内置的东西可以进行错误处理然后继续。如果我想这样做,单独的块是要走的路。它有点笨重,但你很少能得到生活中的一切!这里的基础是从文件中读取一行,在检查网络是否存在并且可以打开文件之后,我们实际上得到了一行,然后对它/用它做大量独立的事情,所以我很舒服直到出现这种错误和情况,这导致我在浪费了大约一天的时间在无效的解决方案上之后走上了这条路。 :)
【解决方案2】:

begin 块(包括来自 def 的隐含块)在第一个异常处结束。如果你想在不管成功/失败之后单独做某事,那就把它放在块之外。

3.times do |i|
  begin
    raise "Raised from iteration #{i}"
  rescue => e
    p e
  end
  puts "I'm after the exception"
end

如果您想在 returnbreak 等之后做某事,请使用 ensure 块。

5.times do |i|
  begin
    break if i == 3
    raise "Raised from iteration #{i}"
  rescue => e
    p e
  ensure
    puts "I always run #{i}"
  end
end

哪些输出:

#<RuntimeError: Raised from iteration 0>
I always run 0
#<RuntimeError: Raised from iteration 1>
I always run 1
#<RuntimeError: Raised from iteration 2>
I always run 2
I always run 3

如果您真的想忽略并继续执行许多可能抛出的语句,它们每个都需要一个单独的 rescue 块。您可以使用另一种包装方法来做到这一点。但是要非常小心,被忽略的异常实际上并不重要,因为继续是安全的。

设计异常,旨在中止以下操作并尽量避免留下不一致和未知的程序状态。

def suppress
  yield
rescue => e
  puts "Supressing #{e}"
end
5.times do |i|
  suppress { raise "I throw sometimes #{i}" if i <= 3 }
  suppress { raise "I throw sometimes too #{i}" if i > 2 }
  puts "After possible exceptions #{i}"
end

输出:

Supressing I throw sometimes 0
After possible exceptions 0
Supressing I throw sometimes 1
After possible exceptions 1
Supressing I throw sometimes 2
After possible exceptions 2
Supressing I throw sometimes 3
Supressing I throw sometimes too 3
After possible exceptions 3
Supressing I throw sometimes too 4
After possible exceptions 4

【讨论】:

  • 感谢 Fire Lancer - 但如果您知道您的某段代码可能会通过并出现异常,这将是有效的。如果你不知道,或者不知道哪一块,你希望救援把所有的代码都包裹起来,所以你不一定要这样做。想象一下 raise 实际上是一堆东西,在某些情况下,可能会在某些情况下引发异常。
  • 一般来说,在异常之后继续“下一条语句”是不安全的,而且会很困难,如果你真的想要,你需要以某种方式显式包装它们,或者单独显式开始块,或一些包装方法
  • 你不想编写 YOLO 代码来通过异常推土机并继续前进。
  • 请注意,由于这是用 ruby​​-on-rails 标记的,ActiveSupport 会为您添加一个 Kernel#suppress 方法,前提是您没有从中获得 puts 声明
猜你喜欢
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 2018-12-30
  • 1970-01-01
相关资源
最近更新 更多