의존성 주입의 비판과 단점


119

의존성 주입 (DI)은 잘 알려져 있고 세련된 패턴입니다. 대부분의 엔지니어는 다음과 같은 장점을 알고 있습니다.

  • 단위 테스트에서 분리 가능 / 쉬움
  • 클래스의 종속성을 명시 적으로 정의
  • 우수한 설계 촉진 (예 : 단일 책임 원칙 (SRP))
  • 빠른 구현 구현 ( 예 : DbLogger대신 ConsoleLogger)

저는 DI가 훌륭하고 유용한 패턴이라는 업계 전반의 합의가 있다고 생각합니다. 현재 비판이 그리 많지 않습니다. 커뮤니티에서 언급 된 단점은 일반적으로 미미합니다. 그들 중 일부 :

  • 수업 수 증가
  • 불필요한 인터페이스 생성

현재 우리는 동료와 건축 설계에 대해 이야기합니다. 그는 상당히 보수적이지만 열린 마음입니다. 그는 IT에 종사하는 많은 사람들이 최신 트렌드를 모방하고 장점을 반복하며 일반적으로 너무 많이 생각하지 않기 때문에 내가 생각하는 것에 의문을 갖고 싶어합니다. 너무 깊이 분석하지 마십시오.

내가 묻고 싶은 것은 :

  • 구현이 하나만있을 때 의존성 주입을 사용해야합니까?
  • 언어 / 프레임 워크를 제외한 새 객체를 만드는 것을 금지해야합니까?
  • 특정 클래스를 단위 테스트 할 계획이 없다면 단일 구현을 잘못 생각하고 있습니까 (우리가 하나의 구현을 가지고 있으므로 "빈"인터페이스를 만들고 싶지 않다고 가정 해 봅시다)?

33
디펜 던시 인젝션을 패턴으로 실제로 요구하고 있습니까, 아니면 DI 프레임 워크 사용을 요구하고 있습니까? 이것들은 실제로 별개의 것입니다. 문제의 어느 부분에 관심이 있는지 명확히하거나 두 가지에 대해 명시 적으로 질문해야합니다.
Frax

10
프레임 워크가 아닌 패턴에 대한 @Frax
Landeeyo

10
의존성 반전과 의존성 주입을 혼동하고 있습니다. 전자는 설계 원칙입니다. 후자는 객체 계층 구조를 구성하는 기술 (일반적으로 기존 도구로 구현 됨)입니다.
jpmc26

3
나는 종종 실제 데이터베이스를 사용하고 모의 객체를 사용하지 않고 테스트를 작성합니다. 많은 경우에 정말 잘 작동합니다. 그리고 대부분의 경우 인터페이스가 필요하지 않습니다. UserService해당 클래스가 있으면 논리에 대한 홀더 일뿐입니다. 데이터베이스 연결이 주입되고 롤백 된 트랜잭션 내에서 테스트가 실행됩니다. 많은 사람들이이 나쁜 관행을 부를 것이지만 이것이 매우 효과적이라는 것을 알았습니다. 테스트를 위해 코드를 왜곡 할 필요가 없으며 통합 테스트의 힘을 찾는 버그를 얻게됩니다.
usr

3
DI는 거의 항상 좋습니다. 그것으로 나쁜 점은 많은 사람들 이 DI를 알고 있다고 생각 하지만 그들이 아는 것은 자신이하는 일을 확신하지 않고도 이상한 프레임 워크를 사용하는 방법입니다. 오늘날 DI는 Cargo Cult Programming으로 많은 어려움을 겪고 있습니다.
T. Sar

답변:


160

먼저, 디자인 방식을 프레임 워크 개념과 분리하고 싶습니다. 가장 단순하고 가장 기본적인 수준의 종속성 주입은 다음과 같습니다.

부모 개체는 자식 개체에 필요한 모든 종속성을 제공합니다.

그게 다야. 인터페이스, 프레임 워크, 주입 스타일 등이 필요하지 않습니다. 공정하기 위해서는 20 년 전에이 패턴에 대해 처음 배웠습니다. 새로운 것이 아닙니다.

의존성 주입과 관련하여 부모와 자식이라는 용어에 대해 혼란을 겪고있는 2 명 이상의 사람들로 인해 :

  • 부모는 인스턴스화하고 사용하는 자식 개체를 구성하는 개체입니다
  • 아이가 수동적으로 인스턴스화 할 수 있도록 설계되어있는 구성 요소입니다. 즉, 부모가 제공하는 모든 종속성을 사용하도록 설계되었으며 자체 종속성을 인스턴스화하지 않습니다.

