루비 스타일 가이드

이것은 루비 코드를 위한 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 외에도 지원되지 않는 몇 가지 줄 바꿈 스타일 가이드가 있습니다.

규칙: 연관된 로직을 그룹화하기 위해 새 줄을 사용하세요

# 나쁨
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 객체에 새로운 라이프사이클 이벤트를 추가할 때는, 콜백 대신 서비스 클래스에 로직을 추가하는 것이 좋습니다.

콜백을 피해야 하는 이유

일반적으로, 콜백은 피해야 합니다. 왜냐하면:

  • 콜백은 인보케이션 순서가 명확하지 않고 코드 내러티브를 깨뜨립니다.
  • 콜백은 반사(reflection)를 이용하기 때문에 찾고 이동하기 어렵습니다.
  • 콜백은 객체의 전체 콜백 체인을 트리거하기 때문에 객체 상태에 변화를 선택적으로 적용하기 어렵게 만듭니다.
  • 콜백은 로직을 ActiveRecord 클래스에 갇히게 만듭니다. 이러한 타이트한 결합은 너무 많은 비즈니스 로직을 포함하는 뚱뚱한 모델을 유도합니다. 대신 더 재사용 가능하고 결합이 느슨하며 테스트하기 쉬운 서비스 객체에 존재할 수 있는 비즈니스 로직을 포함해야 합니다.
  • 객체의 부적절한 상태 전이는 속성 유효성으로 더 잘 강제할 수 있습니다.
  • 콜백의 과도한 사용은 공장 생성 속도에 영향을 줍니다. 수백 개의 콜백을 갖는 몇몇 클래스로 인해 해당 객체의 인스턴스를 자동화된 테스트를 위해 생성하는 것은 매우 느린 작업이 될 수 있으며, 느린 사양을 유발할 수 있습니다.

이러한 예시들 중 일부는 thoughtbot의 비디오에서 논의되었습니다.

GitLab 코드베이스는 콜백을 많이 사용하고 있으며, 보이지 않는 의존성으로 인해 추가된 후에 리팩토링하기 어려울 수 있습니다. 그 결과, 이 지침은 모든 기존 콜백을 제거하도록 요구하지 않습니다.

콜백을 사용해야 하는 경우

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

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

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

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

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 앱에는 이것이 상당히 피해 없어 보입니다. 그러나 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 생성 로직과 따로 테스트할 수 있습니다.
  • 인보케이션 순서가 명백합니다.
  • 변경 가능: 프로젝트에 리포지터리를 만들지 않고도 일부 시나리오가 있는 경우, 프로젝트와 리포지터리 클래스를 리팩토링해야 하는 대신 새로운 서비스 클래스를 만들 수 있습니다.
  • Project 팩토리의 각 인스턴스는 두 번째(Repository) 객체를 만들지 않습니다.

우리가 의견이 없는 스타일

RuboCop 규칙이 제안되고 추가하지 않기로 한 경우, 해당 결정을 문서화하여 더 찾기 쉽게 만들어야 하며 참조로써 관련 토론에 링크를 제공해야 합니다.

문자열 리터럴 따옴표 처리

무릎까지 작업량이 많아서 문자열 리터럴이 단일인지 또는 이중인지에는 관심이 없습니다.

이전 토론은 다음을 포함합니다:

타입 안전성

루비 3로 업그레이드되어 더 많은 옵션이 이용 가능해졌습니다. 타입 안전성을 강제하기 위한 여러 옵션이 있습니다.

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

자세한 정보는 remote_development 도메인 README의 Type safety를 참조하세요.

함수형 패턴

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

특히 도메인 로직에서 함수형 프로그래밍 패턴은 종종 읽기 쉽고 유지보수하기 좋으며 버그에 강한 코드를 작성할 수 있습니다. 이때도 루비의 관용적이고 익숙한 패턴을 사용합니다. 그러나 함수형 프로그래밍 패턴은 몇 가지 주의를 요구합니다. 특정 패턴들은 혼란을 일으키며, 직접적으로 지원되는 경우라도 피해야 합니다. curry 메서드가 그 예시입니다.

자세한 정보는 아래를 참조하세요: