답변:
독자가 만날 때 :
user = User.new
user.username = "foobar"
user.save!
그들은 세 줄을 모두 따라야 할 것이며라는 인스턴스를 만드는 것임을 인식해야 할 것입니다 user
.
다음과 같은 경우 :
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
그러면 즉시 명확해질 것입니다. 독자는 인스턴스 user
가 생성 되었음을 알기 위해 블록 내부에있는 내용을 읽을 필요가 없습니다 .
user = User.create!(username: 'foobar')
이 경우와 같은 것이 가장 명확하고 짧을 것이라고 생각합니다. :)-질문의 마지막 예입니다.
user
있습니다. 또한 "독자는 인스턴스 user
가 생성 되었음을 알기 위해 블록 내부에있는 내용을 읽을 필요가 없습니다 ." 첫 번째 코드 블록에서 독자는 "인스턴스 user
가 생성 되었음을 알기 위해"첫 번째 줄만 읽으면되기 때문에 가중치가 없습니다 .
탭을 사용하는 또 다른 경우는 객체를 반환하기 전에 조작하는 것입니다.
그래서이 대신 :
def some_method
...
some_object.serialize
some_object
end
추가 줄을 절약 할 수 있습니다.
def some_method
...
some_object.tap{ |o| o.serialize }
end
어떤 상황에서이 기술은 한 줄 이상을 절약하고 코드를 더 간결하게 만들 수 있습니다.
some_object.tap(&:serialize)
블로거처럼 탭을 사용하는 것은 단순히 편리한 방법입니다. 귀하의 예에서는 과도했을 수 있지만 사용자와 많은 작업을 수행하려는 경우 탭은 분명히 더 깨끗한 인터페이스를 제공 할 수 있습니다. 따라서 아마도 다음과 같은 예에서 더 나을 수 있습니다.
user = User.new.tap do |u|
u.build_profile
u.process_credit_card
u.ship_out_item
u.send_email_confirmation
u.blahblahyougetmypoint
end
위의 방법을 사용하면 모든 메서드가 동일한 개체 (이 예제의 사용자)를 참조한다는 점에서 이러한 모든 메서드가 함께 그룹화되어 있음을 쉽게 확인할 수 있습니다. 대안은 다음과 같습니다.
user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint
다시 말하지만, 이것은 논쟁의 여지가 있습니다. 그러나 두 번째 버전이 조금 더 복잡해 보이고 모든 메서드가 동일한 객체에서 호출되고 있는지 확인하기 위해 조금 더 사람이 파싱해야하는 경우가 있습니다.
user = User.new, user.do_something, user.do_another_thing
수 있습니다 ... 왜 이렇게 할 수 있는지 확장 해 주시겠습니까?
tap
은 내 경험에 어떤 이점도 추가 한 적이 없습니다. 지역 user
변수를 만들고 작업하는 것이 훨씬 더 깨끗하고 제 생각에는 읽기 쉽습니다.
u = user = User.new
한 다음 u
설정 호출에 사용했다면 첫 번째 예와 더 일치합니다.
이것은 일련의 연결 범위 를 디버깅하는 데 유용 할 수 있습니다 .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')
따라서 로컬 변수에 아무것도 저장하지 않고 원래 코드를 많이 변경하지 않고도 체인의 어느 지점에서나 디버그하기가 매우 쉽습니다.
마지막으로, 정상적인 코드 실행을 방해하지 않고 빠르고 눈에 잘 띄지 않는 디버깅 방법 으로 사용합니다 .
def rockwell_retro_encabulate
provide_inverse_reactive_current
synchronize_cardinal_graham_meters
@result.tap(&method(:puts))
# Will debug `@result` just before returning it.
end
함수 내에서 예제 시각화
def make_user(name)
user = User.new
user.username = name
user.save!
end
이 접근 방식에는 기본적으로 암시 적 반환 값 이라는 큰 유지 관리 위험이 있습니다.
이 코드에서는 save!
저장된 사용자 를 반환해야합니다. 그러나 다른 오리를 사용하거나 현재의 오리를 사용하는 경우 완료 상태 보고서와 같은 다른 항목을 얻을 수 있습니다. 따라서 오리를 변경하면 코드가 깨질 수 있습니다 user
. 일반 또는 탭을 사용 하여 반환 값을 확인하면 발생하지 않는 일 입니다.
나는 이와 같은 사고를 꽤 자주 보았는데, 특히 하나의 어두운 버그가있는 모서리를 제외하고는 일반적으로 반환 값이 사용되지 않는 함수에서 그렇습니다.
암시 적 반환 값은 초보자가 효과를 알지 못한 채 마지막 줄 뒤에 새 코드를 추가하는 것을 깨뜨리는 경향이 있습니다. 그들은 위의 코드가 실제로 무엇을 의미하는지 알지 못합니다.
def make_user(name)
user = User.new
user.username = name
return user.save! # notice something different now?
end
user
하셨나요?
User.new.tap{ |u| u.username = name; u.save! }
사용자 이름을 설정 한 후 사용자를 반환하려면 수행해야합니다.
user = User.new
user.username = 'foobar'
user
tap
그 어색한 귀환을 당신 과 함께
User.new.tap do |user|
user.username = 'foobar'
end
Object#tap
저에게 가장 일반적인 사용 사례입니다 .
user = User.new.tap {|u| u.username = 'foobar' }
변수의 범위가 실제로 필요한 부분으로 만 제한되기 때문에 코드가 덜 복잡해집니다. 또한 블록 내의 들여 쓰기는 관련 코드를 함께 유지하여 코드를 더 읽기 쉽게 만듭니다.
자신을 블록에 양보 한 다음 자신을 반환합니다. 이 방법의 주요 목적은 체인 내의 중간 결과에 대한 작업을 수행하기 위해 메서드 체인을 "탭"하는 것입니다.
rails 소스 코드에서 tap
usage를 검색 하면 흥미로운 사용법을 찾을 수 있습니다. 다음은 사용 방법에 대한 몇 가지 아이디어를 제공하는 몇 가지 항목 (전체 목록이 아님)입니다.
특정 조건에 따라 배열에 요소 추가
%w(
annotations
...
routes
tmp
).tap { |arr|
arr << 'statistics' if Rake.application.current_scope.empty?
}.each do |task|
...
end
배열 초기화 및 반환
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
...
msg << connection.explain(sql, bind)
end.join("\n")
코드를 더 읽기 쉽게 만들기위한 구문 설탕-아래 예에서 변수 hash
를 server
사용하고 코드의 의도를 더 명확하게 만들 수 있습니다.
def select(*args, &block)
dup.tap { |hash| hash.select!(*args, &block) }
end
새로 생성 된 객체에서 메서드를 초기화 / 호출합니다.
Rails::Server.new.tap do |server|
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
end
아래는 테스트 파일의 예입니다.
@pirate = Pirate.new.tap do |pirate|
pirate.catchphrase = "Don't call me!"
pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
pirate.save!
end
yield
임시 변수를 사용하지 않고 호출 결과에 따라 조치를 취 합니다.
yield.tap do |rendered_partial|
collection_cache.write(key, rendered_partial, cache_options)
end
@sawa의 답변에 대한 변형 :
이미 언급했듯이를 사용 tap
하면 코드의 의도를 파악 하는 데 도움이됩니다 (반드시 더 간결하게 만들지는 않음).
다음 두 함수는 똑같이 길지만 첫 번째 함수에서는 처음에 빈 해시를 초기화 한 이유를 파악하기 위해 끝까지 읽어야합니다.
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
더 강력한 주장을합니다. 나는 당신이를 볼 때 user = User.new
, 의도가 이미 분명 하다는 것에 동의합니다 . 그러나 익명의 데이터 구조는 무엇이든 사용할 수 있으며, tap
메서드는 최소한 데이터 구조가 메서드의 초점임을 분명히합니다.
def tapping1; {one: 1, two: 2}; end
프로그램 사용은 .tap
이 경우에 느 50 %에 관한 것입니다
콜 체인을위한 도우미입니다. 객체를 주어진 블록으로 전달하고 블록이 완료된 후 객체를 반환합니다.
an_object.tap do |o|
# do stuff with an_object, which is in o #
end ===> an_object
이점은 블록이 다른 결과를 반환하더라도 탭이 항상 호출 된 객체를 반환한다는 것입니다. 따라서 흐름을 중단하지 않고 기존 메서드 파이프 라인의 중간에 탭 블록을 삽입 할 수 있습니다.
를 사용하는 데 이점이 없다고 말하고 싶습니다 tap
. @sawa가 지적했듯이 유일한 잠재적 인 이점 은 "독자는 인스턴스 사용자가 생성되었음을 알기 위해 블록 내부에있는 내용을 읽을 필요가 없습니다."라는 것입니다. 그러나 그 시점에서 단순하지 않은 레코드 생성 논리를 수행하는 경우 해당 논리를 자체 메서드로 추출하여 의도를 더 잘 전달할 수 있다고 주장 할 수 있습니다.
나는 tap
코드의 가독성에 대한 불필요한 부담이며 Extract Method 와 같은 더 나은 기술없이 또는 더 나은 기술로 대체 할 수 있다는 의견을 고수합니다 .
하지만 tap
편리한 방법이며, 또한 개인적인 취향입니다. 부여 tap
시도. 그런 다음 탭을 사용하지 않고 코드를 작성하고 다른 방법보다 좋은지 확인하십시오.
우리가 사용할 수있는 용도와 장소는 다양 할 수 있습니다 tap
. 지금까지 tap
.
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. []를 사용 하여 업데이트 하지만 params 해시 대신 back bar가 표시 되므로 명시 적으로 반환해야합니다. 즉
def update_params(params)
params[:foo] = 'bar'
params
end
이 상황을 극복하기 위해 tap
방법이 작용합니다. 객체에서 호출 한 다음 실행하려는 코드가있는 블록을 통과 탭합니다. 객체는 블록에 양보 된 다음 반환됩니다. 즉
def update_params(params)
params.tap {|p| p[:foo] = 'bar' }
end
수십 가지 다른 사용 사례가 있습니다. 직접 찾아보십시오. :)
출처 :
1) API Dock Object 탭
2) 사용해야하는 5 가지 루비 방법
당신이 옳습니다 : tap
당신의 예에서 사용하는 것은 당신의 대안보다 무의미하고 아마도 덜 깨끗합니다.
Rebitzele이 언급했듯이 tap
는 현재 객체에 대한 더 짧은 참조를 만드는 데 자주 사용되는 편리한 방법입니다.
한 가지 좋은 사용 사례 tap
는 디버깅입니다. 객체를 수정하고 현재 상태를 인쇄 한 다음 동일한 블록에서 객체를 계속 수정할 수 있습니다. 예를 들어 http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions 를 참조하십시오 .
나는 때때로 tap
내부 메서드 를 사용 하여 조건부로 일찍 반환하고 그렇지 않으면 현재 개체를 반환합니다.
메소드를 읽는 것이 얼마나 어려운지 측정하는 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
그리고 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!
탭을 사용하여 코드를 모듈화하고 로컬 변수를 더 잘 관리 할 수 있습니다. 예를 들어 다음 코드에서는 메서드 범위에서 새로 만든 개체에 지역 변수를 할당 할 필요가 없습니다. 블록 변수 u 는 블록 내에서 범위가 지정됩니다. 실제로 루비 코드의 아름다움 중 하나입니다.
def a_method
...
name = "foobar"
...
return User.new.tap do |u|
u.username = name
u.save!
end
end
레일에서는 tap
매개 변수를 명시 적으로 허용 목록에 사용할 수 있습니다 .
def client_params
params.require(:client).permit(:name).tap do |whitelist|
whitelist[:name] = params[:client][:name]
end
end
내가 사용한 또 다른 예를 들겠습니다. 사용자를 위해 저장하는 데 필요한 매개 변수를 반환하는 user_params 메소드가 있습니다 (이것은 Rails 프로젝트입니다).
def user_params
params.require(:user).permit(
:first_name,
:last_name,
:email,
:address_attributes
)
end
내가 아무것도 반환하지 않지만 루비가 마지막 줄의 출력을 반환하는 것을 볼 수 있습니다.
그리고 얼마 후 조건부로 새 속성을 추가해야했습니다. 그래서 다음과 같이 변경했습니다.
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
여기에서 tap을 사용하여 지역 변수를 제거하고 반환 값을 제거 할 수 있습니다.
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
함수형 프로그래밍 패턴이 모범 사례가되고있는 세상 ( https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming ) 에서는 실제로 단일 값 tap
으로 볼 수 있습니다. map
, 변환 체인에서 데이터를 수정합니다.
transformed_array = array.map(&:first_transformation).map(&:second_transformation)
transformed_value = item.tap(&:first_transformation).tap(&:second_transformation)
신고 할 필요가 없습니다 item
여기에서 여러 번 할 .
코드 가독성 의 차이 는 순전히 문체입니다.
user = User.new.tap do |u|
u.username = "foobar"
u.save!
end
키 포인트:
u
이제 변수가 블록 매개 변수로 어떻게 사용 .user
변수는 이제 사용자를 가리켜 야합니다 (사용자 이름 : 'foobar'와 함께 저장된 사용자).다음은 읽기 쉬운 소스 코드 버전입니다.
class Object
def tap
yield self
self
end
end
자세한 내용은 다음 링크를 참조하십시오.
User.new.tap &:foobar