의존성 주입은 객체 구성 의 패턴입니다 .

왜 인터페이스인가?

인터페이스는 계약입니다. 두 객체가 얼마나 밀접하게 결합 될 수 있는지를 제한하기 위해 존재합니다. 모든 종속성에 인터페이스가 필요한 것은 아니지만 모듈 코드 작성에 도움이됩니다.

단위 테스트의 개념을 추가하면 주어진 인터페이스에 대해 두 가지 개념적 구현, 즉 응용 프로그램에서 사용하려는 실제 객체와 객체에 의존하는 코드를 테스트하는 데 사용되는 모의 객체 또는 스터 빙 객체가 있습니다. 이것만으로도 인터페이스에 대한 충분한 정당화가 가능합니다.

왜 프레임 워크입니까?

자식 개체가 많을 때는 본질적으로 초기화하고 자식 개체에 대한 종속성을 제공하는 것이 어려울 수 있습니다. 프레임 워크는 다음과 같은 이점을 제공합니다.

  • 구성 요소에 대한 자동 배선 종속성
  • 일종의 설정으로 구성 요소 구성
  • 보일러 플레이트 코드를 자동화하여 여러 위치에서 작성된 것을 볼 필요가 없습니다.

또한 다음과 같은 단점이 있습니다.

  • 부모 객체는 "컨테이너"이며 코드에는 없습니다.
  • 테스트 코드에서 직접 종속성을 제공 할 수없는 경우 테스트가 더 복잡해집니다.
  • 리플렉션 및 기타 많은 트릭을 사용하여 모든 종속성을 해결하므로 초기화 속도가 느려질 수 있습니다
  • 특히 컨테이너가 인터페이스와 인터페이스를 구현하는 실제 컴포넌트 사이에 프록시를 삽입하는 경우 런타임 디버깅이 더 어려울 수 있습니다 (Spring에 내장 된 측면 지향 프로그래밍이 마음에 듭니다). 컨테이너는 블랙 박스이며 디버깅 프로세스를 촉진하는 개념으로 항상 구축되는 것은 아닙니다.

모든 말은 장단점이 있습니다. 움직이는 부분이 많지 않은 DI 프로젝트의 경우 DI 프레임 워크를 사용해야 할 이유가 거의 없습니다. 그러나 특정 구성 요소가 이미있는 복잡한 프로젝트의 경우 프레임 워크를 정당화 할 수 있습니다.

[인터넷의 임의 기사]는 어떻습니까?

어때요? 사람들이 "한 가지 진정한 방법"을 수행하지 않으면 많은 사람들이 지나치게 열중하고 많은 제한을 추가하고 당신을 비난 할 수 있습니다. 진정한 방법은 없습니다. 기사에서 유용한 정보를 추출하고 동의하지 않는 내용은 무시하십시오.

요컨대 자신을 생각하고 시도해보십시오.

"오래된 머리"로 작업

최대한 많이 배우십시오. 70 년대에 일하고있는 많은 개발자들과 함께 할 것은 많은 것들에 대해 독단적이지 않은 법을 배웠다는 것입니다. 그들은 올바른 결과를 산출하는 수십 년 동안 협력 해 온 방법을 가지고 있습니다.

나는 이것들 중 몇 가지를 다루는 특권을 가지고 있었고, 그들은 많은 의미가있는 잔인하게 정직한 피드백을 제공 할 수 있습니다. 그리고 그들이 가치를 볼 수있는 곳에, 그들은 그 도구를 레퍼토리에 추가합니다.


6
@CarlLeth, Spring에서 .net 변형까지 다양한 프레임 워크로 작업했습니다. Spring은 리플렉션 / 클래스 로더 블랙 매직을 사용하여 구현을 프라이빗 필드에 바로 주입 할 수 있도록합니다. 이와 같이 만들어진 구성 요소를 테스트하는 유일한 방법은 컨테이너를 사용하는 것입니다. Spring에는 테스트 환경을 구성하기 위해 JUnit 러너가 있지만 직접 설정하는 것보다 더 복잡합니다. 그렇습니다, 나는 단지 실제적인 예를 들었습니다 .
Berin Loritsch

17
문제 해결사 / 유지 보수 자 모자를 착용 할 때 프레임 워크를 통해 DI에 장애물이된다는 단점이 하나 더 있습니다. 거리가 멀어지면 짜증나는 동작으로 인해 오프라인 디버깅이 더 어려워집니다. 최악의 경우, 종속성이 초기화되고 전달되는 방법을보기 위해 코드를 실행해야합니다. "테스트"라는 맥락에서 이것을 언급하지만 소스를 살펴 보는 것만으로도 실제로는 더 나쁩니다. 실행하려고하면 마음에 들지 않습니다 (많은 설정이 필요할 수 있음). 코드를 보아서 코드가하는 일을 방해하는 능력을 방해하는 것은 나쁜 일입니다.
Jeroen Mostert 11:30에

