我们为什么要避免在其修饰形式中使用救援?

时间:2017-01-07 21:00:39

标签: ruby error-handling

我将定义价值。但是这个值可能是哈希键的值。如果此键不存在,我将使用 rescue 来定义值为nil。 例如

foo = bar[:a][:b][:c] rescue nil

但是在实践中告诉我糟糕的风格,因为我使用了修改形式的救援。我将更改逻辑以使用检查三个条件。

foo = bar[:a][:b][:c] if bar.key?(:a) && bar[:a].key?(:b) && bar[:a][:b].key?(:c)

我真的想知道为什么我们应该避免在rails中以修改器形式使用rescue?

4 个答案:

答案 0 :(得分:19)

  

为什么我们要避免在rails中以修饰符形式使用rescue?

首先,因为它隐藏了所有错误,包括您期望的错误和您不希望的错误,并且一条毯子rescue无法向您的未来读者表明代码哪些错误是预期的或意外的。这可能不是一个问题 now ,只有一个简单的foo[:a][:b][:c],但在任何给定的时间点,某人可能会修改该语句以读取foo[:a][:b][some_method]并突然发现 > 冒出some_method的气泡也会被吞下。

其次,通常有一个更好的不那么全面的解决方案,它更明确地设计为只处理您想要忽略的错误:缺少索引或nil返回值。

在您的情况下,替代方案您建议的大量if && && &&。对于哈希,您可以使用digrescue具有foo = bar.dig(:a, :b, :c) 的所有好处,而不会吞噬可能引发的每种类型的异常:

try

同样,对于链式方法调用,您可以使用foo = bar.try(:a).try(:b).try(:c) # or foo = bar&.a&.b&.c (在Rails中)或安全导航操作符(在Ruby 2.3中):

si

答案 1 :(得分:4)

较长的形式更安全,除非没有其他办法,否则我不会在生产中使用短版本。 是否使用检查或执行来避免或捕获错误取决于具体情况。处理时间内的耗费成本很高,因此对于耗时的方法进行基准测试,另一方面进行大量检查但仍然不确定可能更糟。 如果我的代码的可读性会因为进行大量检查而丢失,并且速度不是因为我使用begin ... rescue或def .. rescue但在这种情况下你最好拯救这样的已知异常

begin  
  # -  
  raise "another exception"
rescue SyntaxError 
  # -  
rescue => exception
  @errors += 1
  log exception
  log exception.backtrace  
end   

哪个给出了

another exception
C:/.../test.rb:3:in `<main>'

总是捕获那种异常并放置或更好地记录它,我也用它来引发一个变量@errors,它记录了我的所有脚本并由一个单独的工具监控。

答案 2 :(得分:1)

一个典型的例子,说明为什么这是一个坏主意:

foo = ban[:a][:b][:c] rescue nil

您可能会失去大量时间检查bar确实有{a: {b: {c: :something}}},并且想知道为什么foonil:有一个拼写错误,rescue nil隐藏它

有关替代方案,请参阅@ meagar的好答案。

答案 3 :(得分:1)

避免

... rescue ...

出于与

相同的原因
begin
  ...
rescue
  ...
end

既没有指定异常类,也因此默默地跳过所有错误,而不仅仅是你期望的错误。在救出错误时,您应始终尽可能具体。

创建例外也需要很高的成本。

当您引发异常时,将填充回溯,而在Rails中,这意味着创建500到1000个带有文件名和行号的字符串,因为Rails往往具有深度调用堆栈。因此,如果将rescue nil置于循环中,最终可能很容易创建数千个从未使用过的字符串对象,而使用Ruby的糟糕垃圾收集会影响您的性能。

因此,尽可能尝试使用不会引发异常的替代方案

foo = bar.dig(:a, :b, :c)