본 발표는 Ruby on Rails 애플리케이션에 타입 시스템을 도입함으로써 코드 안전성을 강화하고 개발 생산성을 향상시키는 방안에 대해 논합니다. 특히 Sorbet이라는 타입 검사기를 중심으로 RBI, Tapioca와 같은 관련 도구들의 활용법과 실제 도입 사례를 소개하며, 타입 시스템이 개발 프로세스 전반에 미치는 긍정적인 영향에 대해 심층적으로 다룹니다. 궁극적으로는 개발자가 더욱 효율적이고 안정적으로 Rails 코드를 작성할 수 있도록 돕는 것을 목표로 합니다.
타입 시스템 도입의 주요 동기는 코드 상의 잠재적 문제를 개발 초기 단계에 발견하여 버그 발생률을 줄이고, 이를 통해 애플리케이션의 전반적인 안전성을 높이는 것입니다. 또한, 메서드 시그니처, 반환 값 등을 명확히 함으로써 코드의 가독성을 높이고 다른 개발자(혹은 과거의 자신)가 코드를 더 쉽게 이해하고 수정할 수 있도록 하여 개발 경험과 속도를 개선합니다. 특히 변수명/메서드 변경 시 누락, nil 처리 누락, 타입/값 고려 누락 등 동적 언어에서 발생하기 쉬운 문제들을 정적으로 잡아낼 수 있습니다. 실행 단계에서 문제를 발견하는 것보다 코드를 작성하는 단계에서 즉시 피드백을 받는 것이 훨씬 효율적입니다.
타입 시스템 구현을 위해 RBI(Ruby Interface)는 타입 정의를 위한 DSL로 사용되며, Sorbet은 RBI를 기반으로 코드를 정적으로 분석하는 타입 검사기입니다. Shopify와 Stripe에서 개발/활용하며 신뢰성과 빠른 검사 속도(수만 라인 코드 수분 내 검사)를 입증했습니다. Tapioca는 RBI 및 Sorbet을 보조하는 도구로, Gem의 타입 생성, 의존성 Gem RBI 가져오기, 커뮤니티 RBI 활용, 수동 작성 RBI 관리 등을 지원합니다. 특히 Tapioca DSL 컴파일러는 Ruby 코드를 실제로 실행하여 동적으로 생성되는 코드(예: ActiveRecord의 association, 모델 컬럼)에 대한 타입을 자동으로 생성해주는 강력한 기능입니다.
Sorbet 도입 절차는 Gem 추가, Tapioca 초기화, 그리고 타입 에러 수정 단계로 진행됩니다. 초기에는 수백 개의 에러가 발생할 수 있으나, Sorbet 공식 문서의 에러 레퍼런스를 참고하면 대부분 해결 가능하며, 실제 프로젝트에 도입 성공률이 높습니다. 도입 시 코드 변경량 중 상당 부분은 Tapioca 자동 생성 RBI 파일이며, 실제 수동 작업량은 비교적 적습니다. 숙련될 경우 모델 수 200개 이하 프로젝트는 2~3일 내 도입 가능합니다.
실제 Rails 애플리케이션에 Sorbet을 적용할 때는 몇 가지 고려사항이 있습니다. 첫째, Sorbet의 strictness level
을 파일 단위로 설정할 수 있으며, 초기 도입 시에는 true
또는 false
레벨이 권장됩니다. 둘째, 런타임 타입 에러 발생 시 기본 동작은 예외 발생이지만, 안전성 향상을 위해 도입했음에도 기존 동작이 멈추는 것을 방지하기 위해 런타임 에러 발생 시 프로덕션 환경에서는 예외를 발생시키지 않고 Sentry 등으로 로깅하는 방식을 사용할 수 있습니다. 셋째, LSP(Language Server Protocol)를 통해 에디터(VS Code, Vim 등)와 Sorbet을 연동하여 코딩 중 실시간 타입 검사 피드백을 받는 것이 개발 생산성 향상에 매우 중요합니다. 넷째, CI 환경에서 Sorbet 타입 체크(sorbet tc
), Tapioca 생성 RBI 검증(tapioca dsl verify
, tapioca gems verify
), RBI 중복/모순 체크(tapioca check-shims
) 등을 수행하여 코드 품질을 보증해야 합니다.
타입 시스템을 효과적으로 활용한 설계 및 구현 방식도 제시됩니다. 타입 적용은 애플리케이션의 안쪽(lib, model)부터 시작하여 바깥쪽(service, controller)으로 진행하는 것이 좋습니다. 자주 사용되거나 반환 값이 명확하지 않은 메서드부터 타입을 붙이는 것이 효율적입니다. 또한, 값에 따른 단순 if/else
조건 분기 대신 Sum Type과 패턴 매칭을 활용하여 가능한 상태를 명확히 정의하고 고려 누락을 방지하는 것이 좋습니다. 루비 자체에 Sum Type이 없어 Sorbet의 Enum을 활용하거나 커스텀 DSL 컴파일러를 작성하여 지원할 수 있습니다. 예외(Exception)는 정적으로 어떤 예외가 발생할지 예측하기 어렵기 때문에, 대신 Result Type과 같은 성공/실패를 명시적으로 나타내는 타입을 사용하여 실패 가능성을 코드 상에 드러내고 호출자가 이를 반드시 처리하도록 유도하는 것이 더 안전합니다. 발표자는 Result Type 등을 포함하는 자체 Gem인 mangrove
를 개발하여 사용하고 있음을 언급했습니다. 이러한 기법들을 통해 값의 종류를 명시적으로 관리하고 예상치 못한 타입의 유입으로 인한 버그를 예방할 수 있습니다.
결론적으로, Ruby on Rails에 Sorbet 기반의 타입 시스템을 도입하는 것은 초기 설정 및 에러 수정에 다소 노력이 필요하지만, 코드 상의 문제를 조기에 발견하고 리팩토링의 안정성을 높이며 개발 생산성을 크게 향상시키는 효과적인 방법입니다. RBI, Sorbet, Tapioca의 조합과 더불어 DSL 컴파일러, Sum Type, Result Type과 같은 기법들을 적극적으로 활용함으로써 더욱 안전하고 유지보수하기 쉬운 Rails 애플리케이션을 구축할 수 있습니다. 타입 시스템은 단순한 코드 검사를 넘어, 개발자가 코드의 동작과 가능한 상태를 더 깊이 이해하고 견고한 설계를 이끌어내는 데 기여합니다.