생성자에서 "new"를 사용하는 것이 항상 좋지 않습니까?


37

생성자에서 "new"를 사용하는 것 (단순한 값이 아닌 다른 객체의 경우)은 단위 테스트를 불가능하게하기 때문에 좋지 않습니다. 단위 테스트에 실제로 경험이 없기 때문에 먼저 배울 규칙을 모 으려고합니다. 또한 이것은 사용 된 언어에 관계없이 일반적으로 유효한 규칙입니까?


9
테스트가 불가능하지는 않습니다. 그래도 코드 유지 관리 및 테스트가 더 어려워집니다. 예를 들어 new is glue 를 읽으십시오 .
David Arno

38
"항상"은 항상 올바르지 않습니다. :) 모범 사례가 있지만 예외는 많습니다.
Paul

63
이것이 무슨 언어 지? new다른 언어로 다른 것을 의미합니다.
user2357112

13
이 질문은 언어에 따라 조금씩 다릅니다. 모든 언어에 new키워드 가있는 것은 아닙니다 . 어떤 언어를 요구하십니까?
Bryan Oakley

7
바보 같은 규칙. "new"키워드와 관련이 거의없는 경우 종속성 삽입을 사용 하지 않습니다. "종속성 반전을 중단하기 위해 새로운 것을 사용하는 것이 문제"라고 말하는 대신 "종속성 반전을 중단하지 마십시오"라고 말하십시오.
Matt Timmermans

답변:


36

항상 예외가 있으며 제목의 '항상'과 관련하여 문제가 있지만,이 지침 은 일반적으로 유효하며 생성자 외부에도 적용됩니다.

생성자에서 new를 사용하면 SOLID의 D를 위반합니다 (종속성 전환 원리). 단위 테스트는 모두 격리에 관한 것이므로 코드를 테스트하기가 어렵습니다. 구체적인 참조가 있으면 클래스를 분리하기가 어렵습니다.

그것은 단지 단위 테스트에 관한 것이 아닙니다. 저장소를 두 개의 다른 데이터베이스로 한 번에 가리 키려면 어떻게해야합니까? 내 상황을 전달할 수 있기 때문에 다른 위치를 가리키는 두 개의 다른 저장소를 인스턴스화 할 수 있습니다.

생성자에서 new를 사용하지 않으면 코드가 더 유연 해집니다. 이것은 new객체 초기화 이외의 구문을 사용할 수있는 언어에도 적용됩니다 .

그러나 분명히 올바른 판단을해야합니다. 사용 new하기에 좋을 때나 사용 하지 않는 것이 좋을 때가 많지만 부정적인 결과는 없습니다. 어느 시점에서 new호출해야합니다. new다른 많은 클래스가 의존하는 클래스 내부 를 호출하는 데 매우주의하십시오 .

생성자에서 빈 개인 컬렉션을 초기화하는 등의 작업을 수행하는 것이 좋으며 주입하는 것은 어리 석습니다.

클래스에 대한 참조가 많을수록 클래스 new내 에서 호출하지 않도록주의해야 합니다.


12
이 규칙에는 아무런 가치가 없습니다. 코드에서 긴밀한 결합을 피하는 방법에 대한 규칙과 지침이 있습니다. 이 규칙은 우리에게 추가적인 가치를주지 않으면 서 너무 제한적이라는 점을 제외하고는 똑같은 문제를 다루는 것 같습니다. 그래서 요점이 뭐야? head = null어떤 식 으로든 코드를 향상시키는 모든 특수한 경우를 처리하는 대신 LinkedList 구현을 위해 더미 헤더 객체를 만드는 이유는 무엇 입니까? 포함 된 컬렉션을 null로 남겨두고 요청시 생성하는 것이 생성자에서 만드는 것보다 더 나은 이유는 무엇입니까?
Voo

22
요점이 없습니다. 예. 지속성 모듈은 절대적으로 상위 모듈이 의존해서는 안되는 것입니다. 그러나 "새로운 것을 사용하지 마십시오 "는 "일부는 주입해야하고 직접 결합되어서는 안된다"는 것이 아닙니다. 신뢰할 수있는 Agile Software Development 사본을 사용하는 경우 Martin은 일반적으로 단일 클래스가 아닌 모듈에 대해 이야기하고 있음을 알 수 있습니다. 그들."
Voo

11
요점은 모듈 간, 특히 다른 추상화 레벨의 모듈 간 종속성을 피해야한다는 것 입니다. 그러나 모듈은 단일 클래스 이상이 될 수 있으며 동일한 추상화 계층의 밀접하게 결합 된 코드 사이에 인터페이스를 세우는 데 포인트가 없습니다. SOLID 원칙을 따르는 잘 설계된 응용 프로그램에서 모든 단일 클래스가 인터페이스를 구현해야하는 것은 아니며 모든 생성자가 항상 인터페이스를 매개 변수로만 사용해야하는 것은 아닙니다.
Voo

