단일 구성 개체가 나쁜 생각입니까?


43

대부분의 응용 프로그램에는 디스크에서 다양한 설정을 읽는 단일 톤 또는 정적 "구성"개체가 있습니다. 거의 모든 클래스가 다양한 목적으로 사용합니다. 기본적으로 이름 / 값 쌍의 해시 테이블 일뿐입니다. 읽기 전용이므로 전 세계 국가가 너무 많다는 사실에 너무 걱정하지 않았습니다. 그러나 이제 단위 테스트를 시작하면서 문제가되기 시작했습니다.

한 가지 문제점은 일반적으로 실행하는 동일한 구성으로 테스트하고 싶지 않다는 것입니다. 이에 대한 몇 가지 해결책이 있습니다.

  • 테스트에만 사용되는 설정 개체에 구성 개체를 지정하여 다른 설정을 전달할 수 있습니다.
  • 단일 구성 개체를 계속 사용하되 단일 개체에서 필요한 모든 곳을 통과하는 인스턴스로 변경하십시오. 그런 다음 응용 프로그램에서 한 번, 테스트에서 다른 설정으로 한 번 구성 할 수 있습니다.

그러나 어느 쪽이든, 여전히 두 번째 문제가 남아 있습니다. 거의 모든 클래스가 구성 객체를 사용할 수 있습니다. 따라서 테스트에서 테스트 할 클래스의 구성뿐만 아니라 모든 종속 항목도 설정해야합니다. 이것은 테스트 코드를 추악하게 만들 수 있습니다.

이런 종류의 구성 객체가 나쁜 생각이라는 결론에 도달하기 시작했습니다. 어떻게 생각해? 어떤 대안이 있습니까? 어디서나 구성을 사용하는 응용 프로그램을 리팩토링하려면 어떻게해야합니까?


설정은 디스크에서 파일을 읽음으로써 설정을 얻습니다. 그렇다면 왜 읽을 수있는 "test.config"파일이 없는가?
아논.

첫 번째 문제는 해결되지만 두 번째 문제는 해결되지 않습니다.
JW01

@ JW01 : 모든 테스트에서 현저하게 다른 구성이 필요합니까? 테스트하는 동안 어딘가에 해당 구성을 설정 해야합니까?
아논.

참된. 그러나 테스트를 위해 다른 풀과 함께 단일 설정 풀을 계속 사용하면 모든 테스트에서 동일한 설정을 사용하게됩니다. 그리고 다른 설정으로 동작을 테스트하고 싶을 수도 있으므로 이것은 이상적이지 않습니다.
JW01

"따라서 테스트에서는 테스트 할 클래스에 대한 구성뿐만 아니라 모든 종속 항목도 설정해야합니다. 이로 인해 테스트 코드가 추악해질 수 있습니다." 예를 들어? 전체 응용 프로그램의 구성이 구성 클래스에 연결되는 것이 아니라 구성 클래스 자체의 구조가 "추악한"것으로 생각됩니다. 클래스 구성을 설정하여 종속성을 자동으로 구성해서는 안됩니까?
AndrewKS

답변:


35

단일 구성 객체에 문제가 없으며 모든 설정을 한 곳에 유지하는 이점을 볼 수 있습니다.

그러나 단일 객체를 어디에서나 사용하면 구성 객체와이를 사용하는 클래스간에 높은 수준의 커플 링이 발생합니다. 구성 클래스를 변경해야하는 경우 구성 클래스를 사용하는 모든 인스턴스를 방문하여 아무 것도 설정하지 않았는지 확인해야합니다.

이를 처리하는 한 가지 방법은 앱의 다른 부분에 필요한 구성 개체의 일부를 노출하는 여러 인터페이스를 만드는 것입니다. 다른 클래스가 구성 개체에 액세스하도록 허용하는 대신 인터페이스 인스턴스를 필요한 클래스로 전달합니다. 그런 식으로 구성을 사용하는 앱 부분은 전체 구성 클래스가 아닌 인터페이스에서 더 작은 필드 컬렉션에 의존합니다. 마지막으로, 단위 테스트를 위해 인터페이스를 구현하는 사용자 정의 클래스를 작성할 수 있습니다.