1
인터페이스는 계약이 아니며 단순히 API입니다. 계약은 의미를 암시합니다. 이 답변은 언어 별 용어와 Java / C # 관련 규칙을 사용합니다.
Frank Hileman

2
@BerinLoritsch 자신의 대답의 주요 요점은 DI 원칙! = 주어진 DI 프레임 워크라는 것입니다. Spring이 끔찍하고 용서할 수없는 일을 할 수 있다는 사실은 일반적인 DI 프레임 워크가 아니라 Spring의 단점입니다. 훌륭한 DI 프레임 워크는 귀찮은 트릭없이 DI 원칙을 따르는 데 도움이됩니다.
Carl Leth

1
@CarlLeth : 모든 DI 프레임 워크는 프로그래머가 철자가 원하지 않는 것을 제거하거나 자동화하도록 설계되었으며, 그 방법은 다양합니다. 내가 아는 한, 그들은 모든 방법 (또는 알 수있는 능력을 제거 하면 바로 A와 B에보고하여 클래스 A와 B의 상호 작용) -, 당신은 또한보고 최소한 필요를 DI 설치 / 구성 / 컨벤션. 프로그래머에게는 문제가 아니라 (실제로 원하는 것임), 관리자 / 디버거 (나중에 동일한 프로그래머 일 수 있음)에게는 잠재적 인 문제입니다. DI 프레임 워크가 "완벽한"경우에도 이는 트레이드 오프입니다.
Jeroen Mostert

88

의존성 주입은 대부분의 패턴과 마찬가지로 문제에 대한 해결책 입니다. 따라서 처음부터 문제가 있는지 물어 보는 것으로 시작하십시오. 그렇지 않은 경우 패턴을 사용하면 코드가 더 나빠질 수 있습니다.

종속성을 줄이거 나 제거 할 수있는 경우 먼저 고려하십시오. 다른 모든 것들이 동일하기 때문에 시스템의 각 구성 요소는 가능한 한 적은 종속성을 갖기를 원합니다. 그리고 의존성이 사라지면 주입 여부에 대한 질문이 무섭게됩니다!

외부 서비스에서 일부 데이터를 다운로드하여 구문 분석 한 후 복잡한 분석을 수행하고 결과에 파일로 쓰는 모듈을 고려하십시오.

이제 외부 서비스에 대한 종속성이 하드 코딩 된 경우이 모듈의 내부 처리를 단위 테스트하기가 실제로 어렵습니다. 따라서 외부 서비스와 파일 시스템을 인터페이스 종속성으로 삽입하기로 결정할 수 있습니다. 대신 모의를 주입하여 내부 논리를 단위 테스트 할 수 있습니다.

그러나 훨씬 더 나은 솔루션은 단순히 분석을 입력 / 출력과 분리하는 것입니다. 부작용없이 모듈로 분석을 추출하면 테스트하기가 훨씬 쉬워집니다. 조롱은 코드 냄새입니다. 항상 피할 수있는 것은 아니지만 일반적으로 조롱에 의존하지 않고 테스트 할 수있는 것이 좋습니다. 따라서 종속성을 제거하면 DI가 완화해야하는 문제를 피할 수 있습니다. 이러한 디자인은 SRP에도 훨씬 잘 부합합니다.

DI가 SRP 또는 우려 분리, 높은 응집력 / 낮은 결합 등과 같은 다른 우수한 설계 원칙을 반드시 촉진하지는 않는다는 점을 강조하고 싶습니다. 그 반대 효과도있을 수 있습니다. 내부적으로 다른 클래스 B를 사용하는 클래스 A를 고려하십시오. B는 A 만 사용하므로 완전히 캡슐화되어 구현 세부 사항으로 간주 될 수 있습니다. B를 A의 생성자에 주입하도록 이것을 변경하면이 구현 세부 사항을 노출 하고이 종속성에 대한 지식과 B를 초기화하는 방법에 대한 지식, B의 수명 등은 시스템의 다른 곳에 별도로 존재해야합니다 A. 누출 우려로 인해 전반적인 아키텍처가 악화되었습니다.

반면에 DI가 실제로 유용한 경우가 있습니다. 예를 들어 로거와 같은 부작용이있는 글로벌 서비스의 경우.

