【问题标题】:How to prevent problems with `return` from block when using Ruby `yield`使用Ruby`yield`时如何防止`return`出现问题
【发布时间】:2023-03-07 12:43:01
【问题描述】:

正如每个 Ruby 程序员最终发现的那样,调用包含 return 语句的块或过程可能很危险,因为这可能会退出您当前的上下文:

def some_method(&_block)
   puts 1
   yield
   # The following line will never be executed in this example
   # as the yield is actually a `yield-and-return`.
   puts 3
end

def test
  some_method do
    puts 2
    return
  end
end

test

# This prints "1\n2\n" instead of "1\n2\n3\n"    

如果您想绝对确定某些代码在调用块或过程之后运行,则可以使用begin ... ensure 构造。但是由于在yield过程中如果出现异常也会调用ensure,所以需要多做一些工作。

我创建了一个tiny module,它以两种不同的方式处理这个问题:

  1. 使用safe_yield,使用return关键字检测yield的块或proc是否实际返回。如果是这样,则会引发异常。

    unknown_block = proc do
      return
    end 
    
    ReturnSafeYield.safe_yield(unknown_block)
    # => Raises a UnexpectedReturnException exception
    
  2. 使用call_then_yield,您可以调用一个块,然后确保执行第二个块,即使第一个块包含return 语句。

    unknown_block = proc do
      return
    end
    ReturnSafeYield.call_then_yield(unknown_block) do
      # => This line is called even though the above block contains a `return`.
    end
    

我正在考虑用它创建一个快速 Gem,或者是否有任何内置解决方案可以防止从我错过的嵌套块快速返回?

【问题讨论】:

  • 这将打破默认的 ruby​​ 行为,带来更多的痛苦,而不是利润。想象一下,我是使用这个技巧的代码的消费者。当我将return 放在我的块中时,我希望它会立即通过控制,如果引发了一些奇怪的异常,我会感到非常惊讶。
  • 用干草覆盖狩猎坑只会隐藏陷阱,使代码更难调试。 Ruby 不是一种保护人们免于射腿的语言,而这是它的主要优势。
  • 这不是一个好主意,但它一个好问题。感谢您提出一个有趣的问题。
  • 你为什么要传递(甚至创建)一个包含return 语句的proc?
  • 我,我认为应该完全允许用户在脚上开枪。

标签: ruby yield


【解决方案1】:

有一个内置的解决方案来检测一个块是否包含return 语句。

可以使用RubyVM::InstructionSequence.disasm反汇编用户传入的block,然后搜索throw 1,代表return语句。

这是一个示例实现:

def safe_yield(&block)
  if RubyVM::InstructionSequence.disasm(block) =~ /^\d+ throw +1$/
    raise LocalJumpError
  end

  block.call
end

您可以通过以下方式将其合并到您的库中:

def library_method(&block)
  safe_yield(&block)
  puts "library_method succeeded"
rescue LocalJumpError
  puts "library_method encountered illegal return but resumed execution"
end

以下是行为良好和行为不端用户的用户体验:

def nice_user_method
  library_method { 1 + 1 }
end

nice_user_method
# library_method succeeded

def naughty_user_method
  library_method { return false if rand > 0.5 }
end

naughty_user_method
# library_method encountered illegal return but resumed execution

评论:

使用raise LocalJumpError/rescue LocalJumpError 可以解决您在使用毯子ensure 时遇到的问题。

我选择LocalJumpError 是因为它看起来很相关,并且因为(我认为!)没有可能的 Ruby 代码会导致 LocalJumpError 在这种情况下“自然地”被提升。如果结果是错误的,您可以轻松替换自己的新异常类。

【讨论】:

    猜你喜欢
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 2012-05-05
    • 2012-12-06
    • 1970-01-01
    • 2020-02-18
    • 2016-05-30
    • 2011-02-08
    相关资源
    最近更新 更多