루비 스타일 가이드

이것은 루비 코드에 대한 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

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

  private

  def do_stuff
    puts @my_var
  end
end

공개 속성은 클래스 외부에서 액세스되는 경우에만 사용해야 합니다. 속성이 내부에서만 액세스되는 경우 사용되는 전략에 대해 강한 의견이 없지만, 관련된 코드에서 일관성이 유지된다면 무관합니다.

새로운 라인 스타일 가이드

RuboCop의 Layout/EmptyLinesAroundMethodBodyCop/LineBreakAroundConditionalBlock 외에도 일부 새로운 라인 스타일을 강제하는데 사용되지 않는 다음 지침들이 있습니다.

규칙: 새로운 라인을 사용하여 관련된 논리를 그룹화하기

# 안 좋은 예
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 클래스에 캡쳐합니다. 이러한 강한 결합은 너무 많은 비즈니스 로직을 포함하고 있는 fat 모델을 장려합니다. 이 로직은 대신에 재사용 가능하며 조립 가능하고 테스트하기 쉬운 서비스 객체에 있을 수 있습니다.
  • 객체의 부적절한 상태 전이는 속성 유효성을 통해 더 나은 방식으로 적용할 수 있습니다.
  • 콜백을 많이 사용하면 팩토리 생성 속도에 영향을 미칩니다. 수백 개의 콜백을 가진 일부 클래스는 객체의 인스턴스를 자동화된 테스트를 위해 만들 때 매우 느린 작업이 될 수 있으며 느린 스펙을 고착합니다.

위의 예는 그러한 것들에 대한 몇 가지 사례를 논의한 것입니다.

GitLab 코드베이스는 콜백에 많은 의존성이 있으며, 보이지 않는 종속성 때문에 한 번 추가된 콜백을 재구조화하는 것이 어렵습니다. 그 결과, 이 안내는 모든 기존 콜백을 제거하도록 요구하지 않습니다.

콜백을 사용하는 경우

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

  • 종속성이 콜백을 사용하고 있으며 콜백 동작을 재정의하고자 하는 경우.
  • 캐시 카운트 증가.
  • 현재 모델의 데이터와 관련이 있는 데이터를 정규화합니다.

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

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

class Project
  has_one :repository
end

class Repository
  belongs_to :project
end

프로젝트가 생성된 후에 리포지토리를 생성하고 프로젝트 이름을 리포지토리 이름으로 사용하고자 합니다. 레일즈에 익숙한 개발자는 즉시 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

이것은 초기 단계 레일스 애플리케이션에서는 해가 없어 보일 수 있지만, 레일스 애플리케이션이 커지고 복잡해지기 시작하면(모든 것은 이 문서에 나열됨), 콜백을 통해 이러한 유형의 로직을 추가하는 것에는 많은 단점이 있을 것입니다. 대신, 이러한 로직을 서비스 클래스에 추가할 수 있습니다.

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) 객체를 만들지 않습니다.

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

루보캅 규칙이 제안되었지만 우리가 추가하지 않기로 한 경우, 해당 결정을 이 가이드에 문서화하여 더 찾기 쉽도록 하고 참고로 해당 토론을 링크합니다.

문자열 리터럴 인용

수정 작업량이 너무 많기 때문에 문자열 리터럴이 단일인용부호로 되어있는지 이중인용부호로 되어있는지에 대해 우리는 신경 쓰지 않습니다.

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

타입 안전성

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

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

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

함수형 패턴

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

함수형 프로그래밍 패턴은 특히 도메인 논리에서 가독성이 높고 유지보수가 쉽며 버그에 강한 코드로 이어질 수 있지만, 현지화한 Ruby 패턴을 사용하면서도 주의해서 사용하여야 합니다. 그러나 함수형 프로그래밍 패턴은 혼란을 야기하는 일부 패턴이 있어 피해야 하며, Ruby가 직접 지원하는 경우일지라도 피해야 할 패턴이 있습니다. curry 메서드가 그런 예시입니다.

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