이러한 아이디어를 더 자세히 살펴 보려면 SOLID 원리 , 특히 인터페이스 분리 원리종속성 반전 원리에 대해 읽어 보는 것이 좋습니다 .


7

인터페이스와 관련 설정 그룹을 분리합니다.

다음과 같은 것 :

public interface INotificationEmailSettings {
   public string To { get; set; }
}

public interface IMediaFileSettings {
    public string BasePath { get; set; }
}

기타

실제 환경에서 단일 클래스는 이러한 많은 인터페이스를 구현합니다. 해당 클래스는 DB 또는 앱 구성 또는 가지고있는 것에서 가져올 수 있지만 대부분의 설정을 얻는 방법을 알고 있습니다.

그러나 인터페이스별로 분리하면 실제 종속성이 훨씬 더 명확하고 세분화되므로 테스트 가능성이 크게 향상됩니다.

그러나 .. 항상 설정을 의존성으로 주입하거나 제공하도록 강요하고 싶지는 않습니다. 일관성이나 명시 성을 위해해야 ​​할 주장이 있습니다. 그러나 실제 생활에서는 불필요한 제한으로 보입니다.

따라서 정적 클래스를 파사드로 사용하여 어디에서나 쉽게 설정을 입력 할 수 있으며 해당 정적 클래스 내에서 인터페이스 구현을 찾아 설정을 가져옵니다.

나는 서비스 위치가 대부분에서 빠른 거절을 받는다는 것을 알고 있지만, 직면하자. 생성자를 통한 의존성을 제공하는 것은 무게이며 때로는 무게가 내가 감당해야 할 것보다 많은 경우가있다. 서비스 위치는 인터페이스에 대한 프로그래밍을 통해 테스트 가능성을 유지하고 여러 구현을 허용하면서 정적 싱글 톤 진입 점의 편의성 (주의 깊게 측정되고 적절한 경우는 거의 없음)을 제공하는 솔루션입니다.

public static class AllSettings {
    public INotificationEmailSettings NotificationEmailSettings {
        get {
            return ServiceLocator.Get<INotificationEmailSettings>();
        }
    }
}

나는이 믹스가 모든 세계에서 최고라고 생각합니다.


3

예, 알다시피 전역 구성 객체는 단위 테스트를 어렵게 만듭니다. 단위 테스트를위한 "비밀"세터를 갖는 것은 빠른 해킹입니다. 비록 좋지는 않지만 매우 유용 할 수 있습니다. 단위 테스트 작성을 시작하여 시간이 지남에 따라 더 나은 디자인으로 코드를 리팩토링 할 수 있습니다.

(필수 참조 : 레거시 코드로 효과적으로 작업 . 여기에는 레거시 코드를 단위 테스트하는 데 사용할 수있는 유용한 트릭이 포함되어 있습니다.)

내 경험상 가장 좋은 것은 가능한 한 전역 구성에 거의 의존하지 않는 것입니다. 그러나 반드시 0은 아닙니다. 모두 상황에 따라 다릅니다. 전역 구성에 액세스하고 실제 구성 속성을 생성하고 사용하는 객체에 전달하는 몇 가지 상위 수준의 "구성자"클래스가 있으면 주최자가 테스트 가능한 코드 자체를 많이 포함하지 않는 한 잘 작동 할 수 있습니다. 이를 통해 기존 물리적 구성 스키마를 완전히 방해하지 않으면 서 앱에서 테스트 할 수있는 중요한 기능의 대부분 또는 전부를 테스트 할 수 있습니다.

