루비 스타일 가이드
이것은 GitLab 전용 루비 코드 스타일 가이드입니다. 이 페이지에 문서화된 모든 내용은 논의 재개 가능합니다.
우리는 루비 스타일 가이드 규칙을 집행하기 위해 RuboCop를 사용합니다.
RuboCop 규칙이 없는 경우, 다음 스타일 가이드를 참조하여 관습적인 루비 작성을 위한 일반 가이드라인으로 삼으세요:
일반적으로 기존 RuboCop 규칙이나 위의 스타일 가이드에서 다루지 않는 스타일은 차단 요소가 되어서는 안 됩니다.
우리가 강한 의견을 제시하지 말아야 할 스타일에 대해서도 확인하세요.
또한,
- 추상화 재사용 가이드라인.
- 테스트 전용 스타일 가이드 및 모범 사례를 참조하세요.
규칙이 없는 스타일
이 스타일은 RuboCop 규칙에 근거하지 않습니다.
이 섹션에 추가된 각 스타일에 대해서는 섹션의 이력 노트에서 논의 링크를 제공하여 맥락을 제공하고 참고 자료로 사용하세요.
attr_reader
를 사용한 인스턴스 변수 접근
인스턴스 변수는 클래스 내에서 다양한 방법으로 접근할 수 있습니다:
# 공개
class Foo
attr_reader :my_var
def initialize(my_var)
@my_var = my_var
end
def do_stuff
puts my_var
end
end
# 비공개
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/EmptyLinesAroundMethodBody
및 Cop/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 코드베이스는 콜백에 크게 의존하며, 일단 추가되면 보이지 않는 의존성으로 인해 리팩토링하기 어렵습니다. 결과적으로, 이 가이드라인에서는 모든 기존 콜백을 제거할 것을 요구하지 않습니다.
콜백을 사용할 때
콜백은 특별한 경우에 사용될 수 있습니다. 콜백 추가가 의미가 있는 경우의 몇 가지 예는 다음과 같습니다:
-
의존성이 콜백을 사용하고 있으며 콜백 동작을 오버라이드하고 싶을 때입니다.
-
캐시 카운트를 증가시킬 때입니다.
-
현재 모델의 데이터와만 관련된 데이터 정규화입니다.
콜백에서 서비스로 이동하는 예
다음과 같은 기본 데이터 모델이 있는 프로젝트가 있습니다:
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
이렇게 간단한 애플리케이션에서는 두 번째 접근 방식의 이점을 보기 어려울 수 있습니다. 하지만 이미 몇 가지 이점이 있습니다:
-
Project
생성 논리와 별개로Repository
생성 논리를 테스트할 수 있습니다. 코드는 더 이상 데메터 법칙을 위반하지 않습니다 (Repository
클래스는project.name
을 알 필요가 없습니다). -
호출 순서의 명확성.
-
변경 용이성: 프로젝트에 대해 저장소를 생성하고 싶지 않은 시나리오가 있다면,
Project
와Repository
클래스를 리팩토링할 필요 없이 새로운 서비스 클래스를 만들 수 있습니다. -
각
Project
팩토리의 인스턴스는 두 번째 (Repository
) 객체를 생성하지 않습니다.
우리가 의견이 없는 스타일
RuboCop 규칙이 제안되고 추가하지 않기로 선택한 경우, 이 가이드에 해당 결정을 문서화하여 더 쉽게 발견할 수 있도록 하고 관련 논의를 참조로 링크해야 합니다.
문자열 리터럴 인용
수정해야 할 작업의 양이 방대하기 때문에, 문자열 리터럴이 싱글 또는 더블 인용부호로 되어 있는지에 대해 신경 쓰지 않습니다.
이전 논의에는 다음이 포함됩니다:
- https://gitlab.com/gitlab-org/gitlab-foss/-/issues/44234
- https://gitlab.com/gitlab-org/gitlab-foss/-/issues/36076
- https://gitlab.com/gitlab-org/gitlab/-/issues/198046
타입 안전성
이제 Ruby 3으로 업그레이드했으므로, 타입 안전성을 강제할 수 있는 더 많은 옵션이 있습니다.
이 옵션 중 일부는 Ruby 문법의 일부로 지원되며, Sorbet 또는 RBS와 같은 특정 타입 안전성 도구를 사용할 필요가 없습니다. 그러나 향후 이러한 도구도 고려할 수 있습니다.
자세한 내용은 remote_development
도메인 README의 타입 안전성을 참조하세요.
함수형 패턴
Ruby와 특히 Rails는 주로 객체 지향 프로그래밍 패턴을 기반으로 하긴 하지만, Ruby는 매우 유연한 언어이며 함수형 프로그래밍 패턴도 지원합니다.
함수형 프로그래밍 패턴은 특히 도메인 논리에서 종종 더 가독성이 좋고, 유지 관리가 용이하며, 버그에 저항성이 강한 코드를 생성할 수 있습니다. 그러나 여전히 관용적이고 익숙한 Ruby 패턴을 사용하게 됩니다.
하지만, 함수형 프로그래밍 패턴은 주의해서 사용해야 하며, 일부 패턴은 혼란을 초래할 수 있으므로 Ruby에서 직접 지원되더라도 피해야 합니다. curry
메서드는 그럴듯한 예입니다.
자세한 내용은 다음을 참조하세요: