programing

루비 탭 방식의 장점

lastcode 2023. 6. 21. 22:38
반응형

루비 탭 방식의 장점

방금 블로그 기사를 읽다가 작가가 사용한 것을 발견했습니다.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!

은 이 세을 모두 " 은세개선따할그나것리그서단것이지고다이이것"라는 이름의 인스턴스를 만들고 있다는 해야 할 입니다.user.

만약 그렇다면:

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

그러면 그것은 즉시 분명해질 것입니다.판독기는 블록 내부의 내용을 읽을 필요 없이 인스턴스를 인식할 수 있습니다.user생성됩니다.

이 기능은 일련의 연결된 범위를 디버깅할 때 유용합니다.

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

위의 방법을 사용하면 모든 방법이 동일한 개체(이 예제의 사용자)를 참조한다는 점에서 함께 그룹화되어 있음을 쉽게 확인할 수 있습니다.대안은 다음과 같습니다.

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

다시 말하지만, 이것은 논쟁의 여지가 있지만, 두 번째 버전이 조금 더 혼란스러워 보이고, 모든 방법이 동일한 개체에서 호출되는지 확인하는 데 약간 더 많은 사람의 구문 분석이 필요하다는 경우가 있습니다.

사용자 이름을 설정한 후 사용자를 반환하려면 다음 작업을 수행해야 합니다.

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

와 함께tap은 그 어색한 답례를 할 수 .

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

변수의 범위가 실제로 필요한 부분으로만 제한되기 때문에 코드가 덜 복잡해집니다.또한 블록 내 들여쓰기는 관련 코드를 함께 보관함으로써 코드를 더 쉽게 읽을 수 있게 합니다.

설명:

블록에 자신을 양보한 다음 자신을 반환합니다.이 방법의 주요 목적은 메소드 체인을 "투입"하여 체인 내의 중간 결과에 대한 작업을 수행하는 것입니다.

레일 소스 코드에서 사용법을 검색하면 흥미로운 사용법을 찾을 수 있습니다.다음은 사용 방법에 대한 몇 가지 아이디어를 제공하는 몇 가지 항목(전체 목록이 아님)입니다.

  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. 읽기 쉽게 하기 통사적 으로서 - 수 .hash그리고.server코드의 의도를 명확하게 합니다.

    def select(*args, &block)
        dup.tap { |hash| hash.select!(*args, &block) }
    end
    
  4. 새로 만든 개체에서 메서드를 초기화/호출합니다.

    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
    
  5. 결과에 따라 작업하려면yield임시 변수를 사용하지 않고 호출할 수 있습니다.

    yield.tap do |rendered_partial|
      collection_cache.write(key, rendered_partial, cache_options)
    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

@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

통화 연결 도우미입니다.지정된 블록으로 객체를 전달하고 블록이 완료된 후 객체를 반환합니다.

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

이점은 블록이 다른 결과를 반환하더라도 탭이 호출된 개체를 항상 반환한다는 것입니다.따라서 흐름을 끊지 않고 기존 메소드 파이프라인의 중간에 탭 블록을 삽입할 수 있습니다.

사용하는 것에는 이점이 없다고 생각합니다.tap@sawa가 지적한 유일한 잠재적 이점은 다음과 같습니다. "인스턴스 사용자가 생성되었음을 알기 위해 블록 내부의 내용을 읽을 필요가 없습니다."하지만 그 시점에서 단순하지 않은 레코드 생성 논리를 수행하는 경우 해당 논리를 자체 방법으로 추출하여 의도를 더 잘 전달할 수 있다는 주장이 제기될 수 있습니다.

라는 의견을 가지고 있습니다.tap는 코드의 가독성에 불필요한 부담이 되며, Extract Method와 같은 더 나은 기술 없이 수행되거나 대체될 수 있습니다.

하는 동안에tap편리한 방법이고, 개인적인 선호이기도 합니다.줘를 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

그리고 플로그의 결과에 따르면 방법은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!

할 수 있는 장소와 할 수 수 .tap지금까지 저는 다음과 같은 두 가지 용도만 찾았습니다.tap.

이 방법의 주요 목적은 메소드 체인을 이용하여 체인 내의 중간 결과에 대한 작업을 수행하는 입니다.

(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}" }

어떤 개체에 대해 메소드를 호출하고 반환 값이 원하는 값이 아니라는 것을 발견한 적이 있습니까?해시에 저장된 매개 변수 집합에 임의 값을 추가하려고 했을 수 있습니다.Hash.[]로 업데이트하지만 params 해시 대신 backbar를 받기 때문에 명시적으로 반환해야 합니다.

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

이상황극위해기복하을,위해,tap방법이 실행됩니다.객체에서 호출한 다음 실행할 코드가 있는 블록을 탭하면 됩니다.객체가 블록에 반환된 다음 반환됩니다.

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

그 밖에도 수십 가지의 사용 사례가 있으니 직접 찾아보세요 :)

다음과 같습니다.
API 도킹 개체
사용해야 할 다섯 가지 방법

맞요: 사법용의 .tap당신의 예는 무의미하고 아마도 당신의 대안들보다 덜 깨끗할 것입니다.

언급했듯이, Rebitzele 지적이듯했이,▁as,tap는 현재 개체에 대한 짧은 참조를 만드는 데 사용되는 편리한 방법일 뿐입니다.

다음을 위한 한 가지 유용한 사용 사례tap디버깅을 위한 것입니다. 개체를 수정하고 현재 상태를 인쇄한 다음 동일한 블록에서 개체를 계속 수정할 수 있습니다.예를 들어 http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions 을 참조하십시오.

나는 가끔 사용하는 것을 좋아합니다.tap내부 메서드는 조건부로 조기에 반환하고 그렇지 않으면 현재 개체를 반환합니다.

탭을 사용하여 코드를 보다 모듈화할 수 있으며 로컬 변수를 보다 효율적으로 관리할 수 있습니다.예를 들어, 다음 코드에서는 메소드의 범위에서 새로 만든 개체에 로컬 변수를 할당할 필요가 없습니다.블럭 변수 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

여기서 탭을 사용하여 로컬 변수를 제거하고 반환을 제거할 수 있습니다.

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변수가 이제 블록 매개 변수로 사용됩니까?
  • 블이완료후된록후,▁the▁after된▁is▁block료,user변수는 이제 사용자(사용자 이름: 'foobar' 및 저장된 사용자)를 가리켜야 합니다.
  • 그것은 그저 즐겁고 읽기 쉽습니다.

API 설명서

다음은 읽기 쉬운 소스 코드 버전입니다.

class Object
  def tap
    yield self
    self
  end
end

자세한 내용은 다음 링크를 참조하십시오.

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

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

위의 답변과는 별도로, 저는 Rspec을 작성하면서 스텁과 조롱에 탭을 사용했습니다.

例시:가 여러 해야 할 때,.여러 개의 인수를 사용하여 스텁하고 조롱해야 하는 복잡한 쿼리가 있을 때 이를 놓쳐서는 안 됩니다.여기서 대안은 다음을 사용하는 것입니다.receive_message_chain(그러나 세부 정보가 부족합니다.)

# Query
Product
  .joins(:bill)
  .where("products.availability = ?", 1)
  .where("bills.status = ?", "paid")
  .select("products.id", "bills.amount")
  .first
# RSpecs

product_double = double('product')

expect(Product).to receive(:joins).with(:bill).and_return(product_double.tap do |product_scope|
  expect(product_scope).to receive(:where).with("products.availability = ?", 1).and_return(product_scope)
  expect(product_scope).to receive(:where).with("bills.status = ?", "paid").and_return(product_scope)
  expect(product_scope).to receive(:select).with("products.id", "bills.amount").and_return(product_scope)
  expect(product_scope).to receive(:first).and_return({ id: 1, amount: 100 })
end)

# Alternative way by using `receive_message_chain`
expect(Product).to receive_message_chain(:joins, :where, :where, :select).and_return({ id: 1, amount: 100 })

언급URL : https://stackoverflow.com/questions/17493080/advantage-of-tap-method-in-ruby

반응형