【问题标题】:What is wrong with this rescue example?这个救援示例有什么问题?
【发布时间】:2012-12-04 18:02:47
【问题描述】:
x = StandardError.new(:hello)
y = StandardError.new(:hello)
x == y # => true
x === y # => true

begin
  raise x
rescue x
  puts "ok" # gets printed
end

begin
  raise x
rescue y
  puts "ok" # doesn't get printed
end

为什么不打印第二个“ok”?我想不通。我读过 here 说 ruby​​ 使用 === 运算符来匹配救援子句的异常,但表面上并非如此。

我使用的是 Ruby 1.9.3

编辑:所以在执行raise xx == yx === y 之后似乎不再成立。似乎是因为xy no longer have the same backtrace

【问题讨论】:

  • 不,这意味着“捕获任何 StandardError 和子类,并将实例放入变量 y”。 y 不被解释为一个值(如我所愿),而是一个变量名。
  • 我认为如果您尝试在 RFID 标签上进行模式匹配,raise / catch 成语可能不是最清晰的方法。 case 语句或使用模式的动态调度呢?
  • 应用程序有一些与卡通信的“低级”逻辑。这是抛出异常的地方。在此之上是一些更高层次的应用逻辑。标签返回的一些错误实际上是相当高的级别(诸如“文件已存在于标签上”之类的东西)并且必须冒泡给用户。例外似乎是最好的选择。
  • 为什么不使用RFIDException 类并在引发异常raise RFIDException, "additional info" 时附加您需要的任何其他错误信息。然后救援这个异常并应用救援块中需要的任何处理。如果您需要向上冒泡错误,只需重新引发相同的异常。您不营救Exception 班级是在“滥用”营救机制。我不认为它是为你想做的事情而指定的。
  • 如果所有异常都需要在同一个地方处理,这将起作用。我会rescue RFIDException 然后做case exception.type 或类似的事情并处理它。不幸的是,不同的错误必须在调用堆栈的不同级别处理。

标签: ruby exception rescue


【解决方案1】:

我认为这是一个错误,或者说是 Ruby 1.9 的规格不足。请注意,Ruby 2.0 提出了一个

TypeError: class or module required for rescue clause

在第 8 行和第 14 行。

请注意,raise 也不一定按照您的想法进行。当您raise 一个对象时,您实际上并没有引发 那个 对象,而是引发了一个 new 对象,该对象是根据以下简单规则从您传递的对象构造的:

  • 如果对象响应exception,则在对象上调用exception并提高返回值
  • 如果对象是Exception的子类,调用new并提高返回值
  • 否则失败
  • 如果上述任何方法的返回值不是Exception 的实例,也会失败

所以,你不是实际上提高了x,你提高了x.exception。不过,根据Exception#exceptionx.exception 的文档是x

【讨论】:

    【解决方案2】:

    我只想在表格中添加一些内容:OP 代码表明这两个异常是相同的,但它们不是 - 此外,我想说明 OP 的含义:

    所以看起来在做 raise x 之后,x == y 和 x === y 不再成立。这似乎是因为 x 和 y 不再具有相同的回溯。

     x = StandardError.new(:hello)
     y = StandardError.new(:hello)
     class Object
       def all_equals(o)
         ops = [:==, :===, :eql?, :equal?]
         Hash[ops.map(&:to_s).zip(ops.map {|s| send(s, o) })]
       end
     end
    
     puts x.all_equals y # => {"=="=>true, "==="=>true, "eql?"=>false, "equal?"=>false}
    
     begin
       raise x
     rescue
       puts "ok" # gets printed
     end
    
     puts x.all_equals y # => {"=="=>false, "==="=>false, "eql?"=>false, "equal?"=>false}
    

    【讨论】:

      【解决方案3】:

      救援的定义好像是:

      [rescue [error_type [=> var],..]
      

      xy 都不是严格意义上的 error_type。它们是错误类型的实例。我不认为你真的以你在那里的方式运行有效的代码。

      如果你运行:

      begin
        raise x
      rescue y.class
        puts "ok"
      end
      

      然后它将按预期工作。

      还要注意,在 Ruby 1.8 上,x == yx === y 都不会返回 true

      【讨论】:

      • @Norswap 但还是;你到底想做什么?我假设您只是在玩耍,因为您实际上所做的就像在说rescue 1。这没有任何意义。你应该拯救类,而不是实例。
      • @Casper:我认为代码是有效的(即,如果它编译,它会这样做)。是否清楚是另一个问题。但我认为这与问题无关。
      • 我正在围绕 RFID 阅读器构建一个应用程序。与 RFID 标签通信时,可能会返回一些操作码,指示错误。我希望能够“捕捉”特定的错误;但不必为每个错误创建一个新的异常类(这很麻烦)。所以我的想法是用救援来做“模式匹配”:我想捕捉一些满足某些条件的类的异常。我在这里追求的条件是类的某些字段是否具有我希望的值。
      • 1.9 镐书说“对于开始块中的每个救援子句,Ruby 依次将引发的异常与每个参数进行比较。如果引发的异常与参数匹配,Ruby 将执行救援并停止寻找。匹配是使用参数===$!"。所以我认为构造是有效的。我也把手指放在了一些东西上。请参阅问题中的编辑。所以我想这个问题已经回答了:/ 对我来说太糟糕了。
      【解决方案4】:

      编辑:为了澄清问题以供将来回复,因为我认为我的回复不正确,这里的微妙之处在于 xyinstances而不是 classes,通常你会在 raise 语句中使用类。


      如果预期的行为是在第二次救援时打印,y 将无法解决问题。您正在引发类 x 的异常,并且您没有可以处理 x 的救援子句。但是,如果您捕获了公共基类 StandardError,您会看到第二个块打印了“ok”:

      begin
        raise x
      rescue StandardError
        puts "ok"
      end
      

      关于#===,我认为问题在于,当您提出时,您处理的是x实例,而不是x 作为一个类。

      【讨论】:

      • 我知道,但这不是我的问题。我的问题是“为什么rescue x 捕获xrescue y 没有?”。您没有解释为什么 rescue x 会捕获 x。另外,您的回答中有错字,因为我没有“引发x 类的异常”(x 只是保存异常的变量,属于StandardError 类)。我不确定你的意思。
      • @Norswap: x 是一个持有类引用的变量。被捕获的东西是那个类的一个实例。
      • 不,它不包含对类的引用。 x.class => StandardError。它是 StandardError 类的一个实例。
      • @Norswap:你可能是对的——我会做更多的阅读。 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-03
      • 2010-09-14
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多