이것은 Spring for Java와 같이 이미 진정한 의존성 주입 솔루션에 가까워지고 있습니다. 그러한 프레임 워크로 마이그레이션 할 수 있다면 좋습니다. 그러나 실제 생활에서, 특히 레거시 앱을 다룰 때 DI에 대한 느리고 세심한 리팩토링이 달성 가능한 최선의 타협입니다.


나는 당신의 접근 방식을 정말로 좋아했으며 세터의 아이디어가 "비밀"로 유지되거나 그 문제에 대한 "빠른 해킹"으로 간주되어야한다고 제안하지 않습니다. 단위 테스트가 응용 프로그램의 중요한 사용자 / 소비자 / 부분이라는 입장을 받아들이면 test_더 이상 속성과 메서드 를 갖는 것이 그렇게 나쁘지 않습니까? 문제에 대한 진정한 해결책은 DI 프레임 워크를 사용하는 것이지만, 레거시 코드 또는 기타 간단한 경우를 사용하여 테스트 코드를 소외시키지 않으면 서 귀하와 같은 예에서 명확하게 적용됩니다.
Filip Dupanović가

1
@kRON, 이것들은 레거시 코드 단위를 테스트 가능하게 만드는 데 매우 유용하고 실용적인 트릭이며 광범위하게 사용하고 있습니다. 그러나 이상적으로 프로덕션 코드에는 테스트 목적으로 만 소개 된 기능이 포함되어서는 안됩니다. 장기적으로, 이것은 내가 리팩토링하는 곳입니다.
Péter Török

2

필자의 경험에 따르면 Java 세계에서 이것이 Spring을 의미합니다. 런타임시 설정되는 특정 특성을 기반으로 오브젝트 (beans)를 작성하고 관리하며 그렇지 않으면 애플리케이션에 투명합니다. Anon의 test.config 파일을 사용할 수 있습니다. 언급하거나 Spring이 다른 키 (예 : 호스트 이름 또는 환경)를 기반으로 속성을 설정하기 위해 처리 할 논리를 구성 파일에 포함시킬 수 있습니다.

두 번째 문제와 관련하여 일부 재구성을 통해이 문제를 해결할 수 있지만 그다지 심각하지는 않습니다. 이것이 당신의 경우에 의미하는 것은 예를 들어 다양한 다른 클래스가 사용하는 전역 구성 객체가 없다는 것입니다. Spring을 통해 다른 클래스를 구성한 다음 구성에 필요한 모든 것이 Spring을 통해 구성 클래스를 얻었으므로 구성 객체가 없으므로 코드에서 직접 해당 클래스를 사용할 수 있습니다.


2

이 질문은 실제로 구성보다 더 일반적인 문제에 관한 것입니다. "단일"이라는 단어를 읽 자마자 나는 그 패턴과 관련된 모든 문제를 즉시 생각했다.

싱글 톤 패턴은 "유해한 것으로 간주됩니다". 의미하는 것은 항상 잘못된 것은 아니지만 일반적으로 그렇습니다 . 아무것도 싱글 톤 패턴을 사용하는 것을 고려하고 있다면 다음 을 고려하지 마십시오.

  • 서브 클래 싱해야합니까?
  • 인터페이스에 프로그래밍해야합니까?
  • 단위 테스트를 수행해야합니까?
  • 자주 수정해야합니까?
  • 응용 프로그램이 여러 프로덕션 플랫폼 / 환경을 지원하도록 설계 되었습니까?
  • 메모리 사용에 대해 약간의 걱정이 있습니까?

귀하의 답변이 이들 중 하나 (그리고 아마도 내가 생각하지 않은 몇 가지 다른 것)에 대해 "예"라면 싱글 톤을 사용하고 싶지 않을 것입니다. 구성에는 종종 싱글 톤 (또는 인스턴스없는 클래스)이 제공 할 수있는 것보다 훨씬 더 많은 유연성이 필요합니다.