18
나는 유행어가 많고 실용적인 고려 사항에 대해 많은 토론을하지 않기 때문에 하향 투표했습니다. 두 개의 DB에 대한 저장소에 관한 것이 있지만 솔직히 말하면 실제 사례는 아닙니다.
jpmc26

5
@ jpmc26, BJoBnh이 대답을 묵살하지 않더라도 그 요점은 매우 명확하게 표현됩니다. "dependency inversion principal", "concrete reference"또는 "object initialize"가 유행어라고 생각하면 실제로는 그 의미를 모릅니다. 그것은 대답의 잘못이 아닙니다.
R. Schmitz

50

다른 객체를 여러 개 만들지 않고 단순히 새로운 인스턴스를 초기화하기 위해 생성자를 사용하는 것을 선호하지만 도우미 객체는 정상이며 무언가 내부 도우미인지 여부에 대한 판단을 사용해야합니다.

클래스가 컬렉션을 나타내는 경우 내부 도우미 배열 또는 목록 또는 해시 집합이있을 수 있습니다. new이러한 도우미를 만드는 데 사용 되며 매우 정상적인 것으로 간주됩니다. 이 수업은 다른 내부 도우미를 사용하기 위해 주사를 제공하지 않으며 그럴 이유가 없습니다. 이 경우 개체의 공용 메서드를 테스트하면 컬렉션의 요소가 누적, 제거 및 교체 될 수 있습니다.


어떤 의미에서, 프로그래밍 언어의 클래스 구성은 더 높은 수준의 추상화를 생성하기위한 메커니즘이며, 문제 영역과 프로그래밍 언어 프리미티브 사이의 간격을 메우기 위해 이러한 추상화를 만듭니다. 그러나 클래스 메커니즘은 도구 일뿐입니다. 프로그래밍 언어에 따라 다르며 일부 언어의 경우 일부 언어에서는 프로그래밍 언어 수준에서 여러 개체가 필요합니다.

요약하자면, 호출자가 단일 추상화로 표시되는 동안 추상화에 단순히 하나 이상의 내부 / 헬퍼 오브젝트가 필요한지 여부 또는 다른 오브젝트가 호출자에게 더 잘 노출되는지 여부를 판단해야합니다. 예를 들어, 호출자가 클래스를 사용할 때 이러한 다른 객체를 볼 때 제안되는 종속성 제어.


4
+1. 이것은 정확히 맞습니다. 클래스의 의도 된 동작을 식별해야하며 어떤 구현 세부 사항을 노출해야하고 그렇지 않은지를 결정해야합니다. 클래스의 "책임"이 무엇인지를 결정하는 미묘한 직관 게임이 있습니다.
jpmc26

27

모든 공동 작업자가 개별적으로 단위 테스트를 수행하는 데 관심이있는 것은 아니지만 호스팅 / 인스턴스화 클래스를 통해 간접적으로 테스트 할 수 있습니다. 이것은 특히 시험 후 시험을 할 때 각 클래스, 각 공개 방법 등을 테스트해야한다는 사람들의 생각과 일치하지 않을 수 있습니다. TDD를 사용할 때이 '협업 자'를 리팩터링하여 테스트 첫 번째 프로세스에서 이미 테스트중인 클래스를 추출 할 수 있습니다.


14
Not all collaborators are interesting enough to unit-test separatelyend of story :-),이 경우는 가능하며 아무도 감히 언급 할 수 없습니다. @Joppe 답변을 조금 더 설명해 보시기 바랍니다. 예를 들어, 단순한 구현 세부 사항 (대체에 적합하지 않음) 인 클래스의 예와 필요한 경우 후자의 추출 방법을 추가 할 수 있습니다.
Laiv

@Laiv 도메인 모델은 일반적으로 구체적이고 비 추상적입니다. 중첩 된 객체를 주입하지 않아도됩니다. 논리가없는 단순한 값 객체 / 복합 객체도 후보입니다.
Joppe

4
+1입니다. 파일 관련 작업을 수행 하기 위해 호출 해야하는 언어가 설정된 경우 new File()해당 호출을 금지하는 것은 의미가 없습니다. stdlib의 File모듈 에 대해 회귀 테스트를 작성하여 무엇을 하시겠습니까? 아마. 반면 new에, 스스로 발명 한 수업을 부르는 것이 더 모호합니다.
Kilian Foth

