RuboCop 규칙 개발 가이드라인

우리의 코드베이스 스타일은 RuboCop에 의해 정의되고 강제됩니다.

bundle exec rubocop --parallel로 로컬에서 어떤 offenses를 확인할 수 있습니다. CI에서는 이 작업이 자동으로 static-analysis 작업에 의해 확인됩니다.

또한, Solargraph gem을 사용하여 지원되는 IDE에 RuboCop을 통합할 수 있습니다. RuboCop을 통합하는 방법을 알아보세요.

우리가 결정을 내리지 않은 RuboCop 규칙에 대해서는 Ruby style guide를 따라 idiomatic한 Ruby를 작성하세요.

리뷰어/유지보수자는 스타일에 대해 너무 엄격하게 요구하지 말아야 합니다.

일부 RuboCop 규칙은 비활성화되어 있고, 이에 대해 리뷰어/유지보수자는 작성자에게 한 스타일을 사용하도록 요청해서는 안 됩니다. 양쪽 모두 허용된 것이기 때문에 이는 이상적인 상황이 아닙니다. 왜냐하면 이는 bike-shedding을 위한 공간을 남겨둡니다. 이상적으로는 스타일과 관련된 토론, 지적, 또는 리뷰에서의 번복을 피하기 위해 모든 RuboCop 규칙을 활성화해야 합니다. GitLab Ruby style guide에는 리뷰에서 자주 나타나는 스타일의 비고전적인 디렉터리이 포함되어 있습니다.

게다가, 우리는 특정한 테스트용 스타일 가이드와 모범 관행를 가지고 있습니다.

규칙 인라인 비활성화

일반적으로 RuboCop 규칙은 코드베이스에 적용하려는 규칙에 반하는 합의된 코드 표준을 무효화시키므로 인라인으로 비활성화되어서는 안 됩니다.

만약 인라인 비활성화를 사용해야 한다면, 비활성화된 규칙이 있는 행에 이유를 코드 주석으로 제공하세요.

더 많은 맥락 정보는 이 인라인 비활성화 주석 위에 있는 코드 주석으로 들어갈 수 있습니다. 자세한 맥락을 제공하기 위해 리소스(이슈, 에픽, …)에 링크를 제공하여 상세한 맥락을 제공하세요.

임시 인라인 비활성화의 경우에는 rubocop:todo를 사용하고, 후속 이슈를 링크하세요.

예를 들면:

# bad
module Types
  module Domain
    # rubocop:disable Graphql/AuthorizeTypes
    class SomeType < BaseObject
      if condition # rubocop:disable Style/GuardClause
        # more logic...
      end
      
      object.public_send(action) # rubocop:disable GitlabSecurity/PublicSend
    end
    # rubocop:enable Graphql/AuthorizeTypes
  end
end

# good
module Types
  module Domain
    # rubocop:disable Graphql/AuthorizeTypes -- 부모 엔터티에서 이미 승인됨
    class SomeType < BaseObject
      if condition # rubocop:todo Style/GuardClause -- 정리: https://gitlab.com/gitlab-org/gitlab/-/issues/1234567890
        # more logic...
      end
      
      # 이 시점에서 `action`은 `public_send`에서 사용해도 안전합니다.
      # 자세한 내용은 https://gitlab.com/gitlab-org/gitlab/-/issues/123457890를 참조하세요.
      object.public_send(action) # rubocop:disable GitlabSecurity/PublicSend -- 사용자 입력 확인됨
    end
    # rubocop:enable Graphql/AuthorizeTypes
  end
end

새로운 RuboCop cops 만들기

일반적으로 린팅 규칙은 프로그래밍적으로 강제하는 것이 더 좋습니다. 이로써 bike-shedding을 줄일 수 있습니다.

이를 위해, 코드베이스에 새로운 RuboCop 규칙을 만드는 것을 장려합니다.

특정 스타일을 강제하기 위해 새로운 cop을 추가하기 전에 팀과 논의하는 것이 중요합니다.

