如何正确使用 Object#tap 方法

Object#tap 常常被滥用,我们经常遇到下面的代码:

obj = SomeClass.new.tap do |o|
  o.blah = true
end

这种写法有个毛线用处?根本没有节省一行代码,并且这代码块也增加了理解难度。还如不下面这种传统的写法:

obj = SomeClass.new
obj.blah = true
obj

代码行数一样;返回同样的结果;只有一个局部变量;逻辑清晰易懂。

那么什么情况下应该使用 tap 呢,我觉得下面这样的例子比较合适:

list
  .map { |o| o.foo.bar }
  .reverse
  .more_transform              .tap { |things| puts "transformed: #{things.inspect}" }
  .even_more_transformations

tap 将代码块引入到了方法链。这招在进行调试的时候特别有效。比如:

User
  .active                      .tap { |users| puts "Users so far: #{users.size}" }
  .non_admin                   .tap { |users| puts "Users so far: #{users.size}" }
  .at_least_years_old(25)      .tap { |users| puts "Users so far: #{users.size}" }
  .residing_in('USA')

不需要改动原来的方法链,也不需要引入储存数据的临时变量就可以在方法链上的任意一点进行调试。

所以结论是:tap 是一种不会中断正常执行的代码进行的快速调试方法。

def rockwell_retro_encabulate
  provide_inverse_reactive_current
  synchronize_cardinal_graham_meters
  @result.tap(&method(:puts))
  # Will debug `@result` just before returning it.
end

改变对象

tap 方法会返回原对象,如果你想返回一个被改变的对象咋办?

Combinatory method like tap, but able to return a different value? The magic is break in tap returns another value.

def best_nice_method
  something_complex(a, b, c).tap |obj|
    break obj.one_more_method_call if a_predicate_check?
  end
end

原文

如果觉得我的文章对您有用,请在支付宝公益平台找个项目捐点钱。 @Victor Jan 19, 2016

奉献爱心