我们为什么要避免使用其修饰forms的救援?

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

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

但在实践中告诉我糟糕的风格,因为我使用其修饰forms的救援。 我将改变逻辑以使用检查三个条件。

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

我真的很想知道为什么我们要避免在rails中使用其修改器forms的救援?

我们为什么要避免在rails中以修改器forms使用rescue?

首先,因为它隐藏了所有错误,包括您期望的错误和您不希望的错误,并且全面的rescue并未使您的代码的未来读者明白哪些错误是预期的或意外的。 现在这可能不是问题,只需要一个简单的foo[:a][:b][:c] ,但在任何给定的时间点,某人可能会修改该语句以读取foo[:a][:b][some_method]并且突然之间任何应该冒出some_method错误也会被吞噬。

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

在你的情况下,替代方案并不是你所建议的大量if && && && 。 对于哈希,您可以使用dig ,它具有rescue所有好处,而不会吞下可能引发的每种类型的exception:

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

类似地,对于链式方法调用,您可以使用try (在Rails中)或安全导航操作符(在Ruby 2.3中):

 foo = bar.try(:a).try(:b).try(:c) # or foo = bar&.a&.b&.c 

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

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

这使

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

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

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

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

你可能会失去很多时间检查那个bar真的有{a: {b: {c: :something}}}并想知道为什么foo nil :有一个错字, rescue nil隐藏它。

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

避免

 ... rescue ... 

出于同样的原因

 begin ... rescue ... end 

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

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

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

因此,在可能的情况下尝试使用不会引发exception的替代方案

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