Shim Won

January 18, 2015 5:0 pm

번역 루비 2.2에서의 슈퍼 메소드 디버그

큰 프로젝트에서의 디버깅은 힘듭니다. 루비는 프로세스에서 메소드 메타데이터콜 스택을 노출함으로써 디버그를 쉽게합니다. 최근에 루비 2.2.0에서 이 메타 살펴보기에 슈퍼 메소드 메타데이터가 추가 되었습니다. 이 글에서는 이 정보를 디버그할 때 어떻게 사용하는지와 왜 이게 추가될 필요가 있었는지 설명합니다.

제가 초장기 적었던 글중에 “루비로 루비를 해부”는 루비만 가지고 루비 프로세스를 살펴보고 디버그하는 모든 방법에 대해 적었습니다. 만약 Method 메소드를 들어본적이 없다면 이 동영상은 볼만한 가치가 있습니다.

요약하면, 루비는 어디에 코드가 정의되어 있는지 알뿐 아니라 어떻게 코드를 실행하는지도 압니다. 예를들어 이 작은 클래스를 보죠.

class Dog
  def bark
    puts "woof"
  end
end

우리는 정확히 어디에 Dog#bark가 정의되어 있는지 볼 수 있습니다.

puts Dog.new.bark
# => "woof"
puts Dog.new.method(:bark).source_location.inspect
# => ["/tmp/dog.rb", 2]

누가 미친 메타 프로그래밍을 했거나, 당신이 실수로 메소드를 덮어썻다고 해도, 루비는 항상 어디에 있는 메소드가 호출될지 알려줍니다.

Super 문제

“루비 해부” 발표를 보셨다면, super 메소드에 매우 큰 문제가 있다는것을 아실 겁니다. 호출되는 마지막 메소드의 위치가 어디에 적혀있는지 알려주는게 거의 불가능 하죠.

class SchneemsDog < Dog
  def bark
    super
  end
end

저는 메타 프로그래밍을 이용해 이 문제를 해결했습니다.

cinco = SchneemsDog.new
cinco.class.superclass.instance_method(:bark)
# => ["/tmp/dog.rb", 6]

대부분 동작합니다만, 어떤 종류의 메타프로그래밍을 사용한다면 동작하지 않죠. 예를들어, 이렇게하면 잘못된 결과가 나옵니다.

module DoubleBark
  def bark
    super
    super
  end
end
cinco = SchneemsDog.new
cinco.extend(Doublebark)

이 경우, cinco.barkDoublebark 모듈에 정의된 메소드를 호출합니다.

cinco.bark
# => bark
# => bark
puts cinco.method(:bark)
#<Method: SchneemsDog(DoubleBark)#bark>

실제 “super”는 SchneemsDog 클래스 안에 정의된 것을 참조합니다. 하지만, 코드는 메소드가 Dog안에 있다고 이야기합니다. 이건 틀렸죠.

puts cinco.class.superclass.instance_method(:bark)
# => #<UnboundMethod: Dog#bark>

이는 Doublebark 모듈이 cinco.class의 조상이 아니기 때문입니다. 이 이슈를 어떻게 해결할 수 있을까요?

Super 해결책

기능 #9781에서, 이 정보를 바로 줄 수 있는 메소드 추가를 제안했습니다. 얼마 뒤 나의 동료 중 하나인 “패치 몬스터” 노부요시 나카다가 실행되는 패치를 첨부했고, 이는 7월경에 루비 트렁크 (2.2.0에도 적용 예정)에도 적용됩니다. 만약 루비 2.2.0에서 디버그를 한다면, 이제 Method#super_method를 사용할 수 있습니다. 이 기능은 전에 언급한 것과 같은 코드를 사용합니다.

cinco = SchneemsDog.new
cinco.method(:bark).super_method
# => #<Method: Dog#bark>

이제 Dog 클래스가 아닌 SchneemsDog 클래스의 메소드가 리턴되는 것을 볼 수 있습니다. 결과에서 source_location을 호출하면 이제는 올바른 값이 나옵니다.

module DoubleBark
  def bark
    super
    super
  end
end
cinco = SchneemsDog.new
cinco.extend(Doublebark)
puts cinco.method(:bark)
# => #<Method: SchneemsDog(DoubleBark)#bark>
puts cinco.method(:bark).super_method
# => #<Method: SchneemsDog#bark>

이는 더 간단할 뿐 아니라, 올바른 값입니다. super_method의 리턴값은 어떤 미친 메타프로그래밍을 했더라도, 슈퍼가 호출될 때와 같은 메소드를 호출합니다. 간단한 예이긴 하지만, 실무에서 유용한 무언가를 찾으셨길 바랍니다.