【问题标题】:Why should we avoid using rescue in its modifier form?为什么我们应该避免在其修饰符形式中使用救援?
【发布时间】:2017-01-07 21:00:39
【问题描述】:

我将定义价值。但是这个值可能是哈希键的值。如果此键不存在,我将使用 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)

我真的很想知道为什么我们应该避免在其修饰符形式中使用救援?

【问题讨论】:

  • 值得注意的是,虽然它的风格很糟糕,但它有合法的用途。

标签: ruby error-handling


【解决方案1】:

为什么我们应该避免在 rails 中以修饰符形式使用救援?

首先,因为它隐藏了所有错误,包括您期望的错误和您不期望的错误,并且rescue 的毯子不会让您的代码的未来读者清楚哪些错误是预期的或意外的。这可能不是问题现在,使用简单的foo[:a][:b][:c],但在任何给定时间点,有人可能会修改该语句以读取foo[:a][:b][some_method],然后突然出现应该的任何错误em> 泡出来的some_method 也被吞了。

其次,通常有一个更好的、不包罗万象的解决方案,它更明确地设计为仅处理您打算忽略的错误:缺少索引或nil 返回值。

在您的情况下,替代方案不是您建议的大量 if && && &&。对于哈希,您可以使用 dig,它具有 rescue 的所有优点,而不会吞下可能引发的所有类型的异常:

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

【讨论】:

  • 恕我直言,值得注意的是,引发异常是一项非常昂贵的操作。在密钥不存在的情况下,使用其中一种替代方法返回 nil 通常会更快。
【解决方案2】:

较长的形式更安全,除非没有其他方法,否则我不会在生产中使用较短的版本。 是否使用检查或例外来避免或捕获错误取决于具体情况。例外在处理器时间上代价高昂,因此您对耗时方法的基准测试也是如此,另一方面,进行大量检查但仍不确定可能会更糟。 如果我的代码的可读性会因进行大量检查而丢失,并且速度不是我使用 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,它会为我的所有脚本记录并由一个单独的工具监控。

【讨论】:

    【解决方案3】:

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

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

    你可能会浪费很多时间来检查 bar 是否真的有 {a: {b: {c: :something}}} 并想知道为什么 foonil :有一个错字,rescue nil 隐藏了它。

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

    【讨论】:

      【解决方案4】:

      避免

      ... rescue ...
      

      出于同样的原因

      begin
        ...
      rescue
        ...
      end
      

      两者都没有指定异常类,因此会默默地跳过所有错误,而不仅仅是您期望的错误。在挽救错误时,您应该始终尽可能具体。

      此外,创建异常的成本也很高。

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

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

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

      【讨论】:

        猜你喜欢
        • 2018-03-29
        • 1970-01-01
        • 2011-07-10
        • 1970-01-01
        • 2018-12-26
        • 2020-03-24
        • 1970-01-01
        • 2015-10-24
        • 1970-01-01
        相关资源
        最近更新 更多