正如每个
Ruby程序员最终都会发现的那样,调用包含返回语句的块或者进程可能是危险的,因为这可能会退出你当前的上下文:
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"
如果你想绝对确定你调用了一个块或者proc后有些代码运行,你可以使用begin …确保构造.但是,如果在收益期间有异常,那么确保也会被调用,它需要更多的工作.
我创建了一个tiny module,以两种不同的方式处理这个问题:
>使用safe_yield,检测是否使用return关键字返回生成的块或proc.如果是这样,它会引起一个例外.
unknown_block = proc do return end ReturnSafeYield.safe_yield(unknown_block) # => Raises a UnexpectedReturnException exception
>使用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
解决方法
有一个内置解决方案来检测块是否包含return语句.
您可以使用RubyVM::InstructionSequence.disasm
反汇编用户传递的块,然后搜索throw 1代表返回语句.
这是一个示例实现:
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可以解决您在使用毯子时遇到的问题.
我选择了LocalJumpError,因为它似乎是相关的,因为(我想!)没有可能的Ruby代码会导致在这个上下文中“自然”地引发LocalJumpError.如果事实证明是假的,您可以轻松地替换自己的新异常类.