ruby水龙头的优点

我只是在阅读一篇博客文章,并注意到作者在一些片段中使用了tap

 user = User.new.tap do |u| u.username = "foobar" u.save! end 

我的问题是使用tap的好处或好处是什么? 我不能这样做:

 user = User.new user.username = "foobar" user.save! 

或更好:

 user = User.create! username: "foobar" 

当读者遇到:

 user = User.new user.username = "foobar" user.save! 

他们将不得不遵循所有三条线,然后认识到它只是创build一个名为user的实例。

如果是:

 user = User.new.tap do |u| u.username = "foobar" u.save! end 

那么这将立即清楚。 读者不需要读取块内的内容就可以知道创build了一个实例user

另一个使用tap的例子是在返回之前对对象进行操作。

所以,而不是这个:

 def some_method ... some_object.serialize some_object end 

我们可以节省额外的线路:

 def some_method ... some_object.tap{ |o| o.serialize } end 

在某些情况下,这种技术可以节省更多的一行,使代码更紧凑。

使用点击,就像博客做的一样,只是一个方便的方法。 在你的例子中这可能是过度的,但是如果你想和用户做一堆事情的话,点击可以提供一个更干净的外观界面。 所以,在一个例子中可能会更好一些,如下所示:

 user = User.new.tap do |u| u.build_profile u.process_credit_card u.ship_out_item u.send_email_confirmation u.blahblahyougetmypoint end 

使用上面的代码可以很容易地快速地看到所有这些方法被组合在一起,它们都指向相同的对象(本例中为用户)。 替代scheme是:

 user = User.new user.build_profile user.process_credit_card user.ship_out_item user.send_email_confirmation user.blahblahyougetmypoint 

再一次,这是有争议的 – 但可以这样说,第二个版本看起来有点混乱,并且需要更多的人类parsing来看到所有的方法被调用在同一个对象上。

在函数中形象化你的例子

 def make_user(name) user = User.new user.username = name user.save! end 

这种方法存在很大的维护风险,基本上是隐式的返回值

在那个代码中,你依赖于save! 返回保存的用户。 但是,如果你使用不同的鸭子(或者你现在的鸭子不断发展),你可能会得到其他的东西,如完成状态报告。 因此,对鸭子的更改可能会破坏代码,如果确保使用普通user的返回值或使用水龙头,则不会发生这种情况。

我经常看到这样的事故,特别是那些通常不使用返回值的function,除了一个黑色的小车angular落。

隐含的返回值往往是新手往往会在最后一行之后添加新代码而不注意效果的事情之一。 他们没有看到上面的代码真的意味着什么:

 def make_user(name) user = User.new user.username = name return user.save! # notice something different now? end 

@ sawa的答案变化:

正如已经指出的那样,使用tap可以帮助你弄清楚你的代码的意图(虽然不一定使它更紧凑)。

以下两个函数同样长,但是在第一个函数中,您必须通读结尾才能找出为什么我在开始时初始化一个空的Hash。

 def tapping1 # setting up a hash h = {} # working on it h[:one] = 1 h[:two] = 2 # returning the hash h end 

另一方面,从一开始你就知道正在初始化的哈希将是块的输出(在本例中是函数的返回值)。

 def tapping2 # a hash will be returned at the end of this block; # all work will occur inside Hash.new.tap do |h| h[:one] = 1 h[:two] = 2 end end 

我会说,使用tap没有任何优势。 @sawa指出 ,唯一的潜在好处是,我引用:“读者不需要读取块内部的内容就可以知道创build了一个实例用户。 然而,在这一点上,可以这样说,如果你正在做非简单化的logging创build逻辑,你的意图可以通过将逻辑提取到自己的方法中来更好地传达。

我坚持认为tap是代码可读性的不必要的负担,并且可以不用或者用像Extract Method这样的更好的技术来替代。

tap是一种方便的方法,也是个人喜好。 尝试一下。 然后写一些代码,而不使用水龙头,看看你是否喜欢另一种方式。

由于variables的范围仅限于真正需要的部分,因此会导致代码混乱。 此外,通过保持相关代码在一起,块内的缩进使代码更易读。

tap说明 :

产生自我的块,然后返回自我。 这种方法的主要目的是“挖掘”一个方法链,以便对链中的中间结果执行操作。

