Ruby 스타일 가이드
이는 루비 코드에 대한 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
프로젝트를 만든 후 리포지터리를 생성하고 프로젝트 이름을 리포지터리 이름으로 사용하고자 합니다. 레일스에 익숙한 개발자는 즉시 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
생성 로직과 분리하여 테스트할 수 있습니다. - 호출 순서의 명확성.
- 변경 가능성: 프로젝트에 대한 리포지터리를 생성하고 싶지 않은 시나리오가 있는 경우에는
Project
와Repository
클래스를 다시 설계할 필요 없이 새로운 서비스 클래스를 만들 수 있습니다. - 각
Project
팩토리의 인스턴스는 두 번째(Repository
) 객체를 만들지 않습니다.
이렇게 간단한 애플리케이션에는 두 번째 접근 방식의 혜택이 분명하지 않을 수 있습니다. 그러나 여러분은 이미 다음의 혜택을 얻을 것입니다:
-
Repository
생성 로직을Project
생성 로직과 분리하여 테스트할 수 있습니다. - 호출 순서의 명확성.
- 변경 가능성: 프로젝트에 대한 리포지터리를 생성하고 싶지 않은 시나리오가 있는 경우에는
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에서 Type safety를 참조하세요.
함수형 패턴
루비와 특히 Rails는 주로 객체지향 프로그래밍 패턴에 기반을 두고 있지만, 루비는 매우 유연한 언어이며 함수형 프로그래밍 패턴도 지원합니다.
특히 도메인 로직에서 함수형 프로그래밍 패턴은 종종 더 읽기 쉽고 유지 보수하기 쉬우며 버그에 강한 코드로 이어질 수 있습니다. 그러나 함수형 프로그래밍 패턴은 주의해서 사용해야 합니다. 왜냐하면 몇 가지 패턴은 혼란을 야기하고 직접 지원되는 경우에도 피해야 할 수 있기 때문입니다. curry
메서드가 이에 대한 좋은 예입니다.
더 많은 정보를 원하시면 다음을 참조하세요: