왜 글로벌 주가 그렇게 악한가? 개념을 잘 알고

시작하기 전에 추상화 및 종속성 주입의 개념을 잘 알고 있다고 가정하겠습니다. 눈을 뜨고있을 필요는 없습니다.

글쎄, 대부분의 사람들은 “글로벌 변수를 사용하지 마십시오”또는 “싱글 톤은 글로벌이기 때문에 악한 것”이라는 것을 실제로 이해하지 않고 여러 번 말합니다. 하지만 실제로는 무엇 이며 불길한 전역 상태에 대해 그렇게 나쁜?

내 응용 프로그램, 예를 들어 시스템 폴더 경로 또는 응용 프로그램 전체 데이터베이스 자격 증명에 대한 전역 구성이 필요하다고 가정 해 봅시다.

이 경우 전체 응용 프로그램에서 일반적으로 사용할 수있는 일종의 전역 공간에서 이러한 설정을 제공하는 것 외에 다른 좋은 해결책은 없습니다.

나는 나쁜 알고 학대 를하지만, 글로벌 공간은 정말 THAT 악? 그리고 그렇다면 어떤 좋은 대안이 있습니까?



답변

간단히 말해 프로그램 상태를 예측할 수 없게 만듭니다.

좀 더 정교하게 말하면, 동일한 전역 변수를 사용하는 두 개의 객체가 있다고 상상해보십시오. 어느 모듈 내에서 임의의 소스를 사용하지 않는다고 가정하면, 메소드를 실행하기 전에 시스템의 상태를 알면 특정 메소드의 출력을 예측 (및 테스트) 할 수 있습니다.

그러나 개체 중 하나의 메서드가 공유 전역 상태의 값을 변경하는 부작용을 유발하면 다른 개체에서 메서드를 실행할 때 시작 상태가 무엇인지 더 이상 알 수 없습니다. 더 이상 메소드를 실행할 때 얻을 수있는 결과를 예측할 수 없으므로 테스트 할 수 없습니다.

학업 수준에서 이것은 심각하게 들리지 않을 수도 있지만 테스트 코드를 단위로 구성 할 수있는 것이 정확성 (또는 최소한 목적에 적합 함)을 증명하는 과정에서 중요한 단계입니다.

현실에서 이것은 매우 심각한 결과를 초래할 수 있습니다. 글로벌 데이터 구조를 채우는 하나의 클래스와 해당 데이터 구조의 데이터를 소비하여 상태를 변경하거나 프로세스에서 데이터를 파괴하는 다른 클래스가 있다고 가정하십시오. populator 클래스가 완료되기 전에 프로세서 클래스가 메소드를 실행하면 프로세서 클래스가 처리 할 데이터가 불완전 할 수 있으며 populator 클래스가 작업중인 데이터 구조가 손상되거나 손상 될 수 있습니다. 이러한 상황에서 프로그램 동작은 완전히 예측할 수 없으며 서사시 손실로 이어질 수 있습니다.

또한 전역 상태는 코드의 가독성을 손상시킵니다. 코드에 코드에 명시 적으로 도입되지 않은 외부 종속성이있는 경우 코드 유지 관리 작업을 수행하는 사람은 코드의 출처를 찾아야합니다.

어떤 대안이 존재하는지에 관해서는 글로벌 상태를 전혀 가질 수 없지만 실제로는 일반적으로 전역 상태를 다른 모든 것을 감싸는 단일 객체로 제한 할 수 있으며 범위 지정 규칙에 따라 절대로 참조해서는 안됩니다 사용중인 언어의 특정 객체에 특정 상태가 필요한 경우 생성자 또는 setter 메서드에 인수로 전달하여 명시 적으로 요청해야합니다. 이것을 의존성 주입이라고합니다.

사용중인 언어의 범위 지정 규칙으로 인해 이미 액세스 할 수있는 상태로 어리석은 것처럼 보일 수 있지만 이점은 엄청납니다. 이제 누군가가 코드를 따로 살펴보면 필요한 상태와 코드의 출처를 알 수 있습니다. 또한 코드 모듈의 유연성과 다른 컨텍스트에서 재사용 할 수있는 기회와 관련하여 큰 이점이 있습니다. 상태가 전달되고 상태 변경이 코드 블록에 로컬 인 경우 원하는 상태 (올바른 데이터 유형 인 경우)를 전달하고 코드를 처리하도록 할 수 있습니다. 이 스타일로 작성된 코드는 쉽게 상호 교환 할 수있는 느슨하게 연결된 구성 요소 모음이 나타나는 경향이 있습니다. 모듈의 코드는 상태가 어디에서 왔는지, 처리 방법 만 신경 쓰지 않아야합니다.

