인스턴스 변수를 포함하는 모듈은 해로울 수 있다.

배경

Rails는 어떤 이유에서인지 사람들이 컨트롤러, 헬퍼 및 뷰에서 모듈과 인스턴스 변수를 사용하도록 장려합니다. 또한 ActiveSupport::Concern의 사용을 장려함으로써 모든 것을 하나의 거대한 객체에 저장하고 모든 사람들이 그 하나의 거대한 객체에서 모든 것에 액세스하도록 하는 생각을 더 견고하게 만듭니다.

문제점

물론 개발하는 데 편리합니다. 왜냐하면 우리는 모든 것을 즉시 사용할 수 있기 때문입니다. 그러나 선택한 객체가 커질수록 동일한 이유로 나중에 그것이 제어하기 어려워질 수 있습니다.

동일한 맥락에서 너무 많은 것들이 있고, 그것들이 서로 의존하는지 여부에 따라 그것이 매우 어려워집니다. 복잡성이 특정 지점에 도달하면 매우 어렵고 코드를 추적하기도 매우 어려워집니다. 예를 들어 클래스가 3가지 다른 인스턴스 변수를 사용할 수 있으며, 그 모든 변수가 3가지 다른 모듈에서 초기화되고 조작될 수 있습니다. 그 변수들이 문제를 일으킬 때 어려워집니다. 어떤 모듈이 갑자기 변수 중 하나를 변경할 수도 있습니다. 모든 것이 모든 것에 영향을 줄 수 있습니다.

유사한 우려사항

사람들은 다중 상속이 나쁘다고 말합니다. 여러 모듈을 여기저기 흩뿌려 놓은 다중 인스턴스 변수도 동일한 문제를 겪습니다. ActiveSupport::Concern에도 동일한 원칙이 적용됩니다. 참고: 전용 클래스 및 조합으로 관심 분리 패턴 대체 고려

유사한 아이디어도 있습니다: 과도한 모델 증가 문제 해결을 위한 데코레이터 및 인터페이스 분리 사용

included는 문제의 전반적인 해결책이 되지 못합니다. 그것들은 의존성을 정의하지만, 최종 거대한 객체의 인스턴스 변수를 통해 각 모듈이 암시적으로 대화할 수 있도록 허용합니다. 그것이 문제의 원인입니다.

해결책

우리는 거대한 객체를 여러 객체로 분할하고, 그들이 각자 API 즉, 공용 메소드를 사용하여 서로 통신하도록 해야 합니다. 다시 말해, 상속보다는 조합을 사용해야 합니다. 이 방식으로 각 작은 객체는 자체 제한된 상태, 즉 인스턴스 변수를 가지게 됩니다. 하나의 인스턴스 변수가 잘못될 때, 그것이 그 작은 객체에서 비례함을 명확히 알 수 있습니다. 다른 누구도 그것을 건드릴 수 없기 때문입니다.

명확히 정의된 API로 인해 것들이 덜 결합되고 추적하기 쉬우며, 다른 객체들이 사용하기에 훨씬 확장 가능해집니다. 왜냐하면 암시적인 의존성 대신 명확한 방법으로 통신하기 때문입니다.

허용 가능한 사용

그러나 동일한 모듈에 있는 인스턴스 변수를 사용하는 것이 항상 나쁜 것은 아닙니다. 즉, 다른 모듈이나 객체가 그것들을 건드리지 않는 한 허용됩니다.

특히 ||=을 사용하여 값 설정을 하는 경우에 이를 허용합니다:

module M
  def f
    @f ||= true
  end
end

불행히도 보다 복잡한 규칙을 코프에 쉽게 코딩하는 것은 어렵기 때문에, 우리는 사람들의 최선의 판단에 의존해야 합니다. 좋은 패턴을 찾아 다른 것을 추가할 수 있다면 그렇게 해야 합니다.

이 규칙을 해제하여 다시 적용하는 방법

코프를 비활성화할 수 있다 하더라도 그렇게하지 않는 것이 좋습니다. 어떤 코드는 간단한 형태로 다시 작성될 수 있습니다. 이는 허용 가능한 메소드로 고려되어야 합니다:

module Gitlab
  module Emoji
    def emoji_unicode_version(name)
      @emoji_unicode_versions_by_name ||=
        JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json')))
      @emoji_unicode_versions_by_name[name]
    end
  end
end

이 방법은 이미 스스로 포함되어 있기 때문에 완전히 좋습니다. 다른 메소드도 @emoji_unicode_versions_by_name을 사용해서는 안 되고, 그러면 좋습니다. 그러나 여전히 이 메소드는 코프에 어긋나기 때문에 권장하지 않습니다.

반면, 이 메소드를 두 가지로 분할할 수 있습니다:

module Gitlab
  module Emoji
    def emoji_unicode_version(name)
      emoji_unicode_versions_by_name[name]
    end

    private

    def emoji_unicode_versions_by_name
      @emoji_unicode_versions_by_name ||=
        JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json'))
    end
  end
end

그러면 코프는 불만을 가지지 않습니다.

이 규칙을 해제하는 방법

코드 바로 뒤에 해제 주석을 넣어주십시오:

module M
  def violating_method
    @f + @g # rubocop:disable Gitlab/ModuleWithInstanceVariables
  end
end

여러 줄이 있는 경우 섹션에 대해 활성화 및 비활성화할 수도 있습니다:

module M
  # rubocop:disable Gitlab/ModuleWithInstanceVariables
  def violating_method
    @f = 0
    @g = 1
    @h = 2
  end
  # rubocop:enable Gitlab/ModuleWithInstanceVariables
end

해제하지 않으면 그 이후에 아무 것도 검사되지 않음에 유념하십시오.

현재 무시해야 할 것들

Rails 헬퍼 및 메일러의 작동 방식 때문에 instance variable을 사용을 피할 수 없을 수 있습니다. 이러한 경우에 해당 모듈들을 현재 무시할 수 있습니다. 이러한 모듈들은 다른 무작위 객체와 공유되지 않으므로 어느 정도 고립되어 있습니다.

뷰에 있는 인스턴스 변수

인스턴스 변수는 사용하기 어렵기 때문에 나쁘다. (컨트롤러의 관점에서) 누가 인스턴스 변수를 사용하고 (부분의 관점에서) 어디에서 그들을 설정하는지 쉽게 알 수 없기 때문입니다. 데이터 의존성을 추적하는 것이 매우 어려워집니다.

우리는 대신 다음과 같은 방법을 사용하려고 노력하고 있습니다:

= render 'projects/commits/commit', commit: commit, ref: ref, project: project

그리고 부분 내에서:

- ref = local_assigns.fetch(:ref)
- commit = local_assigns.fetch(:commit)
- project = local_assigns.fetch(:project)

이 방법으로 값이 어디에서 오는지 더 명확해지고, 인스턴스 변수를 사용하는 것보다 오타 검사의 혜택을 얻을 수 있습니다. 미래에는 partials에서의 인스턴스 변수 사용을 금지해야 합니다.