正如每个
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.如果事实证明是假的,您可以轻松地替换自己的新异常类.