문제는 패턴과 아키텍처가 툴이 아닌 목표 자체가 될 때입니다. "DI를 사용해야합니까?" 말 앞에 카트를 놓는 것입니다. "우리에게 문제가 있습니까?" "이 문제에 가장 적합한 솔루션은 무엇입니까?"

당신의 질문의 일부는 "패턴의 요구를 만족시키기 위해 불필요한 인터페이스를 만들어야합니까?"로 요약됩니다. 당신은 아마 이것에 대한 답을 이미 알고있을 것입니다 – 절대적으로 아닙니다 ! 달리 말하면 누구나 고가의 컨설팅 시간 인 무언가를 판매하려고합니다. 인터페이스는 추상화를 나타내는 경우에만 값을 갖습니다. 단일 클래스의 표면을 모방 한 인터페이스를 "헤더 인터페이스"라고하며 이는 알려진 반 패턴입니다.


15
더 동의 할 수 없었습니다! 또한 그것을 위해 조롱하는 것은 실제 구현을 실제로 테스트하지 않는다는 것을 의미합니다. 경우 A사용하는 B생산, 그러나 단지에 대해 테스트되었습니다 MockB이 생산에 작동하는지, 우리의 테스트는 말해주지 않는다. 도메인 모델의 순수한 (부작용이없는) 구성 요소가 서로를 주입하고 조롱하면 결과적으로 모든 시간을 낭비하고 부풀어 오며 취약한 코드베이스를 만들고 결과 시스템에 대한 신뢰도가 떨어집니다. 동일한 시스템의 임의의 부분이 아닌 시스템의 경계를 모의하십시오.
Warbo

17
@CarlLeth 왜 DI가 코드를 "테스트 가능하고 유지 보수 가능"하게 만들고 코드가 적지 않다고 생각하십니까? JacquesB는 부작용 이 테스트 가능성 / 유지 가능성에 해가되는 것입니다. 코드에 부작용이 없다면, 다른 코드를 어떻게 / 어디에서 / 언제 / 어떻게 호출하든 상관 없습니다. 우리는 그것을 간단하고 직접적으로 유지할 수 있습니다. 코드에 부작용이있는 경우 주의 해야 합니다. DI는 기능에서 부작용을 제거하고 매개 변수에 넣을 수 있으므로 이러한 기능을보다 테스트 가능하지만 프로그램은 더 복잡하게 만들 수 있습니다. 때로는 불가피하다 (예 : DB 액세스). 코드에 부작용이 없다면 DI는 쓸모없는 복잡성입니다.
Warbo

13
@CarlLeth : DI는 코드를 금지하는 의존성이있는 코드를 독립적으로 테스트 할 수있게하는 문제에 대한 해결책 중 하나 입니다. 그러나 전체적인 복잡성을 줄이거 나 코드를 더 읽기 쉽게 만들지 않으므로 유지 관리 성이 반드시 증가하지는 않습니다. 그러나 더 나은 관심사 분리를 통해 이러한 모든 종속성을 제거 할 수 있으면 DI가 필요하지 않기 때문에 DI의 이점을 완벽하게 "무효화"합니다. 이것은 종종 코드를보다 테스트 가능 하고 유지 보수 하기 쉽게 만드는 더 나은 솔루션 입니다.
Doc Brown

5
@Warbo 이것은 원래이며 여전히 유일한 조롱 사용입니다. 시스템 경계에서도 거의 필요하지 않습니다. 사람들은 거의 쓸모없는 테스트를 만들고 업데이트하는 데 많은 시간을 낭비합니다.
Frank Hileman

6
@CarlLeth : 이제 오해가 어디에서 왔는지 봅니다. 의존성 반전 에 대해 이야기하고 있습니다. 그러나 질문,이 답변 및 나의 의견은 DI = depency injection에 관한 것 입니다.
Doc Brown

36

내 경험상 의존성 주입에는 여러 가지 단점이 있습니다.

첫째, DI를 사용한다고해서 광고 된만큼 자동화 된 테스트를 단순화 할 수는 없습니다. 인터페이스의 모의 구현으로 클래스를 단위 테스트하면 해당 클래스가 인터페이스와 어떻게 상호 작용하는지 확인할 수 있습니다. 즉, 테스트중인 클래스가 인터페이스에서 제공하는 계약을 사용하는 방법을 단위 테스트 할 수 있습니다. 그러나 이는 테스트중인 클래스에서 인터페이스로의 입력이 예상보다 훨씬 더 확실하다는 것을 제공합니다. 테스트중인 클래스가 거의 보편적으로 모의 출력이기 때문에 테스트 대상 클래스가 인터페이스에서 출력 될 것으로 예상된다는 확신이 다소 나쁘다. 이는 자체적으로 버그, 과도 단순화 등의 영향을 받는다. 간단히 말해, 실제 인터페이스 구현에서 클래스가 예상대로 작동하는지 확인할 수는 없습니다.