고통없이 싱글 톤의 거의 모든 이점을 원한다면 Spring 또는 Castle과 같은 종속성 주입 프레임 워크 또는 환경에 사용할 수있는 모든 것을 사용하십시오. 이렇게하면 한 번만 선언하면 컨테이너가 필요한 모든 인스턴스에 자동으로 인스턴스를 제공합니다.


0

C #에서 이것을 처리 한 한 가지 방법은 인스턴스 생성 중에 잠기고 모든 클라이언트 개체에서 사용할 수 있도록 모든 데이터를 한 번에 초기화하는 단일 개체를 갖는 것입니다. 이 싱글 톤이 키 / 값 쌍을 처리 할 수있는 경우 여러 다른 클라이언트 및 클라이언트 유형에서 사용하기 위해 복잡한 키를 포함하여 모든 방식의 데이터를 저장할 수 있습니다. 동적으로 유지하고 필요에 따라 새 클라이언트 데이터를로드하려는 경우 클라이언트 기본 키가 있는지 확인하고 누락 된 경우 해당 클라이언트의 데이터를로드하여 기본 사전에 추가 할 수 있습니다. 기본 구성 싱글 톤에는 동일한 클라이언트 유형의 여러 사용자가 기본 구성 싱글 톤을 통해 모두 액세스 한 동일한 데이터를 사용하는 클라이언트 데이터 세트가 포함될 수 있습니다. 이 구성 오브젝트 구성의 대부분은 구성 클라이언트가이 정보에 액세스하는 방법 및 해당 정보가 정적인지 동적인지에 따라 달라질 수 있습니다. 필요한 것에 따라 키 / 값 구성과 특정 객체 API를 모두 사용했습니다.

내 구성 싱글 톤 중 하나가 데이터베이스에서 다시로드 할 메시지를 수신 할 수 있습니다. 이 경우 두 번째 개체 컬렉션에로드하고 컬렉션을 교환하기 위해 기본 컬렉션 만 잠급니다. 이것은 다른 스레드에서 읽기 차단을 방지합니다.

구성 파일에서로드하는 경우 파일 계층 구조가있을 수 있습니다. 한 파일의 설정으로로드 할 다른 파일을 지정할 수 있습니다. 이 메커니즘은 각각 고유 한 구성 설정을 가진 여러 선택적 구성 요소가있는 C # Windows 서비스와 함께 사용했습니다. 파일 구조 패턴은 파일에서 동일하지만 기본 파일 설정을 기반으로로드되거나로드되지 않았습니다.


0

설명하는 클래스 는 함수 대신 데이터를 제외하고 는 God 객체 반 패턴 처럼 들립니다 . 문제입니다. 어떤 이유로 든 필요한 경우 데이터를 다시 읽는 동안 구성 데이터를 읽고 적절한 객체에 저장해야합니다.

또한 부적절한 이유로 싱글 톤을 사용하고 있습니다. 단일 객체는 여러 객체가 존재하여 상태가 나쁜 경우에만 사용해야합니다. 이 경우 싱글 톤을 사용하는 것은 하나 이상의 구성 판독기가 있으면 즉시 오류 상태가 발생하지 않아야하므로 부적절합니다. 둘 이상인 경우 아마도 잘못하고 있지만 구성 판독기 중 하나만 있어야 할 필요는 없습니다.

마지막으로 이러한 전역 상태를 만드는 것은 데이터에 직접 액세스해야하는 것보다 더 많은 클래스를 허용하므로 캡슐화를 위반하는 것입니다.


0

관련 설정의 "뭉치"가있는 설정이 많으면 각 구현마다 별도의 인터페이스로 설정을 분할 한 다음 DI에 필요한 곳에 해당 인터페이스를 주입하는 것이 좋습니다. 테스트 가능성, 낮은 커플 링 및 SRP를 얻습니다.

나는 Los Techies의 Joshua Flanagan으로부터이 문제에 영감을 받았습니다. 그는 얼마 전에 그 문제에 관한 기사썼습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.