yakutta musluk yönteminin avantajı


116

Sadece bir blog makalesi okuyordum ve yazarın tapbir pasajda şuna benzer bir şey kullandığını fark ettim :

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

Sorum şu ki, kullanmanın faydası veya avantajı tam olarak tapnedir? Sadece yapamaz mıydım:

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

veya daha iyisi:

user = User.create! username: "foobar"

Yanıtlar:


103

Okuyucular karşılaştığında:

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

üç satırı da takip etmeleri ve sonra bunun sadece adlandırılmış bir örnek oluşturduğunu anlamaları gerekir user.

O olsaydı:

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

o zaman bu hemen anlaşılır. Bir okuyucunun, bir örneğin useroluşturulduğunu bilmek için bloğun içinde ne olduğunu okuması gerekmez .


3
@Matt: Ayrıca, blok işini tamamladığında süreçte yapılan değişken tanımlarını da atın. Nesnede çağrılan tek bir yöntem varsa, yazabilirsinizUser.new.tap &:foobar
Boris Stitnicky

28
Bu kullanımı çok zorlayıcı bulmuyorum - tartışmasız daha fazla okunabilir değil, bu yüzden bu sayfadaydı. Güçlü bir okunabilirlik argümanı olmadan, hızı karşılaştırdım. Testlerim, yukarıdakilerin basit uygulamaları için% 45 ek çalışma süresine işaret ediyor ve nesnedeki ayarlayıcı sayısı arttıkça azalıyor - yaklaşık 10 veya daha fazlası ve çalışma zamanı farkı ihmal edilebilir (YMMV). Hata ayıklama sırasında bir yöntem zincirine 'dokunmak' bir kazanç gibi görünüyor, aksi takdirde beni ikna etmek için daha fazlasına ihtiyacım var.
dinman2022

7
Bence user = User.create!(username: 'foobar')bu durumda en açık ve en kısa böyle bir şey :) - sorudan son örnek.
Lee

4
Bu cevap kendisiyle çelişir ve bu nedenle mantıklı değildir. "Sadece adlandırılmış bir örnek oluşturmaktan" daha fazlası oluyor user. Ayrıca, "Bir okuyucunun, bir örneğin useroluşturulduğunu bilmek için bloğun içinde ne olduğunu okuması gerekmez ." ağırlık taşımaz çünkü ilk kod bloğunda, okuyucunun "bir örneğin useroluşturulduğunu bilmek için" yalnızca ilk satırı okuması gerekir .
Jackson

5
Neden buradayım o zaman? Neden hepimiz burada musluğu araştırıyoruz.
Eddie

37

Dokunmayı kullanmak için başka bir durum, nesneyi iade etmeden önce üzerinde değişiklik yapmaktır.

Yani bunun yerine:

def some_method
  ...
  some_object.serialize
  some_object
end

ekstra satır kaydedebiliriz:

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

Bazı durumlarda bu teknik birden fazla satırı kaydedebilir ve kodu daha kompakt hale getirebilir.


24
Daha da sert some_object.tap(&:serialize)
olurdum

28

Blogger'ın yaptığı gibi dokunmayı kullanmak basitçe bir kolaylık yöntemidir. Örneğinizde aşırıya kaçmış olabilir, ancak kullanıcıyla bir çok şey yapmak istediğiniz durumlarda, dokunma muhtemelen daha temiz görünen bir arayüz sağlayabilir. Öyleyse, aşağıdaki gibi bir örnekte belki daha iyi olabilir:

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

Yukarıdakileri kullanmak, tüm bu yöntemlerin aynı nesneye (bu örnekteki kullanıcı) atıfta bulunmaları açısından birlikte gruplandırıldığını hızlı bir şekilde görmeyi kolaylaştırır. Alternatif şudur:

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

Yine, bu tartışmalıdır - ancak ikinci versiyonun biraz daha karmaşık göründüğü ve tüm yöntemlerin aynı nesneye çağrıldığını görmek için biraz daha insan ayrıştırması gerektiği iddia edilebilir.


2
Bu, OP'nin sorusuna zaten koyduğu şeyin daha uzun bir örneğidir, yine de yukarıdakilerin hepsini user = User.new, user.do_something, user.do_another_thingyapabilirsiniz ... Lütfen bunun neden yapılabileceğini açıklar mısınız?
Matt