둘째, DI는 코드를 탐색하기가 훨씬 어렵습니다. 함수 입력으로 사용되는 클래스의 정의를 탐색하려고 할 때 인터페이스는 사소한 성가심 (예 : 단일 구현이있는 경우)에서 주요 시간 싱크 (예 : IDisposable과 같은 지나치게 일반적인 인터페이스가 사용되는 경우)가 될 수 있습니다. 사용중인 실제 구현을 찾으려고 할 때. 이것은 "이 로깅 명령문이 인쇄 된 직후에 발생하는 코드에서 널 참조 예외를 수정해야합니다"와 같은 간단한 연습을 하루의 노력으로 바꿀 수 있습니다.

셋째, DI와 프레임 워크의 사용은 양날의 검입니다. 일반적인 작업에 필요한 보일러 플레이트 코드의 양을 크게 줄일 수 있습니다. 그러나 이러한 공통 작업이 실제로 어떻게 연결되어 있는지 이해하려면 특정 DI 프레임 워크에 대한 자세한 지식이 필요합니다. 종속성이 프레임 워크에로드되는 방식을 이해하고 주입 할 프레임 워크에 새로운 종속성을 추가하려면 상당한 양의 배경 자료를 읽고 프레임 워크에 대한 몇 가지 기본 자습서를 따라야합니다. 이것은 간단한 작업을 시간이 많이 걸리는 작업으로 바꿀 수 있습니다.


또한 주사할수록 시작 시간이 길어질 것입니다. 대부분의 DI 프레임 워크는 사용 시점에 관계없이 시작시 모든 주입 가능한 싱글 톤 인스턴스를 만듭니다.
Rodney P. Barbati

7
실제 구현 (모의가 아닌)으로 클래스를 테스트하려면 단위 테스트와 유사하지만 모의를 사용하지 않는 테스트 기능 테스트를 작성할 수 있습니다.
BЈовић

2
두 번째 단락은 더 미묘한 차이가 필요하다고 생각합니다. DI 자체로는 코드를 탐색하기가 어렵지 않습니다. 가장 간단하게 DI는 단순히 SOLID를 따르는 결과입니다. 복잡성을 증가시키는 것은 불필요한 간접 및 DI 프레임 워크 의 사용입니다 . 그 외에는이 대답이 머리에 못을 박았습니다.
Konrad Rudolph

4
의존성 주입은 실제로 필요한 경우를 제외하고 다른 불필요한 코드가 많이 존재할 수 있다는 경고 신호입니다. 경영진은 종종 개발자가 복잡성을 위해 복잡성을 추가한다는 사실에 놀랐습니다.
Frank Hileman 19:43에

1
+1. 이것은 요청 된 질문에 대한 실제 답변이며 허용되는 답변이어야합니다.
메이슨 휠러

13

나는 ".NET의 의존성 주입"에서 Mark Seemann의 조언을 따랐다.

DI는 '휘발성 의존성'이있을 때 사용해야합니다. 예를 들어 변경 될 수있는 합리적인 기회가 있습니다.

향후에 둘 이상의 구현이 있거나 구현이 변경 될 수 있다고 생각되면 DI를 사용하십시오. 그렇지 않으면 new괜찮습니다.


5
그는 또한 OO 및 기능 언어 blog.ploeh.dk/2017/01/27/…
jk에

1
좋은 지적입니다. 기본적으로 모든 종속성에 대한 인터페이스를 만들면 YAGNI에 어긋납니다.
Landeeyo

4
".NET의 종속성 주입"에 대한 참조를 제공 할 수 있습니까 ?
Peter Mortensen

1
단위 테스트를 수행하는 경우 종속성이 불안정 할 가능성이 큽니다.
Jaquez

11
좋은 점은 개발자가 항상 완벽한 정확성으로 미래를 예측할 수 있다는 것입니다.
Frank Hileman

5

DI에 대한 나의 가장 큰 애완 동물은 이미 몇 가지 대답으로 이미 언급되었지만 여기에서 조금 더 확장하겠습니다. DI (컨테이너 등을 사용하여 오늘날 대부분 수행되므로) 실제로 코드 가독성이 떨어집니다. 그리고 코드 가독성은 오늘날의 대부분의 프로그래밍 혁신에 대한 이유입니다. 누군가가 말했듯이-코드 작성은 쉽습니다. 코드 읽기가 어렵습니다. 그러나 한 번의 작은 write-once throwaway 유틸리티를 작성하지 않는 한 매우 중요합니다.