국가를 통과하는 것이 세계 국가에 의존하는 것보다 훨씬 우수한 다른 많은 이유가 있습니다. 이 답변은 결코 포괄적이지 않습니다. 왜 세계 상태가 나쁜지에 대한 책을 쓸 수도 있습니다.


답변

변하기 쉬운 세계 상태는 여러 가지 이유로 악하다 :

  • 변경 가능한 전역 상태의 버그 -많은 까다로운 버그는 변경 가능성으로 인해 발생합니다. 프로그램의 어느 곳에서나 돌연변이로 인해 발생할 수있는 버그는 더 까다로울 수 있습니다. 정확한 원인을 찾기가 어렵습니다.
  • 불량한 테스트 가능성 -변경 가능한 글로벌 상태 인 경우 작성하는 모든 테스트에 대해 상태를 구성해야합니다. 이로 인해 테스트가 더 어려워집니다 (따라서 사람들이 사람들이 할 가능성이 적습니다!). 예를 들어 응용 프로그램 전체 데이터베이스 자격 증명의 경우 한 테스트에서 다른 테스트 데이터베이스와 다른 특정 테스트 데이터베이스에 액세스해야하는 경우에는 어떻게됩니까?
  • 비 유연성 -코드의 한 부분에 전역 상태에서 하나의 값이 필요하지만 다른 부분에 다른 값이 필요한 경우 (예 : 트랜잭션 중 임시 값)? 갑자기 손에 리팩토링이 심하게 있습니다
  • 함수 불순물 – “순수한”함수 (즉, 결과가 입력 매개 변수에만 의존하고 부작용이없는 함수)는 더 큰 프로그램을 추론하고 구성하기가 훨씬 쉽습니다. 변경 가능한 전역 상태를 읽거나 조작하는 기능은 본질적으로 불완전합니다.
  • 코드 이해 -많은 가변 글로벌 변수에 의존하는 코드 동작은 이해하기가 훨씬 어렵습니다. 코드 동작에 대해 추론하기 전에 글로벌 변수와의 가능한 상호 작용 범위를 이해해야합니다. 경우에 따라이 문제는 다루기 어려워 질 수 있습니다.
  • 동시성 문제 -변경 가능한 전역 상태는 일반적으로 동시 상황에서 사용될 때 어떤 형태의 잠금이 필요합니다. 이것은 올바르게 작성하기가 어렵고 (버그의 원인이 됨) 코드에 훨씬 더 복잡한 작업을 추가합니다 (유지하기가 어렵고 비용이 많이 듭니다).
  • 성능 -동일한 전역 상태에서 여러 스레드가 지속적으로 bash를 수행하면 캐시 경합이 발생하여 시스템 전체의 속도가 느려집니다.

변경 가능한 전역 상태의 대안 :

  • 함수 매개 변수 -간과 되기는하지만 전역 상태를 피하는 가장 좋은 방법은 함수를 더 잘 매개 변수화하는 것입니다. 중요한 개념적 질문을 해결하도록 강요합니다.이 기능이 작동하려면 어떤 정보가 필요합니까? “컨텍스트”라는 데이터 구조를 가지고 모든 관련 정보를 마무리하는 일련의 함수로 전달 될 수 있습니다.
  • 의존성 주입 -함수 매개 변수와 동일하며 조금 더 일찍 수행되었습니다 (함수 호출이 아닌 객체 구성에서). 의존성이 변경 가능한 객체 인 경우 조심하십시오. 이로 인해 변경 가능한 전역 상태와 동일한 문제가 빠르게 발생할 수 있습니다 …..
  • 불변의 글로벌 상태 는 대부분 무해합니다. 사실상 일정합니다. 그러나 이것이 실제로 일정하고 나중에 변경 가능한 전역 상태로 전환하려는 유혹을받지 않도록하십시오!
  • 불변의 싱글 톤 -불변의 전역 상태와 거의 동일하지만 필요할 때까지 인스턴스화를 연기 할 수 있다는 점이 다릅니다. 고가의 일회성 사전 계산이 필요한 대규모 고정 데이터 구조에 유용합니다. 가변 싱글 톤은 물론 가변 글로벌 상태와 동등하므로 악의적이다 🙂
  • 동적 바인딩 -Common Lisp / Clojure와 같은 일부 언어에서만 사용 가능하지만 다른 스레드에는 영향을 미치지 않는 제어 된 범위 (일반적으로 스레드 로컬 기준) 내에서 값을 효과적으로 바인딩 할 수 있습니다. 현재 실행 스레드 만 영향을 받는다는 것을 알기 때문에 이것은 전역 변수와 동일한 효과를 얻는 “안전한”방법입니다. 예를 들어 독립적 인 트랜잭션을 처리하는 스레드가 여러 개인 경우에 특히 유용합니다.

