Ruby 스타일 가이드

이것은 루비 코드에 대한 GitLab 특정 스타일 가이드입니다. 이 페이지에 기술된 모든 내용은 토론을 위해 다시 여는 것이 될 수 있습니다.

우리는 루비 스타일 가이드 규칙을 시행하기 위해 RuboCop을 사용합니다.

RuboCop 규칙이 없는 경우 일반적으로 다음 스타일 가이드를 참고하여 문법적인 루비 작성 가이드라인을 작성하세요.

일반적으로, 기존의 RuboCop 규칙이나 상기 스타일 가이드에서 스타일이 다루지 않는 경우, 이것은 블로킹 요소가 되어서는 안 됩니다.

일부 스타일은 생각할 필요가 없는 스타일입니다.

또한 보세요:

우리가 규칙이 없는 스타일

이러한 스타일은 RuboCop 규칙에 의해 지지되지 않습니다.

이 섹션에 추가된 각 스타일에 대한 문맥을 제공하고 참조로 사용하려면 이제부터 섹션의 히스토리 노트에서 논의에 대한 링크를 걸어주십시오.

attr_reader를 사용한 인스턴스 변수 액세스

클래스 내부에서 다양한 방법으로 인스턴스 변수에 액세스할 수 있습니다:

# public
class Foo
  attr_reader :my_var

  def initialize(my_var)
    @my_var = my_var
  end

  def do_stuff
    puts my_var
  end
end

# private
class Foo
  def initialize(my_var)
    @my_var = my_var
  end

  private

  attr_reader :my_var

  def do_stuff
    puts my_var
  end
end

# 직접
class Foo
  def initialize(my_var)
    @my_var = my_var
  end

  private

  def do_stuff
    puts @my_var
  end
end

공개 속성은 클래스 외부에서 액세스될 경우에만 사용해야 합니다. 속성이 내부적으로만 액세스되는 경우에는 어떤 전략을 사용하는지에 대해 강한 의견이 없으며, 관련 코드에서 일관성이 있을 필요가 있습니다.

새로운 줄 스타일 가이드

RuboCop의 Layout/EmptyLinesAroundMethodBodyCop/LineBreakAroundConditionalBlock 외에도, RuboCop에 의해 지원되지 않는 다음 가이드라인이 있습니다.

규칙: 관련된 로직을 그룹화하기 위해 새 줄만으로 코드를 분리

# 나쁨
def method
  issue = Issue.new

  issue.save

  render json: issue
end
# 좋음
def method
  issue = Issue.new
  issue.save

  render json: issue
end

규칙: 블록 앞에 새 줄

# 나쁨
def method
  issue = Issue.new
  if issue.save
    render json: issue
  end
end
# 좋음
def method
  issue = Issue.new

  if issue.save
    render json: issue
  end
end
예외: 코드 블록이 다른 코드 블록 내부에서 바로 시작하거나 끝날 때는 새 줄이 필요하지 않습니다.
# 나쁨
def method
  if issue

    if issue.valid?
      issue.save
    end

  end
end
# 좋음
def method
  if issue
    if issue.valid?
      issue.save
    end
  end
end

ActiveRecord 콜백 피하기

ActiveRecord 콜백을 사용하면 “객체의 상태 변경 전후에 로직을 트리거할” 수 있습니다.

콜백은 대안이 없는 경우에 사용하되, 그 이유를 완전히 이해해야만 콜백을 사용하세요.

ActiveRecord 객체에 대한 새로운 라이프사이클 이벤트를 추가할 때, 콜백 대신 서비스 클래스에 로직을 추가하는 것이 좋습니다.

콜백을 피해야 하는 이유

일반적으로 콜백을 피해야 하는 이유:

  • 콜백은 호출 순서가 명확하지 않아 이해하기 어렵고 코드 내러티브를 깨뜨립니다.
  • 콜백은 일반적인 메소드 호출이 아닌 리플렉션에 의존하기 때문에 찾고 탐색하기 어렵습니다.
  • 콜백은 객체의 상태 전체를 항상 트리거하므로 객체 상태에 대한 변경을 선택적으로 적용하기 어렵게 만듭니다.
  • 콜백은 로직을 ActiveRecord 클래스에 갇히게 합니다. 이러한 강한 결합은 너무 많은 비즈니스 로직을 포함하고 있는 뚱뚱한 모델을 유도합니다. 대신 더 재사용 가능하고 조립 가능하며 테스트하기 쉬운 서비스 객체에 로직을 두는 것이 좋습니다.
  • 객체의 부적절한 상태 전이는 속성 유효성 검사를 통해 더 나은 방식으로 강제할 수 있습니다.
  • 콜백의 많은 사용은 팩토리 생성 속도에 영향을 줍니다. 수백 개의 콜백을 가진 일부 클래스는 자동화된 테스트를 위해 해당 객체의 인스턴스를 생성하는 것이 매우 느린 작업이 될 수 있어서 지연된 테스트를 초래할 수 있습니다.

위의 예시들 중 일부는 thoughtbot의 비디오에서 논의됩니다.

