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

Ruby - What are the differences between checking if block_given? and !block.nil?

我有一个 ruby 方法需要检查是否有块传递给它。 一位同事建议简单地检查 block.nil? 是否在性能上稍微快一些并且适用于命名块。这已经很烦人了,因为他正在使用命名块并使用 block.call 而不是 yield 调用它,后者已被证明是 significantly faster,因为命名块在术语中更容易理解可读性。

版本 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 稍快,但是快速查看 source code 似乎表明 block_given? 是线程安全的。

这两种方法之间的主要区别是什么?请帮我证明他是错的!

我认为主要区别在于block_given?可以在方法定义中不显式定义&block的情况下使用:

def named_block
  if !block_given?
    puts "No block"
  else 
    yield
  end
end

就可读性而言,哪个版本更好?有时显式命名块可以更具可读性,有时 yield 可以更具可读性。这也是个人喜好的问题。

就速度而言,在您包含的基准测试中,yield 更快。这是因为 Ruby 不必为块初始化新对象 (Proc) 并将其分配给变量。

首先,虽然单个 nil? 检查可能比 block_given? 快,但捕获块的速度很慢。因此,除非您无论如何都要捕获它,否则性能参数无效。

其次,更容易理解。只要你看到 block_given?,你就知道发生了什么。当你有了x.nil?的时候,你要停下来想一想x是什么

第三,成语。根据我的经验,绝大多数 Ruby 开发人员会更喜欢 block_given?。在罗马时...

最后,你可以保持一致。如果你总是使用 block_given? 问题就解决了。如果您使用 nil? 检查,则必须捕获块。

  • 存在性能开销。
  • 它更冗长,这是 Ruby 主义者试图避免的。
  • 命名是编程中最困难的事情之一。 :) 你能为 Enumerable#map 块取一个好名字吗?
  • "Grepability" 是代码库的理想特征。如果您想找到检查您是否获得区块的所有位置,进行 nil? 检查可能会很困难。

还有另一种方法可以做到这一点:

def named_block
   (Proc.new rescue puts("No block") || ->{}).call
end

▶ named_block
#⇒ No block
▶ named_block { puts 'BLOCK!' }
#⇒ BLOCK!

请不要太认真

UPD:正如@Lukas 在评论中指出的那样,它在块上失败,引发异常 ⇒ FIXED