正如每个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"
Run Code Online (Sandbox Code Playgroud)
如果您希望绝对确定在调用块或proc 之后运行了一些代码,则可以使用begin ... ensure构造.但是,ensure如果在产量期间存在异常,也会调用它,这需要更多的工作.
我创建了一个小模块,以两种不同的方式处理这个问题:
使用safe_yield,检测生成的块或proc是否实际使用return关键字返回.如果是这样,它会引发异常.
unknown_block = proc do
return
end
ReturnSafeYield.safe_yield(unknown_block)
# => Raises a UnexpectedReturnException exception
Run Code Online (Sandbox Code Playgroud)使用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
Run Code Online (Sandbox Code Playgroud)我正在考虑用这个创建一个快速的Gem,还是有任何内置的解决方案来阻止我错过的嵌套块的快速返回?
有一个内置的解决方案可以检测块是否包含语句return。
您可以使用RubyVM::InstructionSequence.disasm来反汇编用户传入的块,然后在其中搜索throw 1,它代表一条return语句。
这是一个示例实现:
def safe_yield(&block)
if RubyVM::InstructionSequence.disasm(block) =~ /^\d+ throw +1$/
raise LocalJumpError
end
block.call
end
Run Code Online (Sandbox Code Playgroud)
以下是将其合并到您的库中的方法:
def library_method(&block)
safe_yield(&block)
puts "library_method succeeded"
rescue LocalJumpError
puts "library_method encountered illegal return but resumed execution"
end
Run Code Online (Sandbox Code Playgroud)
以下是行为良好和行为不当的用户的用户体验:
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
Run Code Online (Sandbox Code Playgroud)
评论:
使用raise LocalJumpError/rescue LocalJumpError可以解决您在使用毯子时遇到的问题ensure。
我选择LocalJumpError它是因为它看起来相关,并且因为(我认为!)没有可能的 Ruby 代码会导致LocalJumpError在这种情况下“自然”地引发。如果事实证明这是错误的,您可以轻松地替换您自己的新异常类。
| 归档时间: |
|
| 查看次数: |
234 次 |
| 最近记录: |