이와 관련하여 DI의 문제점은 불투명하다는 것입니다. 컨테이너는 블랙 박스입니다. 객체는 단순히 어딘가에서 나타나며 누가 언제 그리고 언제 구성 했는가? 생성자에게 전달 된 것은 무엇입니까? 이 인스턴스를 누구와 공유하고 있습니까? 누가 알아...

또한 주로 인터페이스로 작업 할 때 IDE의 모든 "정의로 이동"기능이 연기됩니다. 프로그램을 실행하지 않고 프로그램 흐름을 추적하는 것은 끔찍하게 어렵습니다.이 특정 지점에서 인터페이스의 WHICH 구현이 사용 된 것을 보았습니다. 때로는 기술적 인 장애물이있어 밟지 않아도됩니다. DI 컨테이너의 뒤틀린 장을 통과하는 것이 가능하더라도 모든 일이 금방 좌절의 원인이됩니다.

DI를 사용한 코드를 효율적으로 사용하려면이를 잘 알고 있어야하며 어디로 가는지 이미 알고 있어야합니다 .


3

신속한 구현 구현 (예 : ConsoleLogger 대신 DbLogger)

DI는 일반적으로 좋은 일이지만 모든 것에 맹목적으로 사용하지 않는 것이 좋습니다. 예를 들어, 나는 로거를 주입하지 않습니다. DI의 장점 중 하나는 종속성을 명시적이고 명확하게 만드는 것입니다. ILogger거의 모든 클래스의 종속성으로 나열 할 필요 는 없습니다 . 필요한 유연성을 제공하는 것은 로거의 책임입니다. 모든 로거는 정적 최종 멤버이므로 정적이 아닌 로거가 필요할 때 로거 주입을 고려할 수 있습니다.

수업 수 증가

이는 DI 자체가 아니라 주어진 DI 프레임 워크 또는 모의 프레임 워크의 단점입니다. 대부분의 장소에서 수업은 구체적인 수업에 의존하므로 보일러 플레이트가 필요하지 않습니다. Guice (Java DI 프레임 워크)는 기본적으로 클래스를 자체적으로 바인딩하며 테스트에서 바인딩을 재정의해야합니다 (또는 대신 수동으로 연결).

불필요한 인터페이스 생성

필요할 때만 인터페이스를 만듭니다 (좀 드문 경우입니다). 이것은 때때로 클래스의 모든 발생을 인터페이스로 대체해야하지만 IDE 가이를 대신 할 수 있음을 의미합니다.

구현이 하나만있을 때 의존성 주입을 사용해야합니까?

예. 그러나 상용구를 추가하지 마십시오 .

언어 / 프레임 워크를 제외한 새 객체를 만드는 것을 금지해야합니까?

아니요. 인스턴스가 방금 생성되어 전달되고 주입 할 때 아무런 가치가없는 많은 값 (불변) 및 데이터 (변이 가능) 클래스가 있습니다. 그러한 물체).

그들에게는 대신에 공장을 주입해야 할 수도 있지만 대부분의 경우 이해가되지 않습니다 (예 : @Value class NamedUrl {private final String name; private final URL url;}실제로 공장이 필요하지 않으며 주입 할 것이 없습니다).

특정 클래스를 단위 테스트 할 계획이 없다면 단일 구현을 잘못 생각하고 있습니까 (우리가 하나의 구현을 가지고 있으므로 "빈"인터페이스를 만들고 싶지 않다고 가정 해 봅시다)?

IMHO 코드가 부풀어 오르지 않는 한 괜찮습니다. 의존성을 주입하되 나중에 번거 로움없이 인터페이스를 만들지 마십시오.

실제로, 현재 프로젝트에는 4 개의 클래스가 있습니다 (수백 개 중) . 데이터 객체를 포함하여 너무 많은 장소에서 사용되는 단순한 클래스이므로 DI 에서 제외 하기로 결정했습니다 .


대부분의 DI 프레임 워크의 또 다른 단점은 런타임 오버 헤드입니다. 이것은 (자바, 거기에 시간을 컴파일 이동할 수 있습니다 단검 , 다른 언어에 대한 아무 생각).

또 다른 단점은 모든 곳에서 발생하는 마술이며 조정이 가능합니다 (예 : Guice를 사용할 때 프록시 생성을 비활성화했습니다).


-4

내 의견으로는 Dependency Injection의 전체 개념이 과대 평가되었다고 말해야합니다.

DI는 현대의 글로벌 가치에 해당합니다. 당신이 주입하는 것은 전역 싱글 톤과 순수한 코드 객체입니다. 그렇지 않으면 주입 할 수 없습니다. 주어진 라이브러리 (JPA, Spring Data 등)를 사용하려면 대부분의 DI 사용이 귀하에게 강요됩니다. DI는 대부분 스파게티를 육성하고 재배하기위한 완벽한 환경을 제공합니다.

정직하게 말하면, 클래스를 테스트하는 가장 쉬운 방법은 모든 종속성이 재정의 가능한 메소드로 작성되도록하는 것입니다. 그런 다음 실제 클래스에서 파생 된 Test 클래스를 작성하고 해당 메소드를 대체하십시오.

그런 다음 Test 클래스를 인스턴스화하고 모든 메소드를 테스트하십시오. 이것은 당신에게 명확하지 않을 것입니다-당신이 테스트하는 방법은 테스트중인 클래스에 속하는 방법입니다. 그리고 이러한 모든 메소드 테스트는 테스트중인 클래스와 연관된 단위 테스트 클래스 인 단일 클래스 파일에서 발생합니다. 여기에는 오버 헤드가 없습니다. 이것이 단위 테스트의 작동 방식입니다.

코드에서이 개념은 다음과 같습니다.

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}

결과는 DI를 사용하는 것과 정확히 동일합니다. 즉, ClassUnderTest가 테스트 용으로 구성되어 있습니다.

유일한 차이점은이 코드가 완전히 간결하고, 완전히 캡슐화되고, 코드를 작성하기 쉽고, 이해하기 쉽고, 더 빠르며, 메모리를 적게 사용하며, 대체 구성이 필요하지 않으며, 프레임 워크가 필요하지 않으며, 4 페이지의 원인이되지 않는다는 것입니다 (WTF!) 스택 추적에는 정확히 ZERO (0) 클래스가 포함되어 있으며 초보자부터 전문가에 이르기까지 OO 지식이 거의없는 사람에게는 완전히 분명합니다 (생각하지만 오해 할 것입니다).

물론 우리는 그것을 사용할 수 없습니다-너무 명백하고 유행이 아닙니다.

마지막 날, DI에 대한 나의 가장 큰 관심사는 내가 본 프로젝트가 비참하게 실패한다는 것입니다. DI는 모든 것이 하나로 묶인 접착제 인 거대한 코드베이스였습니다. DI는 아키텍쳐가 아닙니다. 실제로는 소수의 상황에서만 관련이 있습니다. 대부분의 경우 다른 라이브러리 (JPA, Spring Data 등)를 사용하도록 강요됩니다. 대부분의 경우 잘 설계된 코드 기반에서 DI의 대부분의 사용은 일상적인 개발 활동이 이루어지는 수준 이하에서 발생합니다.


6
당신은 동등한 것이 아니라 의존성 주입의 반대에 대해 설명했습니다. 모델에서 모든 객체는 모든 종속성의 구체적인 구현을 알아야하지만 "주"구성 요소의 책임이되는 DI를 사용하여 적절한 구현을 함께 붙입니다. DI는 다른 DI- 종속성 반전과 밀접한 관계가 있으며, 상위 수준의 구성 요소가 하위 수준의 구성 요소에 대한 의존성을 갖기를 원하지 않습니다.
Logan Pickup

1
단 하나의 클래스 상속 수준과 하나의 종속성 수준이있을 때 깔끔합니다. 지구가 확장되면서 지옥으로 변할 것입니까?
Ewan

4
인터페이스를 사용하고 initializeDependencies ()를 생성자와 동일하지만 깔끔한 위치로 옮기는 경우. 다음 단계에서 구성 매개 변수를 추가하면 모든 테스트 클래스를 제거 할 수 있습니다.
Ewan

5
이것에는 너무 많은 문제가 있습니다. 다른 사람이 말했듯이, 당신의 'DI 해당하는'예는 정반대이며, 모든 의존성 주입하지 않고, 개념의 이해의 전체 부족을 보여줍니다뿐만 아니라 다른 잠재적 인 함정을 소개 : 부분적으로 초기화 된 객체는 코드 냄새입니다 이완으로 초기화를 생성자로 옮기고 생성자 매개 변수를 통해 전달하십시오. 그럼 당신은 DI를 가지고 있습니다 ...
Mr.Mindor

