Ruby Sorbet에서 RBS 주석을 활용한 타입 검사: Shopify의 혁신

[EN] Inline RBS comments for seamless type checking with Sorbet / Alexandre Terrasa @Morriar

3줄 요약

  • Shopify는 Ruby 개발 경험 개선을 위해 Sorbet 타입 검사에 인라인 RBS 주석을 도입했습니다.
  • 이는 기존 Sorbet의 `sig` 문법과 별도 RBS 파일의 단점(코드 복잡성, 중복)을 해결하여 코드 가독성과 개발자 만족도를 높였습니다.
  • RBS 주석은 런타임 오버헤드 없이 정적 타입 검사를 강화하며, 자동 마이그레이션 도구 및 LSP 지원을 통해 효율적인 개발 환경을 제공합니다.

본 발표는 Shopify의 시니어 스태프 엔지니어인 Alexander Terza가 Ruby on Rails 환경에서 Sorbet을 활용한 타입 검사의 개선 방안, 특히 RBS(Ruby Signature) 주석의 도입과 그 효과에 대해 설명합니다. Shopify는 2019년 Sorbet을 조기 도입하여 대규모 코드베이스의 타입 검사 필요성을 절감했으며, 개발자 설문조사를 통해 기존 타입 명시 방식의 한계와 더 친화적인 문법에 대한 강력한 요구를 확인했습니다.

기존 Sorbet의 sig 문법은 T::Sig 모듈 확장 및 sorbet-runtime gem 의존성을 필요로 하여 코드에 많은 노이즈를 발생시키고, 특히 지역/인스턴스 변수 타입 명시 시 T.let, T.cast와 같은 추가적인 코드 작성을 요구했습니다. 한편, Ruby 3와 함께 등장한 RBS는 별도의 .rbs 동반 파일을 필요로 하여 대규모 코드베이스에서 상당한 코드 중복을 야기하고, 지역 변수 타입 명시 등의 기능을 지원하지 못하는 한계가 있었습니다. 이러한 문제점들을 해결하기 위해 Shopify는 두 방식의 장점을 결합한 인라인 RBS 주석 방식을 개발했습니다.

인라인 RBS 주석은 # type: 또는 #:와 같은 주석 형태로 Ruby 코드 내에 직접 RBS 문법을 사용하여 타입을 명시하는 방식입니다. 이 방식은 Ruby 문법에 얽매이지 않고 RBS의 유연한 타입 표현을 가능하게 하며, 런타임 의존성이 없어 프로덕션 코드에 오버헤드를 주지 않습니다. Sorbet은 타입 검사 파이프라인(파싱 -> 디슈가링 -> 분석 -> 타입 검사)에 새로운 단계를 추가하여 RBS 주석을 처리합니다. 구체적으로, 파싱 단계에서 AST(추상 구문 트리)와 주석 테이블을 생성한 후, 주석과 해당 AST 노드를 연결합니다. 이후 Sorbet은 이 주석 정보를 기반으로 내부적으로 기존 sig 호출과 동일한 형태의 AST를 재작성하여, 나머지 타입 검사 파이프라인은 변경 없이 작동하도록 합니다. 이 과정에서 주석의 원본 위치 정보를 유지하여 LSP(Language Server Protocol)에서 호버(hover) 정보 제공 및 정의로 이동(jump-to-definition)과 같은 기능을 지원하여 개발 편의성을 극대화합니다.

RBS 문법에 직접적으로 존재하지 않는 Sorbet 특화 기능(예: 추상 클래스/메서드, 인터페이스, 제네릭)은 @abstract, @interface 등의 @ 주석 형태로 지원됩니다. 기존 Sorbet sig 문법에서 RBS 주석으로의 원활한 마이그레이션을 돕기 위해 spoom srbc translatespoom srb assertion translate와 같은 자동 변환 도구를 제공하여 코드베이스의 점진적인 전환을 용이하게 합니다. 또한, Ruby LSP는 이미 RBS 주석에 대한 구문 강조 기능을 제공하며, 개발자의 선호에 따라 타입 어노테이션의 투명도를 조절하여 시각적으로 숨길 수 있는 옵션도 제공합니다.

RBS 주석 방식은 개발자에게 더 친화적인 타입 명시 방법을 제공하고 런타임 의존성을 제거하는 큰 장점이 있습니다. 그러나 타입 검사 시간 증가(초기에는 최대 2배 느려졌으나, 현재는 거의 오버헤드 없음), RuboCop 지원 부재, 그리고 런타임 타입 검사 기능의 손실이라는 단점도 존재합니다. 런타임 타입 검사의 경우, 개발 모드에서 Ruby `require` 훅을 통해 `sig` 블록을 재주입하는 방식으로 보완할 수 있습니다. Shopify는 Sorbet 내부 파서를 Prism으로 마이그레이션하여 성능을 더욱 향상시킬 계획이며, RBS 주석 방식은 이미 라이브 상태로 Shopify의 여러 핵심 젬에 적용되고 있습니다. 이 새로운 방식은 Ruby 개발자들이 더욱 효율적이고 즐겁게 타입 검사를 활용할 수 있도록 지원하며, Ruby 생태계의 타입 안전성 향상에 크게 기여할 것으로 기대됩니다.