GitLab 코드베이스는 콜백에 많이 의존하며, 보이지 않는 종속성 때문에 추가되자마자 리팩토링하기 어려울 수 있습니다. 결과적으로, 이 가이드라인은 기존의 모든 콜백을 제거해야 한다고 요구하지는 않습니다.

Callback을 사용하는 시기

Callback은 특수한 경우에 사용될 수 있습니다. 콜백을 추가하는 것이 합리적인 경우의 몇 가지 예는 다음과 같습니다.

  • 의존성이 콜백을 사용하며 콜백 동작을 재정의하고 싶을 때.
  • 캐시 카운트 증가.
  • 현재 모델의 데이터에만 관련된 데이터 정규화.

콜백에서 서비스로 이동하는 예

다음과 같은 기본 데이터 모델이 있는 프로젝트가 있습니다.

class Project
  has_one :repository
end

class Repository
  belongs_to :project
end

프로젝트가 생성된 후 리포지토리를 생성하고 프로젝트 이름을 리포지토리 이름으로 사용하고 싶다고 가정해 봅시다. Rails에 익숙한 개발자는 아마도 즉시 ActiveRecord 콜백을 사용할 것입니다! 그리고 이 코드를 추가할 것입니다.

class Project
  has_one :repository

  after_initialize :create_random_name
  after_create :create_repository

  def create_random_name
    SecureRandom.alphanumeric
  end

  def create_repository
    Repository.create!(project: self)
  end
end

class Repository
  after_initialize :set_name

  def set_name
    name = project.name
  end
end

class ProjectsController
  def create
    Project.create! # 리포지토리를 생성하고 이름을 지정합니다.
  end
end

이는 초기의 작은 Rails 앱을 위해서는 해로울 것 같지 않지만, 콜백을 통해 이러한 유형의 논리를 추가하면 앱이 커지고 복잡해질수록(이 문서에 모두 나열되어 있는) 많은 단점이 있습니다. 대신, 우리는 이 로직을 서비스 클래스에 추가할 수 있습니다.

class Project
  has_one :repository
end

class Repository
  belongs_to :project
end

class ProjectCreator
  def self.execute
    ApplicationRecord.transaction do
      name = SecureRandom.alphanumeric
      project = Project.create!(name: name)
      Repository.create!(project: project, name: name)
    end
  end
end

class ProjectsController
  def create
    ProjectCreator.execute
  end
end

이 정도로 간단한 애플리케이션의 경우, 두 번째 접근 방식의 이점을 파악하기는 어려울 수 있습니다. 그럼에도 불구하고 이미 몇 가지 이점이 있습니다.

  • Repository 생성 로직과 Project 생성 로직을 별도로 테스트할 수 있습니다. 코드는 이제 데메테르의 법칙을 위반하지 않습니다(Repository 클래스는 project.name을 알 필요가 없습니다).
  • 호출 순서가 명확합니다.
  • 변경 가능: 프로젝트에 대해 리포지토리를 생성하고 싶지 않은 몇 가지 시나리오가 있다고 결정하면 ProjectRepository 클래스를 재구성할 필요 없이 새로운 서비스 클래스를 생성할 수 있습니다.
  • Project 팩토리의 각 인스턴스는 두 번째(Repository) 객체를 생성하지 않습니다.

우리가 의견을 갖지 않는 스타일

RuboCop 규칙이 제안되었지만 우리가 추가하지 않기로 결정한 경우에는 해당 결정을 문서화해야 합니다. 이 가이드에서 결정을 보다 쉽게 찾을 수 있도록 관련 토론을 참조 링크로 연결해야 합니다.

문자열 리터럴 인용

수정해야 할 작업이 매우 많기 때문에 문자열 리터럴이 작은따옴표인지 큰따옴표인지에 관심이 없습니다.

이전 토론은 다음과 같습니다.

타입 안전성

이제 Ruby 3로 업그레이드했기 때문에 타입 안전성을 강제하는 데 더 많은 옵션이 있습니다.

이러한 옵션 중 일부는 Ruby 구문의 일부로 지원되며 Sorbet 또는 RBS와 같은 특정 타입 안전성 도구의 사용이 필요하지 않습니다. 그러나 우리는 이러한 도구를 나중에도 고려할 수 있습니다.

자세한 정보는 remote_development 도메인 README의 타입 안전성을 참조하십시오.

기능적 패턴

Ruby 및 특히 Rails는 주로 객체 지향 프로그래밍 패턴을 기반으로 하지만, Ruby는 매우 유연한 언어이며 함수형 프로그래밍 패턴도 지원합니다.

특히 도메인 로직에서 함수형 프로그래밍 패턴은 종종 더 가독성이 좋고 유지보수가 쉬우며 버그에 강한 코드로 이어질 수 있습니다. 그러나 함수형 프로그래밍 패턴은 몇 가지 패턴이 혼란을 초래하고 직접 지원되는 경우라도 피해야 하므로 주의해서 사용해야 합니다. curry 메서드가 이에 해당할 수 있습니다.

자세한 정보는 다음을 참조하십시오.