블록만 받는 함수에 메서드를 그대로 넘겨주고 싶은 적이 있었나요? 아니면 호출하려던 메서드가 객체의 어떤 상위 클래스에서 엉망이 되었는지 추적해 본 적이 있으신가요?
이런 작업들은 method 메서드 하나면 아주 간단하게 처리할 수 있습니다. 잘만 활용하면 의존성을 파악하고, 몇 시간씩 걸릴 디버깅을 단축하며, 코드를 원하는 곳 어디든 보낼 수 있습니다.
람다처럼 쉽게 다루는 메서드
Ruby에는 블록이나 람다를 인자로 받는 메서드가 많습니다. 하지만 람다를 넘기듯 메서드를 다른 메서드에 직접 전달할 수는 없죠. 먼저 method를 사용해야 합니다:
irb(main):001:0> sin_method = Math.method(:sin)
=> #<Method: Math.sin>
irb(main):002:0> (1..10).map(&sin_method)
=> [0.8414709848078965, 0.9092974268256817, 0.1411200080598672, -0.7568024953079282, -0.9589242746631385, -0.27941549819892586, 0.6569865987187891, 0.9893582466233818, 0.4121184852417566, -0.5440211108893699]
여기서 무슨 일이 일어난 걸까요?
첫 번째 줄에서 Math.sin이라는 메서드가 Method 객체로 변환됩니다. Method 객체는 to_proc에 응답하기 때문에 람다를 사용할 수 있는 자리라면 어디서든 동일하게 활용할 수 있습니다:
irb(main):004:0> sin_method = -> (x) { Math.sin(x) }
=> #<Proc:0x007fe9f90a9bd8@(irb):4 (lambda)>
irb(main):005:0> (1..10).map(&sin_method)
=> [0.8414709848078965, 0.9092974268256817, 0.1411200080598672, -0.7568024953079282, -0.9589242746631385, -0.27941549819892586, 0.6569865987187891, 0.9893582466233818, 0.4121184852417566, -0.5440211108893699]
첫 줄을 다시 보세요. 람다를 사용한 이 코드는 앞서 method를 사용한 코드와 정확히 같은 방식으로 동작합니다.
코드 작성 방식에 따라서는 메서드를 람다로 감싸는 것보다 Method 객체를 얻어내는 편이 훨씬 간단할 때도 있습니다. 람다가 필요한데 손에 있는 건 메서드뿐이라면, method 메서드를 떠올려 보세요.
그 메서드, 대체 어디서 왔을까?
객체의 메서드를 호출했는데 기대와 다르게 동작한다면? 누군가 해당 메서드를 오버라이드했거나 몽키 패치를 했을 가능성이 있습니다. 이걸 어떻게 알아낼 수 있을까요?
물론 소스 코드를 몇 시간동안 뒤져볼 수도 있습니다. 하지만 Method에는 작업 속도를 크게 끌어올려 주는 두 가지 메서드가 있습니다.
호출 중인 메서드를 어떤 클래스가 정의했는지 알고 싶다면 owner를 사용하세요:
irb(main):003:0> Task.new.method(:valid?).owner
=> ActiveRecord::Validations
한 걸음 더 나아가 메서드가 정확히 어디에 정의되어 있는지 확인하고 싶다면 source_location을 사용합니다:
irb(main):004:0> Task.new.method(:valid?).source_location
=> ["/usr/local/lib/ruby/gems/2.1.0/gems/activerecord-4.2.0.beta2/lib/active_record/validations.rb", 55]
source_location은 배열을 반환합니다. 첫 번째 요소는 메서드가 정의된 파일의 경로이고, 두 번째 요소는 줄 번호입니다. 이 정보만 있으면 다음에 어디를 봐야 할지 정확히 알 수 있죠.
소스 코드 읽기, 더 이상 뒤질 필요 없이
메서드가 어디에 정의되어 있는지 찾았다면, 이제 그 메서드가 어떻게 구현되어 있는지 궁금해질 차례입니다.
Method 객체만으로는 이게 불가능합니다. 하지만 method_source 젬을 설치하면 많은 메서드의 소스 코드를 콘솔에서 바로 확인할 수 있습니다:
irb(main):002:0> puts Task.new.method(:valid?).source
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
output = super(context)
errors.empty? && output
end
=> nil
심지어 주석까지도 볼 수 있습니다:
irb(main):003:0> puts Task.new.method(:valid?).comment
# Runs all the validations within the specified context. Returns +true+ if
# no errors are found, +false+ otherwise.
#
# Aliased as validate.
#
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
#
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
# some <tt>:on</tt> option will only run in the specified context.
=> nil
정말 멋지지 않나요? 문서가 바로 콘솔 안에 있는 겁니다!
리플렉션(내성)의 매력
제가 가장 좋아하는 Ruby 클래스들은 코드 안에서 코드를 들여다볼 수 있게 해주는 클래스들입니다. Class, Module, Method 같은 클래스들이죠. 이들을 활용하면 실행 중인 코드에 대해 방대한 정보를 얻을 수 있고, 심지어 실시간으로 수정까지 할 수 있습니다. 이런 클래스들을 깊이 공부하고 API를 익혀두면, Ruby로 놀라운 일들을 해낼 수 있게 될 것입니다.