7
@KilianFoth : 하지만 직접 호출하는 모든 것을 테스트 하는 행운의 단위 new File().
Phoshi

1
그것은 추측 이다. 의미가없는 경우, 유용하지 않은 경우 및 의미가 있고 유용한 경우를 찾을 수 있습니다. 그것은 요구와 선호의 문제입니다.
Laiv

13

단위 테스트에 실제로 경험이 없기 때문에 먼저 배울 규칙을 모 으려고합니다.

겪어 본 적이없는 문제에 대해 "규칙"을주의해서 학습하십시오. "규칙"또는 "모범 사례"를 발견 한 경우이 규칙이 "추정 된"위치에 대한 간단한 장난감 예를 찾고 "규칙"의 내용을 무시하고 직접 문제를 해결하려고합니다 .

이 경우 2 개 또는 3 개의 간단한 클래스와 구현해야하는 일부 동작을 시도 할 수 있습니다. 자연스럽게 느껴지는 방식으로 수업을 실시하고 각 행동에 대한 단위 테스트를 작성하십시오. 예를 들어 일방적 인 방식으로 작업을 시작한 경우 나중에 돌아가서 나중에 변경해야하는 문제에 대한 목록을 작성하십시오. 상황이 어떻게 조화를 이루고 있는지 혼란스러워하는 경우 상용구 작성에 짜증이 난 경우; 기타

그런 다음 "규칙"에 따라 동일한 문제를 해결하십시오. 다시 한 번, 발생한 문제 목록을 작성하십시오. 목록을 비교하고 규칙을 따를 때 어떤 상황이 더 좋을지, 그렇지 않은지 생각하십시오.


귀하의 실제 질문에 관해서는, "핵심 논리"와 "서비스"를 구별 하는 포트 및 어댑터 접근 방식 을 선호하는 경향 이 있습니다 (이것은 순수한 기능과 효과적인 절차를 구별하는 것과 유사합니다).

핵심 논리는 문제 영역을 기반으로 응용 프로그램을 "내부"로 계산하는 것입니다. 이 같은 클래스를 포함 할 수 있습니다 User, Document, Order, Invoice, 등이의 벌금 핵심 클래스를 호출해야하는 new다른 핵심 클래스들은 이후있는 거 "내부"구현 세부 사항을. 예를 들어, 만드는 Order힘을 또한 생성 Invoice하고이 Document명령이 있었는지 자세히. 테스트하는 동안 실제로 모의 할 것이기 때문에 테스트 중에이를 조롱 할 필요가 없습니다!

포트와 어댑터는 핵심 로직이 외부 세계와 상호 작용하는 방식입니다. 상황이 좋아하는 곳이다 Database, ConfigFile, EmailSender, 등이 살고있다. 이것들은 테스트를 어렵게 만드는 것들이므로 코어 로직 외부 에서 생성 하고 필요에 따라 (종속성 주입 또는 메소드 인수 등) 전달하는 것이 좋습니다 .

이런 방식으로 데이터베이스, 파일, 이메일 등을 신경 쓸 필요없이 핵심 로직 (중요한 비즈니스 로직이 존재하고 가장 이탈이 많은 애플리케이션 특정 부분)을 자체적으로 테스트 할 수 있습니다. 예제 값을 전달하고 올바른 출력 값을 얻었는지 확인할 수 있습니다.

포트 및 어댑터는 비즈니스 로직을 신경 쓰지 않고 데이터베이스, 파일 시스템 등에 대한 모의를 사용하여 별도로 테스트 할 수 있습니다. 예제 값을 전달하여 저장 / 읽기 / 보내기 등을 할 수 있습니다. 적절하게.


6

여기서 핵심 요점으로 생각하는 것을 모아서 질문에 대답하도록하겠습니다. 간결함을 위해 일부 사용자를 인용하겠습니다.

항상 예외는 있지만이 규칙은 일반적으로 유효하며 생성자 외부에도 적용됩니다.

생성자에서 new를 사용하면 SOLIDD 를 위반합니다 (종속성 전환 원리). 단위 테스트는 모두 격리에 관한 것이므로 코드를 테스트하기가 어렵습니다. 구체적인 참조가 있으면 클래스를 분리하기가 어렵습니다.

-고양이와

예. new내부 생성자를 사용 하면 디자인 결함 (예 : 타이트 커플 링)이 발생하여 디자인이 엄격 해집니다. 테스트하기 어렵지만 불가능하지는 않습니다. 여기서 사용되는 속성은 복원력 (변경에 대한 허용)입니다 1 .

그럼에도 불구하고 위의 인용문이 항상 사실은 아닙니다. 경우 에 따라 밀접하게 결합 되는 클래스 가 있을 수 있습니다 . David Arno는 한 쌍의 댓글을 달았습니다.

