메서드 충돌 문제의 심각성은 과거 Rails와 FLVTools 라이브러리 사용 시 String 클래스에 동일한 이름의 메서드가 추가되어 함께 사용할 수 없었던 경험이나, BigDecimal 라이브러리가 정수 나눗셈의 결과 타입을 Rational로 변경하여 기존 프로그램의 기대를 깨뜨리는 사례를 통해 명확히 드러납니다. 기존 해결책으로는 Smalltalk의 Selector Namespaces, Classboxes, 그리고 루비의 Refinements가 있습니다.
Refinements는 어휘적 스코프 내에서만 메서드 재정의를 허용하며, local rebinding을 지원하지 않아 재정의된 메서드 내부에서 호출되는 다른 메서드에는 해당 재정의가 적용되지 않는 한계가 있습니다. 발표자는 특정 라이브러리에 대한 몽키 패칭 시 local rebinding이 필요하다고 강조하며, Refinements가 이러한 요구를 충족시키지 못한다고 지적합니다.
Classboxes는 local rebinding을 지원하지만, 다른 Classbox에서 클래스를 가져올 때 내부적으로 사용되는 클래스를 덮어써서 예상치 못한 충돌을 일으킬 수 있는 문제가 있습니다. 예를 들어, `Integer
div를 다르게 정의한 두 Classbox를 함께 사용할 때, List 클래스의 average 메서드가 의도치 않게 다른 Integer
div` 정의를 따르게 되는 상황이 발생할 수 있습니다.
이러한 문제들을 해결하기 위해 제안된 메서드 셸터(Method Shelters)는 다음과 같은 특징을 가집니다.
메서드 셸터의 주요 특징
-
메서드 스코프 제공: 메서드에 스코프를 부여하는 모듈 개념으로, 클래스나 인스턴스 변수는 다루지 않습니다.
- 두 가지 챔버:
- Exposed Chamber: 외부로 공개되는 API를 정의합니다. 이곳에 정의된 메서드는 해당 셸터를
import한 다른 셸터에서도 보이고 덮어쓸 수 있습니다. - Hidden Chamber: 내부적으로만 사용되는 메서드를 정의합니다. 이곳의 메서드는
import한 외부 셸터에서는 호출하거나 덮어쓸 수 없어 강력한 캡슐화를 제공합니다.
- Exposed Chamber: 외부로 공개되는 API를 정의합니다. 이곳에 정의된 메서드는 해당 셸터를
-
local rebinding지원: 재정의된 메서드 내부에서 호출되는 메서드에도 해당 셸터의 재정의가 적용됩니다. -
로드 순서 비의존성: 메서드 충돌 시 모호성(ambiguity)을 제거하기 위해, 두 개 이상의 셸터에서 동일한 메서드가 발견되면 오류를 발생시킵니다.
-
글로벌 메서드 처리: 일반적인 ‘글로벌 메서드’도 셸터 내에서 덮어쓸 수 있도록 설계되었습니다.
- 구문:
shelter :name do ... end,import :other_shelter,hidden do ... end,shelter_eval :name do ... end등의 구문을 사용합니다.
구현 및 성능
-
룩업 알고리즘:
Hidden->Exposed->Global순으로 탐색하며, 인스턴스/클래스 계층 구조와 임포트 경로를 따라 탐색합니다. -
구현: Ruby 1.9.2를 수정하여 구현되었으며, 컨트롤 프레임에 셸터 정보를 추가하고 메서드 캐시를 활용합니다.
-
성능: 빈 메서드 호출 시 약 5%의 오버헤드가 있었고, Rails 개발 환경에서는 캐시 무효화로 인해 50%까지 느려졌지만, 프로덕션 환경에서는 4% 정도로 낮아졌습니다. 특히 TDiary 테스트에서는
method_missing의 비효율적인 호출을 셸터가 캡슐화하여 오히려 성능이 향상되는 사례도 발견되었습니다. 또한, 루비 VM의 연산자 최적화를 셸터 내부에서 재정의하더라도 외부에 영향을 주지 않도록 구현되어 있습니다. -
활용: 셸터를 통해 유니크한 이름을 가진 인스턴스 변수 접근자를 정의함으로써 유사 프라이빗 인스턴스 변수를 구현하는 것도 가능합니다.