우리는 여러 개의 루비 코드베이스에서 cop을 유지하고 있고, 그 중 일부는 GitLab 애플리케이션에 특정된 것은 아닙니다. 여러 애플리케이션에 적용될 수 있는 새로운 cop을 만들 때는 이를 gitlab-styles gem에 추가하는 것을 장려합니다. 만약 해당 cop이 主 GitLab 애플리케이션에만 적용되는 규칙을 대상으로 하는 경우, GitLab에 추가해야 합니다.

Cop 유예 기간

cop이 활성화되어 있고 TODO YAML 구성에서 Details: grace period가 정의되어 있는 경우, 해당 cop은 _유예 기간_에 있습니다.

기본 브랜치에서는 grace period에 있는 cop으로부터의 offenses가 RuboCop CI 작업을 실패시키지 않습니다. 대신, 작업은 #f_rubocop 슬랙 채널에 알립니다. 그러나 다른 브랜치에서는 RuboCop 작업이 실패합니다.

유예 기간은 #f_rubocop 채널에서 1주일간 경고가 없을 경우 안전하게 해제될 수 있습니다.

새로운 cop 활성화

  1. .rubocop.yml에서 새로운 cop을 활성화합니다(gitlab-styles를 통해서 이미 활성화되어 있지 않은 경우).
  2. 새로운 cop을 위한 TODO 생성.
  3. 새로운 cop을 grace period로 설정합니다(#cop-grace-period).
  4. TODO를 수정하고 커뮤니티 기여를 장려하는 이슈를 생성하세요 (~"quick win", 또는 ~"Seeking community contributions"). 일부 예시.
  5. #f_rubocop 슬랙 채널에서 1주일간 조용한 경우, grace period를 제거하기 위한 이슈를 생성하세요. 예시 보기.

음소거된 offenses

offenses가 유예 기간에 있는 cop의 경우, #f_rubocop 슬랙 채널은 2시간마다 알림 메시지를 수신합니다.

이 문제를 해결하려면:

  1. 연결된 CI 작업에서 음소거된 offenses를 가진 cop을 찾습니다.
  2. 이러한 cop에 대한 TODO 생성.

RuboCop 노드 패턴

Ruby의 AST와 일치시키기 위해 node patterns을 만들 때, scripts/rubocop-parse를 사용할 수 있습니다. 이것은 일치시키기 위해 Ruby 표현식의 AST를 표시합니다. 또한 !97024도 참조하세요.

RuboCop 예외 해결

RuboCop 예외의 수가 기본 exclude-limit 15을 초과하는 경우, 예외를 여러 커밋에 걸쳐 해결하는 것을 고려할 수 있습니다. 혼란을 최소화하기 위해 우리는 예외 디렉터리을 통해 진행 상황을 추적해야 합니다.

특정 RuboCop 규칙을 위한 초기 디렉터리 또는 초기 디렉터리을 생성하는 것은 rubocop:todo:generate Rake 작업을 실행하는 것이 선호됩니다:

# 초기 디렉터리
bundle exec rake rubocop:todo:generate

# 특정 RuboCop 규칙을 위한 디렉터리
bundle exec rake 'rubocop:todo:generate[Gitlab/NamespacedClass,Lint/Syntax]'

이 Rake 작업은 .rubocop_todo/에 있는 예외 디렉터리을 생성하거나 업데이트합니다. 예를 들어, RuboCop 규칙 Gitlab/NamespacedClass의 설정은 .rubocop_todo/gitlab/namespaced_class.yml에 위치합니다.

반드시 rubocop:todo를 실행한 후 .rubocop_todo/의 모든 변경 사항을 커밋하세요.

기존 RuboCop 예외 표시

코드에서 .rubocop_todo.yml.rubocop_todo/**/*.yml을 통해 제외된 기존 RuboCop 예외를 표시하려면 환경 변수 REVEAL_RUBOCOP_TODO1로 설정하세요.

이를 통해 일상적인 작업 주기 중 기존 RuboCop 예외를 확인하고 그 과정에서 수정할 수 있습니다.

note
.rubocop_todo/**/*.yml 대신에 .rubocop.yml에 영구적인 Exclude를 정의하세요.