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

그들은 세 줄을 모두 따라야 할 것이며라는 인스턴스를 만드는 것임을 인식해야 할 것입니다 user.

다음과 같은 경우 :

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

그러면 즉시 명확해질 것입니다. 독자는 인스턴스 user가 생성 되었음을 알기 위해 블록 내부에있는 내용을 읽을 필요가 없습니다 .


답변

탭을 사용하는 또 다른 경우는 객체를 반환하기 전에 조작하는 것입니다.

그래서이 대신 :

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

다시 말하지만, 이것은 논쟁의 여지가 있습니다. 그러나 두 번째 버전이 조금 더 복잡해 보이고 모든 메서드가 동일한 객체에서 호출되고 있는지 확인하기 위해 조금 더 사람이 파싱해야하는 경우가 있습니다.


답변

이것은 일련의 연결 범위디버깅하는 데 유용 할 수 있습니다 .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
user.username = 'foobar'
user

tap그 어색한 귀환을 당신 과 함께

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


답변

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

설명은 tap말한다 :

자신을 블록에 양보 한 다음 자신을 반환합니다. 이 방법의 주요 목적은 체인 내의 중간 결과에 대한 작업을 수행하기 위해 메서드 체인을 “탭”하는 것입니다.

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

  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. 코드를 더 읽기 쉽게 만들기위한 구문 설탕-아래 예에서 변수 hashserver사용하고 코드의 의도를 더 명확하게 만들 수 있습니다.

    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