红宝石中的水龙头方法的优点

Kyl*_*cot 109 ruby

我刚刚阅读了一篇博客文章,并注意到作者tap在一个片段中使用了类似于:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end
Run Code Online (Sandbox Code Playgroud)

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

user = User.new
user.username = "foobar"
user.save!
Run Code Online (Sandbox Code Playgroud)

或者更好的是:

user = User.create! username: "foobar"
Run Code Online (Sandbox Code Playgroud)

saw*_*awa 98

当读者遇到:

user = User.new
user.username = "foobar"
user.save!
Run Code Online (Sandbox Code Playgroud)

他们必须遵循所有三行,然后才能认识到它只是创建一个名为的实例user.

如果是:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end
Run Code Online (Sandbox Code Playgroud)

然后那将立即清楚.读者不必阅读块内的内容就知道user创建了实例.

  • 我发现这种用法并不引人注目 - 可以说是没有更多的可读性,这就是为什么在这个页面上.没有强烈的可读性论证,我比较了速度.我的测试表明,对于上述简单实现,运行时间增加了45%,随着对象上的setter数量的增加而减少 - 大约10个或更多,运行时差异可以忽略不计(YMMV).在调试过程中"点击"到一系列方法似乎是一场胜利,否则我需要更多来说服我. (20认同)
  • 我想像`user = User.create!(用户名:'foobar')`在这种情况下将是最清晰和最短的:) - 问题的最后一个例子. (6认同)
  • 这个答案与自身相矛盾,因此没有意义.发生的事情比"只创建一个名为`user`的实例"更多.此外,"读者不必阅读块中的内容就知道创建了一个实例`user`".没有重量,因为在第一个代码块中,读者还只需要读取第一行"知道创建了一个实例`user`". (4认同)
  • @Matt:而且,一旦块完成其工作,就丢弃在流程中做出的任何变量定义.如果只有一个方法调用该对象,您可以编写`User.new.tap&:foobar` (3认同)
  • 在设置的属性数量不重要的情况下,"tap"方法允许代码编辑器提供隐藏块内容. (2认同)

meg*_*gas 35

使用tap的另一种情况是在返回之前对对象进行操作.

所以不是这样的:

def some_method
  ...
  some_object.serialize
  some_object
end
Run Code Online (Sandbox Code Playgroud)

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

def some_method
  ...
  some_object.tap{ |o| o.serialize }
end
Run Code Online (Sandbox Code Playgroud)

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

  • 我会更加激烈:`some_object.tap(&:serialize)` (19认同)

Reb*_*ele 26

正如博客所做的那样,使用tap只是一种方便的方法.在你的例子中可能有点过分,但是如果你想要与用户做一些事情,tap可以说可以提供一个更清晰的界面.所以,在一个例子中可能更好,如下:

user = User.new.tap do |u|
  u.build_profile
  u.process_credit_card
  u.ship_out_item
  u.send_email_confirmation
  u.blahblahyougetmypoint
end
Run Code Online (Sandbox Code Playgroud)

使用上面的内容可以很容易地快速查看所有这些方法被组合在一起,因为它们都引用同一个对象(本例中的用户).替代方案是:

user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint
Run Code Online (Sandbox Code Playgroud)

同样,这是值得商榷的 - 但是可以证明第二个版本看起来有点混乱,需要更多人工解析才能看到所有方法都在同一个对象上调用.

  • 这只是OP已经提出的问题的一个较长的例子,您仍然可以使用user = User.new,user.do_something,user.do_another_thing ...来完成以上所有操作……请您扩展_why_一个可以这样做吗? (2认同)

VKa*_*atz 19

这对于调试一系列链接范围很有用.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')
Run Code Online (Sandbox Code Playgroud)

这使得在链中的任何位置调试变得非常容易,而不必将任何内容存储在本地变量中,也不需要更改原始代码.

最后,使用它作为一种快速且不显眼的调试方式,而不会中断正常的代码执行:

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


Sys*_*ank 13

在函数中可视化您的示例

def make_user(name)
  user = User.new
  user.username = name
  user.save!
end
Run Code Online (Sandbox Code Playgroud)

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

在该代码中,您确实依赖于save!返回已保存的用户.但是如果你使用不同的鸭子(或者你当前的鸭子),你可能会得到其他东西,比如完成状态报告.因此,对鸭子的更改可能会破坏代码,如果user使用普通或使用点击确保返回值,则不会发生这种情况.