Örnek aslında aynı olsa da, daha uzun formda gösterildiğinde, musluk kullanımının bu durum için estetik açıdan daha çekici olabileceğini görebiliriz. Göstermeye yardımcı olması için bir düzenleme ekleyeceğim.
Rebitzele

Ben de görmüyorum. Kullanmak tapbenim deneyimlerime hiçbir zaman fayda sağlamadı. Yerel bir userdeğişken yaratmak ve onunla çalışmak bence çok daha temiz ve okunabilir.
gylaz

Bu ikisi eşdeğer değil. Yaptıysanız u = user = User.newve daha sonra ukurulum çağrıları için kullandıysanız , ilk örnekle daha uyumlu olacaktır.
Gerry

26

Bu, bir dizi zincirleme kapsamda hata ayıklarken yararlı olabilir .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')

Bu, zincirin herhangi bir noktasında yerel bir değişkende herhangi bir şey depolamak zorunda kalmadan veya orijinal kodda çok fazla değişiklik gerektirmeden hata ayıklamayı süper kolaylaştırır.

Ve son olarak, normal kod yürütmeyi kesintiye uğratmadan hata ayıklamanın hızlı ve göze çarpmayan bir yolu olarak kullanın :

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

14

Örneğinizi bir işlev içinde görselleştirin

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

Bu yaklaşımla büyük bir bakım riski vardır, temelde örtük getiri değeri .

Bu kodda save!, kaydedilen kullanıcıyı iade etmeye bağlı olursunuz . Ancak farklı bir ördek kullanırsanız (veya mevcut ördek gelişirse), tamamlanma durumu raporu gibi başka şeyler de alabilirsiniz. Bu nedenle, ördekte yapılan değişiklikler kodu bozabilir; bu, dönüş değerini düz bir şekilde sağlarsanız olmayacak bir şeyuser veya dokunarak kullanırsanız .

Bunun gibi kazaları oldukça sık gördüm, özellikle de dönüş değerinin normalde tek bir karanlık arabası köşesi dışında kullanılmadığı işlevlerde.

Örtük dönüş değeri, yeni başlayanların, etkiyi fark etmeden son satırdan sonra yeni kod ekleyen şeyleri kırma eğiliminde olduğu şeylerden biri olma eğilimindedir. Yukarıdaki kodun gerçekte ne anlama geldiğini görmüyorlar:

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

1
İki örneğin arasında kesinlikle hiçbir fark yok. Geri dönmek usermi istedin ?
Bryan Kül

1
Onun amacı buydu: Örnekler tamamen aynı, biri sadece geri dönüş hakkında açık. Onun amacı, dokunarak bundan kaçınılabilmesiydi:User.new.tap{ |u| u.username = name; u.save! }
Obversity

14

Kullanıcı adını ayarladıktan sonra kullanıcıyı iade etmek istiyorsanız, yapmanız gereken

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

Seninle tapo garip dönüşü kurtarabilirsin

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

1
Bu benim için en yaygın tek kullanım durumudur Object#tap.
Lyndsy Simon

1
Sıfır kod satırını kaydettiniz ve şimdi, yöntemin sonuna ne döndüğüne bakarken, bloğun bir #tap bloğu olduğunu görmek için tekrar taramalıyım. Bunun herhangi bir kazanç olduğundan emin değilim.
Irongaze.com

belki ama bu kolayca 1 astar olabilir user = User.new.tap {|u| u.username = 'foobar' }
lacostenycoder

11

Değişkenin kapsamı yalnızca gerçekten ihtiyaç duyulan kısımla sınırlı olduğundan, daha az karmaşık kodla sonuçlanır. Ayrıca, blok içindeki girinti, ilgili kodu bir arada tutarak kodu daha okunaklı hale getirir.

tapDiyor açıklaması :

Kendini bloğa verir ve sonra kendini geri döndürür. Bu yöntemin birincil amacı, zincir içindeki ara sonuçlar üzerinde işlemler gerçekleştirmek için bir yöntem zincirine "girmektir".

