我们为什么要避免使用其修饰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)