답변

  1. 빌어 먹을 앱 전체가 그것을 사용할 수 있기 때문에, 다시 다시 계산하는 것은 매우 어렵습니다. 글로벌과 관련하여 변경 사항이 있으면 모든 코드를 변경해야합니다. 이것은 단순히 grep유형 이름으로 어떤 기능을 사용하는지 알아내는 것보다 훨씬 어려운 문제입니다.
  2. 숨겨진 종속성을 도입하여 멀티 스레딩을 중단하여 점점 더 많은 응용 프로그램에 점점 더 중요해지기 때문에 나쁩니다.
  3. 모든 변수가 무언가를 할 수 있기 때문에 전역 변수의 상태는 항상 완전히 신뢰할 수 없습니다.
  4. 그들은 실제로 테스트하기가 어렵습니다.
  5. 그들은 API 호출을 어렵게 만듭니다. 그냥 “당신은 API를 호출하기 전에 () SET_MAGIC_VARIABLE 전화를 기억해야한다” 구걸 호출하는 것을 잊지 사람. 오류가 발생하기 쉬운 API를 사용하여 찾기 어려운 버그가 발생합니다. 일반 매개 변수로 사용하면 호출자가 강제로 값을 제공하게됩니다.

필요한 함수에 참조를 전달하십시오. 그렇게 어렵지 않습니다.


답변

“state”라고 말하면 일반적으로 “mutable state”를 의미합니다. 전역 변경 가능 상태는 프로그램의 어떤 부분이 (전역 상태를 변경하여) 다른 부분에 영향을 줄 수 있기 때문에 완전히 악 합니다.

알 수없는 프로그램 디버깅을 상상해보십시오. 함수 A가 특정 입력 매개 변수에 대해 특정 방식으로 작동하지만 동일한 매개 변수에 대해 다르게 작동하는 경우가 있습니다. 전역 변수 x를 사용한다는 것을 알았습니다 .

x 를 수정하는 장소를 찾아서 수정하는 장소가 5 개 있다는 것을 알게됩니다. 이제 어떤 경우에 함수 A가 어떤 역할을하는지 알아내는 행운을 빕니다.


답변

당신은 자신의 질문에 대답했습니다. ‘거품이있는’경우 관리하기는 어렵지만 제대로 사용하면 유용하고 예측할 수 있습니다. 전역의 유지 관리 및 변경은 일반적으로 악몽이며 응용 프로그램의 크기가 커질수록 악화됩니다.

글로벌 옵션이 유일한 옵션이고 차이점을 쉽게 해결할 있는 숙련 된 프로그래머 이를 사용하는 데 최소한의 문제가있을 있습니다. 그러나 사용하면서 발생할 수있는 끝없는 문제는 사용에 대한 조언이 필요합니다.

편집 : 내가 의미하는 바를 명확히하기 위해 글로벌은 본질적으로 예측할 수 없습니다. 예측할 수없는 작업과 마찬가지로 예측할 수없는 조치를 취할 수 있지만 수행 할 수있는 작업에는 항상 제한이 있습니다. 이것에 상대적으로 알려지지 않은 변수를 다루어야하는 프로젝트에 참여하는 새로운 개발자들의 번거 로움에, 글로벌 사용에 대한 권장 사항을 이해할 수 있어야합니다.


답변

싱글 톤에는 많은 문제가 있습니다-여기 내 마음에 가장 큰 두 가지 문제가 있습니다.

  • 단위 테스트에 문제가 있습니다. 지구 상태는 한 테스트에서 다음 테스트로 오염 될 수 있습니다

  • “일대일”하드 규칙을 적용합니다.이 규칙은 변경 될 수는 없지만 갑자기 변경됩니다. 전역 적으로 액세스 가능한 객체를 사용한 전체 유틸리티 코드를 변경해야합니다.