Biz ise için raylar kaynak kodlarında arama tapkullanımı , bazı ilginç kullanımlarını bulabilirsiniz. Aşağıda, bunların nasıl kullanılacağına dair bize birkaç fikir verecek birkaç öğe (ayrıntılı liste değil) bulunmaktadır:

  1. Belirli koşullara göre bir diziye bir öğe ekleyin

    %w(
    annotations
    ...
    routes
    tmp
    ).tap { |arr|
      arr << 'statistics' if Rake.application.current_scope.empty?
    }.each do |task|
      ...
    end
  2. Bir diziyi başlatmak ve onu döndürmek

    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
  3. Kodu daha okunaklı hale getirmek için sözdizimsel şeker olarak - Aşağıdaki örnekte değişkenlerin kullanımı söylenebilir hashve serverkodun amacını daha net hale getirir.

    def select(*args, &block)
        dup.tap { |hash| hash.select!(*args, &block) }
    end
  4. Yeni oluşturulan nesnelerde yöntemleri başlatın / çağırın.

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

    Aşağıda test dosyasından bir örnek bulunmaktadır

    @pirate = Pirate.new.tap do |pirate|
      pirate.catchphrase = "Don't call me!"
      pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
      pirate.save!
    end
  5. yieldGeçici bir değişken kullanmak zorunda kalmadan bir aramanın sonucuna göre hareket etmek.

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

9

@ Sawa'nın cevabının bir varyasyonu:

Daha önce de belirtildiği gibi kullanmak tap, kodunuzun amacını anlamanıza yardımcı olur (ancak onu daha kompakt hale getirmesi gerekmez).

Aşağıdaki iki işlev eşit derecede uzun, ancak ilkinde başlangıçta neden boş bir Hash'i başlattığımı anlamak için sonuna kadar okumalısınız.

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

Burada, öte yandan, başlatılan hash'in bloğun çıktısı olacağını (ve bu durumda, fonksiyonun dönüş değeri) baştan beri biliyorsunuz.

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

bu uygulama tapdaha zorlayıcı bir argüman sağlar. Başkalarına, gördüğünüzde user = User.newniyetin zaten net olduğu konusunda hemfikirim . Bununla birlikte, anonim bir veri yapısı herhangi bir şey için kullanılabilir ve tapyöntem en azından veri yapısının yöntemin odak noktası olduğunu açıklığa kavuşturur.
volx757

Emin değil bu örnek daha iyi olup vs benchmarking def tapping1; {one: 1, two: 2}; endgösterileri kullanarak .tapbu durumda yavaş% 50 hakkındadır
lacostenycoder

9

Çağrı zincirleme için bir yardımcıdır. Nesnesini verilen bloğa geçirir ve blok bittikten sonra nesneyi döndürür:

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

Yararı, blok başka bir sonuç döndürse bile, dokunmanın her zaman çağrıldığı nesneyi döndürmesidir. Böylece, akışı kesmeden mevcut bir yöntem boru hattının ortasına bir musluk bloğu ekleyebilirsiniz.


8

Kullanmanın bir avantajı olmadığını söyleyebilirim tap. @Sawa'nın belirttiği gibi tek potansiyel fayda şudur : "Bir okuyucunun, bir örnek kullanıcısının oluşturulduğunu bilmek için bloğun içinde ne olduğunu okuması gerekmez." Bununla birlikte, bu noktada, basit olmayan kayıt oluşturma mantığı yapıyorsanız, bu mantığı kendi yöntemine çıkararak niyetinizin daha iyi iletileceği argümanı yapılabilir.

Ben görüşe sahip tapkod okunabilirliği gereksiz yük ve benzeri olmadan yapılır ve daha iyi bir teknik ile ikame edilebilir Özü Yöntem .

tapKolaylık sağlayan bir yöntem olsa da, aynı zamanda kişisel tercih. Ver tapbir deneyin. Sonra dokunmadan bir kod yazın, bir şekilde diğerinden hoşlanıp hoşlanmadığınızı görün.


4

Kullanabileceğimiz çok sayıda kullanım ve yer olabilir tap. Şimdiye kadar sadece 2 kullanımını buldum tap.

1) Bu yöntemin asıl amacı olan dokunun zinciri içinde ara sonuçlar işlemleri gerçekleştirmek üzere bir metot zinciri. yani

(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) Hiç kendinizi bir nesne için bir yöntem çağırırken buldunuz mu ve dönüş değeri sizin istediğiniz gibi değil mi? Belki bir karmada saklanan bir dizi parametreye rastgele bir değer eklemek istediniz. İle güncelleyinHash. [] İle , ancak params hash'i yerine çubuğu geri alırsınız , bu yüzden onu açıkça döndürmeniz gerekir. yani

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

