발표자는 Ruby에서 ‘class’ 키워드 사용을 지양해야 하는 구체적인 규칙들을 제시합니다. 이러한 규칙들은 기존의 Ruby 및 Rails 개발 관행에 대한 비판적 시각을 담고 있습니다.
1. 객체 팩토리가 아니라면 클래스가 아니다
- 클래스의 유일한 목적은 객체를 생성하는 ‘객체 팩토리’여야 합니다. 인스턴스 변수, 인스턴스 메서드, 생성자가 없는 클래스(예: 체크섬 계산기)는 모듈로 대체되어야 합니다. 모듈은
extend self를 통해self.호출을 줄이고,private키워드를 사용하여 내부 메서드를 은닉할 수 있어 더 나은 캡슐화를 제공합니다.
2. 디자인 패턴의 이름을 딴 것이라면 클래스가 아니다
-
C++의 한계를 극복하기 위해 고안된 디자인 패턴(예: 팩토리, 데코레이터)은 Ruby의 내장 기능을 통해 자연스럽게 구현되는 경우가 많아 불필요합니다.
ActiveRecord::Base와 같은 추상 기본 클래스(Abstract Base Class)는 C++나 Java와 달리 Ruby에서 필요성이 적습니다. Ruby는 강력한 타입 검사가 없고 믹스인(Mix-in)을 지원하기 때문입니다. -
Person < ActiveRecord::Base와 같은 상속은 ‘Person이 Active Record Base이다’라는 잘못된 의미를 부여하며, 단순히 메서드 주입을 위한 게으른 방식입니다. 대신include ActiveModel::Validations와 같이 필요한 모듈만 명시적으로 포함하여 불필요한 780개 이상의 메서드 주입을 피해야 합니다.
3. 상태가 없다면 클래스가 아니다
-
ApplicationJob의 서브클래스처럼perform이라는 단일 함수만 가지고 다른 함수를 호출하는 클래스는 단순히 함수를 담는 ‘버킷’에 불과합니다. 이러한 클래스는 상태를 가지지 않으므로 클래스가 아닌 모듈이나 독립적인 함수로 구현되어야 합니다. -
생성자에서 인스턴스 변수를 설정하고 다른 함수에서 이를 사용하는 클래스도 마찬가지입니다. 이는 객체를 생성하고 즉시 사용하는 불필요한 복잡성을 추가합니다. 대신 인수를 직접 받아 처리하는 함수로 대체하는 것이 더 명확하고 효율적입니다.
4. 생성 후 유효하지 않다면 클래스가 아니다
- 객체가 생성된 직후 유효하지 않은 상태(예:
builder가nil로 초기화된Director클래스)이며, 추가적인 설정 메서드 호출이 필요한 경우 이는 클래스로서 적합하지 않습니다. 이러한 설계는 구현 세부 사항을 외부에 노출하고 객체의 사용성을 저해합니다. 모든 필요한 정보를 한 번에 받아 객체를 유효한 상태로 만드는 단일 함수/모듈 패턴이 더 바람직합니다.
5. 데이터 버킷이라면 클래스가 아니다
- 단순히 데이터를 담는 ‘PORO’(Plain Old Ruby Object) 역할을 하는 클래스는
attr_accessor를 반복하여 정의하는 대신 Ruby의Struct나 불변 데이터 구조를 위한Data를 사용해야 합니다.Struct에 메서드를 추가할 수도 있지만, 비즈니스 로직은date_of_birth와 같은 속성을 가진 모든 객체를 처리할 수 있는 독립적인 함수로 분리하여 Ruby의 다형성을 활용하는 것이 좋습니다. 이는 코드를 더 일반적이고 테스트하기 쉽게 만듭니다.
Elixir의 영향과 장점
- 발표자는 Elixir와 같은 함수형 언어 경험을 통해 클래스 없는 코드 작성의 이점을 깨달았다고 언급합니다. 이 접근 방식은 코드의 결합도를 현저히 낮추고, 메서드 크기를 줄이며, ‘Fat Model’ 문제를 해결하고, 메서드 수준의 다형성을 제공하며, 파일 구조를 유연하게 만듭니다. 결과적으로 디버깅 및 테스트가 훨씬 쉬워집니다. 컨트롤러나 모델 테스트 시 필요한 복잡한 컨텍스트 설정 없이 독립적인 메서드를 직접 호출할 수 있기 때문입니다.