programing tip

루비 탭 방식의 장점

itbloger 2020. 8. 9. 10:09
반응형

루비 탭 방식의 장점


방금 블로그 기사를 읽고 있는데 저자가 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
    

@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. @sawa가 지적했듯이 유일한 잠재적 인 이점 은 "독자는 인스턴스 사용자가 생성되었음을 알기 위해 블록 내부에있는 내용을 읽을 필요가 없습니다."라는 것입니다. 그러나 그 시점에서 단순하지 않은 레코드 생성 논리를 수행하는 경우 해당 논리를 자체 메서드로 추출하여 의도를 더 잘 전달할 수 있다고 주장 할 수 있습니다.

나는 tap코드의 가독성에 대한 불필요한 부담이며 Extract Method 와 같은 더 나은 기술없이 또는 더 나은 기술로 대체 할 수 있다는 의견을 고수합니다 .

하지만 tap편리한 방법이며, 또한 개인적인 취향입니다. 부여 tap시도. 그런 다음 탭을 사용하지 않고 코드를 작성하고 다른 방법보다 좋은지 확인하십시오.


콜 체인을위한 도우미입니다. 객체를 주어진 블록으로 전달하고 블록이 완료된 후 객체를 반환합니다.

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

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


당신이 옳습니다 : tap당신의 예에서 사용하는 것은 당신의 대안보다 무의미하고 아마도 덜 깨끗합니다.

Rebitzele이 언급했듯이 tap는 현재 객체에 대한 더 짧은 참조를 만드는 데 자주 사용되는 편리한 방법입니다.

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

나는 때때로 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) Did you ever find yourself calling a method on some object, and the return value not being what you wanted it to? Maybe you wanted to add an arbitrary value to a set of parameters stored in a hash. You update it with Hash.[], but you get back bar instead of the params hash, so you have to return it explicitly. i.e

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

In order to overcome this situation here, tap method comes into play. Just call it on the object, then pass tap a block with the code that you wanted to run. The object will be yielded to the block, then be returned. i.e

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

There are dozens of other use cases, try finding them yourself :)

Source:
1) API Dock Object tap
2) five-ruby-methods-you-should-be-using


In rails we can use tap to whitelist parameters explicitly:

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

I will give another example which I have used. I have a method user_params which returns the params needed to save for the user (this is a Rails project)

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

You can see I dont return anything but ruby return the output of the last line.

Then, after sometime, I needed to add a new attribute conditionally. So, I changed it to something like this:

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

Here we can use tap to remove the local variable and remove the return:

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

You can make your codes more modular using tap, and can achieve a better management of local variables. For example, in the following code, you don't need to assign a local variable to the newly created object, in the scope of the method. Note that the block variable, u, is scoped within the block. It is actually one of the beauties of ruby code.

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

There is a tool called flog that measures how difficult it is to read a method. "The higher the score, the more pain the code is in."

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

and according on flog's result the method with tap is the most difficult to read (and I agree with it)

 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!

What is the difference?

The difference in terms of code readability is purely stylistic.

Code Walk through:

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

Key points:

  • Notice how the u variable is now used as block parameter?
  • After the block is done, the user variable should now point to a User ( with a username: ‘foobar’, and who is also saved).
  • It's just pleasant and easier to read.

API Documentation

Here’s an easy to read version of the source code:

class Object
  def tap
    yield self
    self
  end
end

For more info, see these links:

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

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

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

반응형