3
@ Mr.Mindor에 추가 : 초기화에 적용되지 않는보다 일반적인 안티 패턴 "순차 결합"이 있습니다. 객체의 메소드 (또는 일반적으로 API 호출)를 특정 순서로 실행 해야하는 경우 (예 : bar이후에만 호출 할 수 foo있는 경우) 잘못된 API입니다. 그건 주장 기능을 (제공 bar), 그러나 우리는 실제로 (이후 사용할 수 없습니다 foo라는 않았을 수 있습니다). initializeDependencies(안티?) 패턴 을 고수 하려면 적어도 개인 / 보호 된 패턴으로 만들고 생성자에서 자동으로 호출해야 API가 성실합니다.
Warbo

-6

정말로 당신의 질문은 "단위 테스트가 나쁜가요?"로 요약됩니다.

대체 수업의 99 %가 단위 테스트를 가능하게하는 모의가 될 것입니다.

DI없이 단위 테스트를 수행하는 경우 클래스가 모의 데이터 또는 모의 서비스를 사용하도록하는 방법에 문제가 있습니다. '논리의 일부'라고 말하면 서비스로 분리되지 않을 수 있습니다.

이를 수행하는 다른 방법이 있지만 DI는 훌륭하고 융통성있는 방법입니다. 테스트를 위해 코드를 작성하면 거의 모든 코드를 사용해야합니다. 다른 코드 조각이 필요하기 때문에 구체적인 유형을 인스턴스화하는 소위 '가난한 사람의 DI'조차도 마찬가지입니다.

단위 테스트의 이점이 압도 될 정도로 나쁜 단점을 상상하기는 어렵습니다.


13
DI가 단위 테스트에 관한 것이라는 귀하의 주장에 동의하지 않습니다. 단위 테스트를 용이하게하는 것은 DI의 장점 중 하나 일뿐 아니라 가장 중요한 것은 아닙니다.
Robert Harvey

5
단위 테스트와 DI가 너무 가깝다는 전제에 동의하지 않습니다. 모의 / 스텁을 사용하여 테스트 스위트를 좀 더 거짓말로 만듭니다 : 테스트중인 시스템이 실제 시스템에서 멀어집니다. 객관적으로 나쁘다. 때로는 그것이 거꾸로 된 것보다 중요합니다 : 조롱 된 FS 호출은 정리가 필요하지 않습니다. 조롱 된 HTTP 요청은 빠르고 결정적이며 오프라인에서 작동합니다. 반대로, 우리 new는 메소드 내부 에서 하드 코딩 된 코드를 사용할 때마다 프로덕션 환경에서 실행되는 동일한 코드가 테스트 중에 실행되고 있음 을 알고 있습니다.
Warbo

8
아니요,“단위 테스트가 나쁜가요?”가 아니라“모의가 (a) 정말로 필요하며 (b) 복잡성을 증가시킬 가치가 있습니까?”그것은 매우 다른 질문입니다. 단위 테스트 나쁘지 않으며 (문자 그대로 아무도 논쟁 하지 않습니다 ) 일반적으로 가치가 있습니다. 그러나 모든 단위 테스트에 조롱이 필요한 것은 아니며 조롱에 상당한 비용이 들기 때문에 적어도 신중하게 사용해야합니다.
Konrad Rudolph

6
@Ewan 당신의 마지막 의견 후 우리는 동의하지 않습니다. 나는 것을 말하고 대부분의 단위 테스트는 DI [워크]을하지 않아도 대부분의 단위 테스트를 조롱 필요가 없기 때문에. 사실, 나는 이것을 코드 품질에 대한 휴리스틱으로도 사용할 것입니다. 대부분의 코드를 DI / 모의 객체없이 단위 테스트 할 수 없다면 너무 올바르게 결합 된 나쁜 코드를 작성했습니다. 대부분의 코드는 고도로 분리되어 있고 단일 책임과 범용이어야하며, 사소하게 테스트 할 수 있어야합니다.
Konrad Rudolph

5
@Ewan 귀하의 링크는 단위 테스트를 잘 정의합니다. 그 정의에 따르면 내 Order예제는 단위 테스트입니다. 메서드 (의 total메소드 Order)를 테스트하고 있습니다. 2 개의 클래스에서 코드를 호출한다고 불평하고 있습니다. 제 답변은 무엇 입니까? 우리는 "한 번에 2 개의 클래스"를 테스트하지 않고 total메소드를 테스트하고 있습니다. 우리 메소드가 어떻게 작동하는지 신경 쓰지 않아야합니다 . 테스트는 취약성, 밀접한 커플 링 등을 유발합니다. 클래스 / 모듈 / CPU 레지스터 등이 아닌 메소드의 동작 (반환 값 및 부작용) 에만 관심이 있습니다. 그것은 과정에서 사용되었습니다.
Warbo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.