Ruby - 检查是否block_given?和!block.nil有什么区别?



我有一个 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在评论中指出的那样,它在块上失败,这引发了异常⇒已修复

最新更新