물론 클래스가 불변 값 객체, 구현 세부 사항 등인 경우 예외가 있습니다. 클래스가 밀접하게 결합되어 있어야합니다 .

데이비드 아르노

바로 그거죠. 일부 클래스 (예 : 내부 클래스)는 단순한 기본 클래스의 구현 세부 정보 일 수 있습니다. 이것들은 메인 클래스와 함께 테스트하기위한 것이며 반드시 교체하거나 확장 할 필요는 없습니다.

또한 SOLID 컬트가 이러한 클래스를 추출하게하면 또 다른 좋은 원칙을 위반할 수 있습니다. 소위 데메테르의 법칙 . 반면에, 나는 디자인 관점에서 그것이 정말로 중요하다는 것을 알았습니다.

따라서 평소와 같이 가능한 대답은 달려 있습니다. 내부 생성자를 사용 new하는 것은 나쁜 습관이 될 수 있습니다. 그러나 항상 체계적으로는 아닙니다.

따라서 클래스가 기본 클래스의 구현 세부 정보 (대부분의 경우는 그렇지 않음) 인지 평가해야합니다 . 그렇다면, 그대로 두십시오. 그렇지 않은 경우 컴포지션 루트 또는 IoC 컨테이너의 종속성 주입 과 같은 기술을 고려하십시오 .


1 : SOLID의 주요 목표는 코드를 더 테스트 가능하게 만드는 것이 아닙니다. 코드가 변경에 더 잘 견딜 수 있도록하는 것입니다. 보다 유연하고 결과적으로 테스트하기 쉬움

참고 : David Arno, TheWhisperCat, 나는 당신이 인용 한 것을 신경 쓰지 않기를 바랍니다.


3

간단한 예로서 다음과 같은 의사 코드를 고려하십시오.

class Foo {
  private:
     class Bar {}
     class Baz inherits Bar {}
     Bar myBar
  public:
     Foo(bool useBaz) { if (useBaz) myBar = new Baz else myBar = new Bar; }
}

(가) 이후 new의 순수 구현 세부 사항이며 Foo, 두 Foo::Bar하고 Foo::Baz속한 Foo유닛 테스트하면 Foo부분 조롱 아무 소용이 없다 Foo. 단위 테스트시 에는 외부 부품 만 조롱하십시오 .FooFoo


-3

예, 응용 프로그램 루트 클래스에서 'new'를 사용하는 것은 코드 냄새입니다. 그것은 특정 구현을 사용하여 클래스를 잠그고 다른 것을 대체 할 수 없다는 것을 의미합니다. 항상 생성자에 종속성을 주입하도록 선택하십시오. 이렇게하면 테스트 중에 모의 종속성을 쉽게 주입 할 수있을뿐만 아니라 필요한 경우 다른 구현을 신속하게 대체 할 수있어 응용 프로그램을 훨씬 유연하게 만들 수 있습니다.

편집 : downvoters-여기에 가능한 코드 냄새로 '신규'를 표시하는 소프트웨어 개발 책에 대한 링크가 있습니다 : https://books.google.com/books?id=18SuDgAAQBAJ&lpg=PT169&dq=new%20keyword%20code%20smell&pg=PT169 # v = onepage & q = new % 20keyword % 20code % 20smell & f = false


15
Yes, using 'new' in your non-root classes is a code smell. It means you are locking the class into using a specific implementation, and will not be able to substitute another.이것이 왜 문제입니까? 의존성 트리의 모든 단일 의존성이 대체 될 수있는 것은 아닙니다.
Laiv

4
@Paul : 기본 구현은 기본으로 지정된 구체적 클래스에 대한 엄격한 참조를 의미합니다. 그렇다고 소위 "코드 냄새"는 아닙니다.
Robert Harvey

8
@EzoelaVacca : 어떤 상황에서도 "code smell"라는 단어를 사용하는 것에주의해야합니다. "공화당 원"또는 "민주당 원"이라고 말하는 것과 비슷합니다. 그 말의 의미는 무엇입니까? 이와 같은 레이블을 제공하자마자 실제 문제에 대한 생각을 멈추고 학습을 중단합니다.
Robert Harvey

4
"보다 융통성있는"것이 자동으로 "더 나은"것은 아닙니다.
whatsisname

4
@EzoelaVacca : new키워드를 사용 하는 것은 나쁜 습관이 아니며 결코 없었습니다. 그건 어떻게 당신이 문제에 그 도구를 사용합니다. 예를 들어, 볼 피닝 해머로 충분할 경우 망치를 사용하지 마십시오.
Robert Harvey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.