我有一个 ruby 方法需要检查是否将块传递给它。 一位同事建议简单地检查block.nil?
的性能是否略快并且适用于命名块。这已经很烦人了,因为他正在使用命名块并使用block.call
而不是yield
调用它,这已被证明要快得多,因为命名块在可读性方面更容易理解。
版本1:
def named_block &block
if block.nil?
puts "No block"
else
block.call
end
end
版本2:
def named_block &block
if !block_given?
puts "No block"
else
block.call
end
end
基准测试显示版本 1 比版本 2 略快,但是快速浏览源代码似乎表明block_given?
是线程安全的。
这两种方法的主要区别是什么?请帮我证明他是错的!
首先,虽然单次nil?
检查可能比block_given?
快,但捕获块的速度很慢。因此,除非您无论如何都要捕获它,否则性能参数是无效的。
其次,它更容易理解。每当你看到block_given?
,你确切地知道发生了什么。当你有x.nil?
时,你必须停下来思考x
是什么。
第三,这是一个成语。根据我的经验,绝大多数 Ruby 开发人员会更喜欢block_given?
.在罗马时...
最后,您可以保持一致。如果您始终使用block_given?
则问题已为您解决。如果使用nil?
检查,则必须捕获块。
- 存在性能开销。
- 它更冗长,这是Ruby主义者试图避免的。
- 命名事物是编程中最困难的事情之一。 :)例如,你能为
Enumerable#map
将要得到的块想出一个好名字吗? -
"可重复性">是代码库应具有的理想特征。如果你想找到所有检查你是否被给予块的地方,做
nil?
检查可能会很困难。
我认为主要区别在于block_given?
可以在方法定义中不显式定义&block
的情况下使用:
def named_block
if !block_given?
puts "No block"
else
yield
end
end
哪个版本在可读性方面更好?有时显式命名块可能更具可读性,有时yield
可能更具可读性。这也是个人喜好的结晶。
当它达到速度时,在你包含的基准测试中,yield
更快。这是因为Ruby不必为块初始化新对象(Proc
)并将其分配给变量。
还有另一种方法可以实现此目的:
def named_block
(Proc.new rescue puts("No block") || ->{}).call
end
▶ named_block
#⇒ No block
▶ named_block { puts 'BLOCK!' }
#⇒ BLOCK!
请不要太认真
UPD:正如@Lukas在评论中指出的那样,它在块上失败,这引发了异常⇒已修复