我经常看到这样的事故,特别是那些通常不使用返回值的功能,除了一个黑暗的马车角.

隐式返回值往往是新手倾向于破坏在最后一行之后添加新代码但没有注意到效果的事情之一.他们看不到上面代码的真正含义:

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


mon*_*ike 12

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

user = User.new
user.username = 'foobar'
user
Run Code Online (Sandbox Code Playgroud)

有了tap你可以保存尴尬的回报

User.new.tap do |user|
  user.username = 'foobar'
end
Run Code Online (Sandbox Code Playgroud)


Wan*_*ker 11

由于变量的范围仅限于真正需要的部分,因此它会导致代码更简洁.此外,块内的缩进通过将相关代码保持在一起使代码更具可读性.

描述tap:

产生自我到块,然后返回自我.此方法的主要目的是"利用"方法链,以便对链中的中间结果执行操作.

如果我们搜索rails源代码以供tap使用,我们可以找到一些有趣的用法.以下是一些项目(非详尽列表),这些项目将为我们提供如何使用它们的一些想法:

  1. 根据特定条件将元素附加到数组

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

    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
    
    Run Code Online (Sandbox Code Playgroud)
  3. 至于语法糖使代码更易读-可以说,在下面的例子中,使用的变量hash,并server使得代码更清晰的意图.

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

    Rails::Server.new.tap do |server|
       require APP_PATH
       Dir.chdir(Rails.application.root)
       server.start
    end
    
    Run Code Online (Sandbox Code Playgroud)

    以下是测试文件中的示例

    @pirate = Pirate.new.tap do |pirate|
      pirate.catchphrase = "Don't call me!"
      pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
      pirate.save!
    end
    
    Run Code Online (Sandbox Code Playgroud)
  5. yield不必使用临时变量的情况下对调用结果起作用.

    yield.tap do |rendered_partial|
      collection_cache.write(key, rendered_partial, cache_options)
    end
    
    Run Code Online (Sandbox Code Playgroud)


Giu*_*ppe 9

@ sawa答案的变体:

如前所述,使用tap有助于确定代码的意图(而不一定使其更紧凑).

以下两个函数同样长,但在第一个函数中,您必须通读结尾以找出我在开始时初始化空哈希的原因.

def tapping1
  # setting up a hash
  h = {}
  # working on it
  h[:one] = 1
  h[:two] = 2
  # returning the hash
  h
end
Run Code Online (Sandbox Code Playgroud)

另一方面,您从一开始就知道正在初始化的哈希将是块的输出(在这种情况下,是函数的返回值).

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
Run Code Online (Sandbox Code Playgroud)


gyl*_*laz 8

我会说使用没有任何优势tap.唯一的潜在好处,正如@sawa指出的那样,并且我引用:"读者不必阅读块中的内容就知道创建了实例用户." 但是,在这一点上可以说,如果你正在进行非简化的记录创建逻辑,那么通过将逻辑提取到自己的方法中可以更好地传达你的意图.

我认为这tap对代码的可读性是一个不必要的负担,并且可以在没有或用更好的技术(如Extract Method)代替的情况下完成.

虽然tap是一种便利方法,但它也是个人偏好.给tap一试.然后编写一些代码而不使用tap,看看你是否喜欢一种方式而不是另一种方式.


Pus*_*abh 8

它是呼叫链的助手.它将其对象传递给给定块,并在块完成后返回对象:

an_object.tap do |o|
  # do stuff with an_object, which is in o #
end  ===> an_object
Run Code Online (Sandbox Code Playgroud)

好处是tap总是返回它所调用的对象,即使该块返回其他一些结果.因此,您可以在现有方法管道的中间插入一个点击块而不会中断流程.


Evm*_*rov 6

有一个名为flog的工具可以衡量读取方法的难度。“分数越高,代码就越痛苦。”

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

def without_tap
  user = User.new
  user.username = "foobar"
  user.save!
end

def using_create
  user = User.create! username: "foobar"
end
Run Code Online (Sandbox Code Playgroud)

根据 flog 的结果,该方法tap是最难阅读的(我同意它)

 4.5: main#with_tap                    temp.rb:1-4
 2.4:   assignment
 1.3:   save!
 1.3:   new
 1.1:   branch
 1.1:   tap

 3.1: main#without_tap                 temp.rb:8-11
 2.2:   assignment
 1.1:   new
 1.1:   save!

 1.6: main#using_create                temp.rb:14-16
 1.1:   assignment
 1.1:   create!
Run Code Online (Sandbox Code Playgroud)