我将定义价值。但是这个值可能是哈希键的值。如果此键不存在,我将使用 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?
答案 0 :(得分:19)
为什么我们要避免在rails中以修饰符形式使用rescue?
首先,因为它隐藏了所有错误,包括您期望的错误和您不希望的错误,并且一条毯子rescue
无法向您的未来读者表明代码哪些错误是预期的或意外的。这可能不是一个问题 now ,只有一个简单的foo[:a][:b][:c]
,但在任何给定的时间点,某人可能会修改该语句以读取foo[:a][:b][some_method]
并突然发现 > 冒出some_method
的气泡也会被吞下。
其次,通常有一个更好的不那么全面的解决方案,它更明确地设计为只处理您想要忽略的错误:缺少索引或nil
返回值。
在您的情况下,替代方案不您建议的大量if && && &&
。对于哈希,您可以使用dig
,rescue
具有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}}}
,并且想知道为什么foo
是nil
:有一个拼写错误,rescue nil
隐藏它
有关替代方案,请参阅@ meagar的好答案。
答案 3 :(得分:1)
避免
... rescue ...
出于与
相同的原因begin
...
rescue
...
end
既没有指定异常类,也因此默默地跳过所有错误,而不仅仅是你期望的错误。在救出错误时,您应始终尽可能具体。
创建例外也需要很高的成本。
当您引发异常时,将填充回溯,而在Rails中,这意味着创建500到1000个带有文件名和行号的字符串,因为Rails往往具有深度调用堆栈。因此,如果将rescue nil
置于循环中,最终可能很容易创建数千个从未使用过的字符串对象,而使用Ruby的糟糕垃圾收集会影响您的性能。
因此,尽可能尝试使用不会引发异常的替代方案
foo = bar.dig(:a, :b, :c)