그러나 대부분의 시스템에는 Big Global Objects가 필요합니다. 이는 크고 비싸거나 (예 : 데이터베이스 연결 관리자) 광범위한 상태 정보 (예 : 잠금 정보)를 보유한 항목입니다.

Singleton의 대안은 시작시 이러한 Big Global Object를 작성하고이 오브젝트에 액세스해야하는 모든 클래스 또는 메소드에 매개 변수로 전달하는 것입니다.

여기서 문제는 “패스 소포”의 큰 게임으로 끝납니다. 컴포넌트와 그 의존성에 대한 그래프가 있으며, 일부 클래스는 다른 클래스를 생성하며, 각각의 스폰 된 컴포넌트 (또는 스폰 된 컴포넌트의 컴포넌트)가 필요하기 때문에 각 클래스는 많은 의존성 컴포넌트를 보유해야합니다.

새로운 유지 관리 문제가 발생합니다. 예 : 갑자기 “WidgetFactory”, 그래프의 깊이에있는 구성 요소에는 조롱하려는 타이머 객체가 필요합니다. 그러나 “WidgetFactory”는 “WidgetCreationManager”의 일부인 “WidgetBuilder”에 의해 작성되며 실제로 하나만 사용하더라도이 타이머 오브젝트에 대해 알고있는 세 개의 클래스가 필요합니다. Singletons를 포기하고 되돌리려는 경우,이 타이머 객체를 전역 적으로 액세스 할 수있게 만드십시오.

다행히도 이것은 Dependency Injection 프레임 워크로 해결되는 문제입니다. 프레임 워크에 어떤 클래스를 생성해야하는지 알려주고 리플렉션을 사용하여 종속성 그래프를 파악하고 필요할 때 각 객체를 자동으로 구성합니다.

요약하자면, 싱글 톤은 좋지 않으며, 대안은 Dependency Injection 프레임 워크를 사용하는 것입니다.

Castle Windsor를 사용하지만 선택의 여지가 있습니다. 사용 가능한 프레임 워크 목록은 2008 년부터이 페이지 를 참조하십시오 .


답변

우선 의존성 주입이 “상태 저장”이 되려면 싱글 톤을 사용해야하므로 사람들은 이것이 대체로 잘못되었다고 말하는 사람들입니다. 사람들은 항상 전역 컨텍스트 객체를 사용합니다. 예를 들어 세션 상태도 본질적으로 전역 변수입니다. 의존성 주입 여부에 관계없이 모든 것을 전달하는 것이 항상 최상의 솔루션은 아닙니다. 나는 현재 많은 글로벌 컨텍스트 객체 (IoC 컨테이너를 통해 주입 된 싱글 톤)를 사용하는 매우 큰 응용 프로그램을 작업 중이며 디버그하는 데 결코 문제가되지 않았습니다. 특히 이벤트 중심 아키텍처의 경우 전역 컨텍스트 개체를 사용하고 변경된 내용을 전달하는 것이 좋습니다. 당신이 묻는 사람에 따라 다릅니다.

무엇이든 남용 될 수 있으며 응용 프로그램 유형에 따라 다릅니다. 예를 들어 웹 앱에서 정적 변수를 사용하는 것은 데스크톱 앱과 완전히 다릅니다. 전역 변수를 피할 수 있다면 그렇게하지만 때로는 그 용도가 있습니다. 최소한 글로벌 데이터가 명확한 맥락에 있는지 확인하십시오. 디버깅까지는 호출 스택과 일부 중단 점으로 해결할 수 없습니다.

나는 맹목적으로 전역 변수를 사용하는 것이 좋지 않다는 것을 강조하고 싶습니다. 함수는 재사용이 가능해야하고 데이터의 출처를 신경 쓰지 않아야합니다. 전역 변수를 참조하면 함수가 특정 데이터 입력과 연결됩니다. 이것이 단일 컨텍스트를 통해 중앙 집중식 컨텍스트 저장소를 처리하고 있지만 이것이 전달되어야하는 이유와 종속성 주입이 도움이되는 이유입니다.

Btw … 어떤 사람들은 Linq의 제작자를 포함하여 의존성 주입이 나쁘다고 생각하지만, 나 자신을 포함하여 사람들이 그것을 사용하지 못하게하지는 않습니다. 궁극적으로 경험은 최고의 선생님이 될 것입니다. 규칙을 따르는 시간과 규칙을 어기는 시간이 있습니다.