Burada bu durumu aşmak için tapyöntem devreye giriyor. Sadece nesnede çağırın, ardından çalıştırmak istediğiniz kodu içeren bir bloğa dokunun. Nesne bloğa verilecek, sonra iade edilecektir. yani

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

Düzinelerce başka kullanım durumu var, bunları kendiniz bulmaya çalışın :)

Kaynak:
1) API Dock Nesnesine dokunun
2) kullanmanız gereken beş yakut yöntemi


3

Haklısın: kullanımı tap örneğinizde kullanımı biraz anlamsız ve muhtemelen alternatiflerinizden daha az temiz.

Rebitzele'nin belirttiği gibi, tap , genellikle mevcut nesneye daha kısa bir referans oluşturmak için kullanılan kullanışlı bir yöntemdir.

tapHata ayıklama için iyi bir kullanım örneği : nesneyi değiştirebilir, mevcut durumu yazdırabilir ve ardından aynı bloktaki nesneyi değiştirmeye devam edebilirsiniz. Örneğin buraya bakın: http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions .

Zaman zaman tapmevcut nesneyi başka türlü döndürürken koşullu olarak erken dönmek için iç yöntemleri kullanmayı seviyorum .



3

Bir yöntemi okumanın ne kadar zor olduğunu ölçen flog adlı bir araç vardır . "Puan ne kadar yüksekse kod o kadar acı çekiyor."

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

ve flog'un sonucuna göre, yöntem tapokunması en zor olanıdır (ve ben buna katılıyorum)

 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!

1

Dokunarak kodlarınızı daha modüler hale getirebilir ve yerel değişkenlerin daha iyi yönetilmesini sağlayabilirsiniz. Örneğin, aşağıdaki kodda, yöntem kapsamında yeni oluşturulan nesneye yerel bir değişken atamanıza gerek yoktur. U blok değişkeninin kapsamının blok içinde olduğuna dikkat edin . Aslında Ruby kodunun güzelliklerinden biridir.

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

1

Raylarda, tapparametreleri açık bir şekilde beyaz listeye eklemek için kullanabiliriz :

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

1

Kullandığım başka bir örnek vereceğim. Kullanıcı için kaydetmesi gereken parametreleri döndüren bir user_params yöntemim var (bu bir Rails projesidir)

def user_params
  params.require(:user).permit(
    :first_name,
    :last_name,
    :email,
    :address_attributes
  )
end

Gördüğünüz gibi hiçbir şey geri vermiyor ama Ruby son satırın çıktısını veriyor.

Sonra, bir süre sonra koşullu olarak yeni bir öznitelik eklemem gerekiyordu. Ben de bunu şöyle bir şeye değiştirdim:

def user_params 
  u_params = params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  )
  u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  u_params
end

Burada yerel değişkeni kaldırmak ve dönüşü kaldırmak için tap özelliğini kullanabiliriz:

def user_params 
  params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  ).tap do |u_params|
    u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  end
end

1

Fonksiyonel programlama desen en iyi yöntem (oluyor dünyada https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming ), gördüğünüz tapbir şekilde, mapgerçekten, tek bir değer üzerinde , verilerinizi bir dönüşüm zincirinde değiştirmek için.

transformed_array = array.map(&:first_transformation).map(&:second_transformation)

transformed_value = item.tap(&:first_transformation).tap(&:second_transformation)

itemBurada birden çok kez beyan etmeye gerek yok .


0

Fark ne?

Kod okunabilirliği açısından fark tamamen stilistiktir.

Kod Gözden Geçirme:

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

Anahtar noktaları:

  • uDeğişkenin artık blok parametresi olarak nasıl kullanıldığına dikkat edin.
  • Engelleme tamamlandıktan sonra, userdeğişken şimdi bir Kullanıcıyı işaret etmelidir (kullanıcı adı: 'foobar' ve ayrıca kaydedilmiş olan).
  • Okuması hoş ve daha kolay.

API Belgeleri

İşte kaynak kodun okunması kolay bir sürümü:

class Object
  def tap
    yield self
    self
  end
end

Daha fazla bilgi için şu bağlantılara bakın:

https://apidock.com/ruby/Object/tap

http://ruby-doc.org/core-2.2.3/Object.html#method-i-tap

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.