如果我们searchrails源代码的tap用法 ,我们可以find一些有趣的用法。 下面是几个项目(不是详尽的列表),将给我们很less的想法如何使用它们:

  1. 根据特定的条件将一个元素追加到数组中

     %w( annotations ... routes tmp ).tap { |arr| arr << 'statistics' if Rake.application.current_scope.empty? }.each do |task| ... end 
  2. 初始化一个数组并返回它

     [].tap do |msg| msg << "EXPLAIN for: #{sql}" ... msg << connection.explain(sql, bind) end.join("\n") 
  3. 作为使代码更易读的语法糖 – 可以说,在下面的例子中,使用variableshashserver使得代码的意图更清晰。

     def select(*args, &block) dup.tap { |hash| hash.select!(*args, &block) } end 
  4. 在新创build的对象上初始化/调用方法。

     Rails::Server.new.tap do |server| require APP_PATH Dir.chdir(Rails.application.root) server.start end 

    以下是testing文件的一个例子

     @pirate = Pirate.new.tap do |pirate| pirate.catchphrase = "Don't call me!" pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}] pirate.save! end 
  5. 在不需要使用临时variables的情况下对yield调用的结果进行操作。

     yield.tap do |rendered_partial| collection_cache.write(key, rendered_partial, cache_options) end 

这对debugging一系列ActiveRecord链接作用域可能很有用

 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') 

这使得在链中的任何一点进行debugging都变得非常容易,而不必将任何东西存储在局部variables中,也不需要对原始代码进行太多改动。

最后,使用它作为快速而不显眼的debugging方式,而不会中断正常的代码执行

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

这是通话链接的帮手。 它将其对象传递给给定的块,并在块结束后返回对象:

 an_object.tap do |o| # do stuff with an_object, which is in o # end ===> an_object 

好处是tap总是返回它所调用的对象,即使这个block返回了其他的结果。 因此,您可以将stream量块插入现有方法stream水线的中间,而不会中断stream程。

如果您想在设置用户名后返回用户,则需要执行此操作

 user = User.new user.username = 'foobar' user 

tap你可以节省这个尴尬的回报

 User.new.tap do |user| user.username = 'foobar' end 

你是对的:在你的例子中使用tap是没有意义的,可能比你的替代品干净。

正如Rebitzele指出的那样, tap只是一个方便的方法,通常用于创build对当前对象的较短引用。

tap一个很好的用例是debugging:你可以修改对象,打印当前状态,然后继续修改同一块中的对象。 看到这里例如: http : //moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions 。

我偶尔喜欢用tap方法来有条件地返回,否则返回当前对象。

可能有一些用途和地方,我们可能能够使用tap 。 到目前为止,我只find了以下两种用法。

1)这种方法的主要目的是为了对链中的中间结果进行操作,以获取一个方法链。 即

 (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a. tap { |x| puts "array: #{x.inspect}" }. select { |x| x%2 == 0 }. tap { |x| puts "evens: #{x.inspect}" }. map { |x| x*x }. tap { |x| puts "squares: #{x.inspect}" } 

2)你有没有发现自己调用某个对象的方法,返回值不是你想要的? 也许你想添加一个任意值到散列中存储的一组参数。 你用Hash []更新它,但是你得到了bar而不是params hash,所以你必须明确地返回它。 即

 def update_params(params) params[:foo] = 'bar' params end 

为了克服这种情况, tap方法开始起作用。 只需在对象上调用它,然后用您想要运行的代码传递一个块。 该对象将被放弃到该块,然后返回。 即

 def update_params(params) params.tap {|p| p[:foo] = 'bar' } end 

有几十个其他用例,请尝试自己find它们:)

资源:
1) API Dock对象点击
2) 你应该使用的五个ruby-methods

您可以使用tap来使代码更加模块化,并且可以实现对局部variables的更好pipe理。 例如,在下面的代码中,您不需要为该方法的范围内新创build的对象分配一个局部variables。 请注意,块variablesu位于块内。 这实际上是Ruby代码的美丽之一。

 def a_method ... name = "foobar" ... return User.new.tap do |u| u.username = name u.save! end end 

在rails中,我们可以明确地使用tap来白名单参数:

 def client_params params.require(:client).permit(:name).tap do |whitelist| whitelist[:name] = params[:client][:name] end end