생성자에서 모든 객체의 작업을 수행해야하는 이유가 있습니까?


49

이 코드가 내 코드 나 동료의 코드가 아니라고 말함으로써이 서문을 시작하겠습니다. 몇 년 전 우리 회사의 규모가 더 좁 았을 때, 우리는 용량이 없었기 때문에 필요한 프로젝트를 수행하여 아웃소싱했습니다. 이제는 일반적으로 아웃소싱 또는 계약자에 대해 아무런 조치도 취하지 않았지만 이들이 생성 한 코드베이스는 대량의 WTF입니다. 즉, (주로) 효과가 있기 때문에 내가 본 아웃소싱 프로젝트의 상위 10 %에 있다고 가정합니다.

회사가 성장함에 따라 우리는 개발을 더 많이 집에서 시도했습니다. 이 특정 프로젝트는 내 무릎에 착륙하여 계속 진행하고 청소하고 테스트를 추가하는 등의 작업을 수행했습니다.

많이 반복되는 패턴이 하나 있는데, 이유가 있는지 궁금해서 그것을 보지 못할 정도로 놀라 울 정도로 끔찍한 것 같습니다. 패턴은 공용 메소드 나 멤버가없는 오브젝트이며 오브젝트의 모든 작업을 수행하는 공용 생성자 일뿐입니다.

예를 들어 (코드는 Java로되어 있지만 중요한 경우 더 일반적인 질문이기를 바랍니다).

public class Foo {
  private int bar;
  private String baz;

  public Foo(File f) {
    execute(f);
  }

  private void execute(File f) {
     // FTP the file to some hardcoded location, 
     // or parse the file and commit to the database, or whatever
  }
}

궁금한 점이 있다면이 유형의 코드는 종종 다음과 같은 방식으로 호출됩니다.

for(File f : someListOfFiles) {
   new Foo(f);
}

오래 전에 루프에서 인스턴스화 된 객체는 일반적으로 나쁜 생각이며 생성자가 최소한의 작업을 수행해야한다는 것을 오래 전에 배웠습니다. 이 코드를 보면 생성자를 삭제하고 execute공개 정적 메서드를 만드는 것이 더 나은 것처럼 보입니다 .

나는 계약자에게 왜 이런 식으로했는지 물어 보았으며, "원하는 경우 변경할 수 있습니다"라는 응답을 받았습니다. 정말 도움이되지 않았습니다.

어쨌든, 프로그래밍 언어로 이와 같은 일을해야 할 이유가 있습니까? 아니면 Daily WTF에 또 다른 제출입니까?


18
public static void main(string[] args)객체 에 대해 알고 알고 들어 본 개발자가 작성한 것으로 보아서 서로 뭉개려고했습니다.
앤디 헌트

다시 부름을받지 않으면 그곳에서해야합니다. 의존성 주입과 같은 많은 현대 프레임 워크는 Bean을 작성하고 속성을 설정하고 THEN을 호출해야 하므로이 무거운 작업자를 사용할 수 없습니다.

4
관련 질문 : 생성자에서 클래스의 주요 작업을 수행하는 데 문제가 있습니까? . 그러나 생성 된 인스턴스는 나중에 사용되므로 약간 다릅니다.
sleske


나는 이것을 완전한 답으로 확장하고 싶지는 않지만, 단지 이것이 가능할만한 시나리오가 있다고 말하겠습니다. 이것과는 거리멀다 . 여기서 객체는 실제로 객체를 필요로하지 않고 인스턴스화됩니다. 반면에 XML 파일에서 일종의 데이터 구조를 작성하는 경우 생성자에서 구문 분석을 시작하는 것이 전적으로 끔찍한 것은 아닙니다. ) 오버로드 생성자를 허용 언어로 사실, 그것은 아무것도 내가 테이너로 당신을 죽일 것 없다
back2dos

답변:


60

좋아, 아래로 내려 가서

루프에서 인스턴스화 된 객체는 일반적으로 나쁜 생각이라고 오래 전에 배웠습니다.

내가 사용한 언어는 없습니다.

C에서는 변수를 미리 선언하는 것이 좋지만, 말한 것과는 다릅니다. 루프 위의 객체를 선언하고 재사용하면 속도 가 약간 빨라지지만 속도의 증가가 의미가없는 언어가 많이 있습니다 (아마도 일부 컴파일러는 최적화를 수행합니다).

일반적으로 루프 내에 객체가 필요한 경우 객체를 만듭니다.

생성자는 최소한의 작업을 수행해야합니다

생성자는 객체의 필드를 인스턴스화하고 객체를 사용할 준비를하는 데 필요한 다른 초기화를 수행해야합니다. 이것은 일반적으로 생성자가 작다는 것을 의미하지만 상당한 양의 작업이 필요한 시나리오가 있습니다.

프로그래밍 언어로 이와 같은 일을 할 이유가 있습니까? 아니면 Daily WTF에 또 다른 제출입니까?

Daily WTF에 제출 한 것입니다. 물론 코드로 할 수있는 일이 더 나쁩니다. 문제는 저자가 클래스가 무엇인지, 어떻게 사용하는지에 대한 광범위한 오해가 있다는 것입니다. 구체적으로, 다음은이 코드에서 잘못된 점입니다.

  • 클래스 오용 : 클래스는 기본적으로 함수처럼 작동합니다. 언급했듯이 정적 함수로 대체되거나 함수를 호출하는 클래스에서 함수가 구현되어야합니다. 그것이 무엇을하고 어디에 사용되는지에 달려 있습니다.
  • 성능 오버 헤드 : 언어에 따라 객체를 만드는 것이 함수를 호출하는 것보다 느릴 수 있습니다.
  • 일반적인 혼동 : 일반적으로 프로그래머가이 코드를 사용하는 방법을 혼동합니다. 그것이 사용되는 것을 보지 않으면, 아무도 저자가 문제의 코드를 어떻게 사용하려고했는지 알 수 없었습니다.

답변 주셔서 감사합니다. "루프에서 객체 인스턴스화"와 관련하여 이것은 많은 정적 분석 도구에 대해 경고하는 것이며 일부 대학 교수로부터도 들었습니다. 물론, 그것은 성능 저하가 더 클 때 90 년대보다 오래 지속될 수 있습니다. 물론 필요하다면,하지만 반대 의견을 듣고 놀랍니다. 내 시야를 넓혀 주셔서 감사합니다.
Kane

8
루프에서 객체를 인스턴스화하는 것은 사소한 코드 냄새입니다. "이 객체를 여러 번 인스턴스화해야합니까?"라고 물어야합니다. 동일한 데이터로 동일한 객체를 인스턴스화하고 동일한 작업을 수행하도록 지시하면 사이클을 한 번 인스턴스화 한 다음 사이클을 저장하고 (스 래싱 메모리 사용을 피함) 객체 상태에 필요한 증분 변경이있는 명령 만 반복하면됩니다. . 그러나 예를 들어 DB 또는 기타 입력에서 데이터 스트림을 읽고 해당 데이터를 보유 할 일련의 오브젝트를 작성하는 경우 반드시 인스턴스화하십시오.
KeithS

3
짧은 버전 : 함수처럼 객체 인스턴스화를 사용하지 마십시오.
Erik Reppen

27

나에게있어, 생성자에서 많은 작업을 수행하는 객체를 얻을 때 약간의 놀라움이 있습니다.

좋은 예를 시도 :

이 사용법이 안티 패턴인지 확실하지 않지만 C #에서는 (어떤 예제를 구성한) 라인을 따르는 코드를 보았습니다.

using (ImpersonationContext administrativeContext = new ImpersonationContext(ADMIN_USER))
{
    // Perform actions here under the administrator's security context
}
// We're back to "normal" privileges here

이 경우 ImpersonationContext클래스는 생성자와 Dispose 메서드에서 모든 작업을 수행합니다. C # using문은 C # 개발자가 잘 이해하고 있으므로 using예외가 발생하더라도 구문에서 벗어나면 생성자에서 발생하는 설정이 롤백되도록 합니다. 아마 내가 이것을 "좋은"것으로 생각할지 모르겠지만, 이것은 내가 이것에 대해 본 가장 좋은 사용법 일 것입니다 (즉, 생성자에서 "작업"). 이 경우 상당히 자명하고 기능적이며 간결합니다.

우수하고 유사한 패턴의 예는 명령 pattern 입니다. 생성자에서 논리를 확실히 실행하지는 않지만 전체 클래스가 메소드 호출과 동일하다는 점에서 비슷합니다 (메소드를 호출하는 데 필요한 매개 변수 포함). 이런 식으로 메소드 호출 정보를 나중에 사용하기 위해 직렬화하거나 전달할 수있어 매우 유용합니다. 아마도 당신의 계약자들은 비슷한 것을 시도하고 있었을 것입니다.

귀하의 예에 대한 의견 :

나는 그것이 매우 명확한 반 패턴이라고 말할 것입니다. 그것은 아무것도 추가하지 않고, 예상 한 것에 반하여, 생성자에서 지나치게 긴 처리를 수행합니다 (생성자에서의 FTP? WTF는 실제로 사람들이 이런 방식으로 웹 서비스를 호출하는 것으로 알려져 있지만).

나는 인용문을 찾기 위해 고심하고 있지만 Grady Booch의 책 "Object Solutions"는 C 개발자 팀이 C ++로 전환하는 팀에 대한 일화를 가지고 있습니다. 분명히 그들은 훈련을 겪었고 경영진에 의해 "객체 지향"을해야한다는 말을 들었다. 무엇이 잘못되었는지 알아 내기 위해 저자는 코드 메트릭 도구를 사용했으며 클래스 당 평균 메서드 수는 정확히 1이고 "Do It"이라는 구문의 변형이라는 것을 알았습니다. 필자는이 특정 문제를 전에는 본 적이 없지만, 사람들이 실제로 객체 지향 코드를 작성하는 방법과 그 이유를 이해하지 못한 채 증상이 나타날 수 있음을 분명히 말해야한다.


1
좋은 예는 Resource Allocation is Initialization 입니다. 예외 안전 코드를 훨씬 쉽게 작성할 수 있으므로 C ++에서 권장되는 패턴입니다. 그래도 C #으로 전달되는지 여부는 알 수 없습니다. (이 경우 할당되는 "자원"은 관리 권한입니다.)
zwol

@ Zack C ++에서 RAII를 알고 있었지만 그런 용어로는 생각하지 못했습니다. C # 세계에서는이를 일회용 패턴이라고하며, 일반적으로 관리되지 않는 리소스가있는 제품은 수명이 다한 후에 해제하는 것이 좋습니다. 이 예제와의 주요 차이점은 일반적으로 생성자에서 값 비싼 연산자를 수행하지 않는다는 것 입니다. 예를 들어 생성자 다음에 SqlConnection의 Open () 메서드를 계속 호출해야합니다.
Daniel B

14

아니.

모든 작업이 생성자에서 수행되면 처음에는 객체가 필요하지 않았 음을 의미합니다. 아마도 계약자는 모듈화를 위해 객체를 사용했을 것입니다. 이 경우 정적 메서드가있는 인스턴스화되지 않은 클래스는 불필요한 객체 인스턴스를 만들지 않고도 동일한 이점을 얻습니다.

객체 생성자에서 모든 작업을 수행 할 수있는 경우는 한 가지뿐입니다. 즉, 객체의 목적이 작업을 수행하기보다는 장기적으로 고유 한 정체성을 나타 내기위한 것입니다. 그러한 경우, 의미있는 작업을 수행하는 것 이외의 이유로 오브젝트를 보관합니다. 예를 들어 싱글 톤 인스턴스입니다.


7

필자는 생성자에서 무언가를 계산하기 위해 많은 작업을 수행하는 많은 클래스를 만들었지 만 객체는 변경할 수 없으므로 속성을 통해 해당 계산 결과를 사용할 수 있으며 다른 작업은 수행하지 않습니다. 그것은 정상적인 불변성입니다.

이상한 점은 부작용이있는 코드를 생성자에 넣는 것입니다. 객체를 만들고 속성이나 메소드에 액세스하지 않고 버리는 것은 이상하게 보입니다 (그러나이 경우 적어도 다른 일이 진행되고 있음이 분명합니다).

함수가 공용 메소드로 이동 한 다음 클래스의 인스턴스가 주입 된 후 for 루프가 메소드를 반복적으로 호출하면 더 좋습니다.


4

생성자에서 모든 객체의 작업을 수행해야하는 이유가 있습니까?

아니.

  • 생성자는 부작용이 없어야합니다.
    • 개인 필드 초기화 이상의 것은 부작용으로 간주해야합니다.
    • 부작용이있는 생성자는 SRP (Single-Responsibilty-Principle)를 중단하고 OOP (Object-Oriented-Programming) 정신과 상반됩니다.
  • 생성자는 가벼워 야하며 절대 실패해서는 안됩니다.
    • 예를 들어, 생성자 내부에 try-catch 블록이 표시되면 항상 떨립니다. 생성자는 예외 나 로그 오류를 발생시키지 않아야합니다.

이 가이드 라인을 합리적으로 질문하고 "하지만이 규칙을 따르지 않고 코드가 제대로 작동합니다!"라고 말할 수 있습니다. 나는 "그렇지 않을 때까지는 사실일지도 모른다"고 대답 할 것이다.

  • 생성자 내부의 예외 및 오류는 예상치 못한 결과입니다. 그들이 그렇게하지 않으면, 미래의 프로그래머들은 이러한 생성자 호출을 방어 코드로 둘러싸 지 않을 것입니다.
  • 생산에 문제가 있으면 생성 된 스택 추적을 구문 분석하기 어려울 수 있습니다. 스택 추적의 상단은 생성자 호출을 가리킬 수 있지만 생성자에서 많은 일이 발생하지만 실패한 실제 LOC를 가리 키지 않을 수 있습니다.
    • 이것이 사실 인 많은 .NET 스택 추적을 구문 분석했습니다.

1
"그들에게 그렇게하지 않으면, 미래의 프로그래머들은 이러한 생성자 호출을 방어 코드로 둘러싸 지 않을 것입니다." - 좋은 지적.
Graham

2

이 일을하는 사람은 일급 시민으로서 기능을 전달할 수없는 Java의 한계를 해킹하려고 한 것 같습니다. 함수를 전달해야 할 때마다 비슷 apply하거나 유사한 메소드를 사용하여 클래스 안에 래핑해야 합니다.

그래서 그는 바로 가기를 사용하고 함수 대체로 클래스를 직접 사용했다고 생각합니다. 함수를 실행할 때마다 클래스를 인스턴스화하면됩니다.

적어도 우리가 여기에서 알아 내려고하기 때문에 확실히 좋은 패턴은 아니지만 Java에서 함수형 프로그래밍과 비슷한 것을 수행하는 가장 장황한 방법 일 수 있습니다.


3
이 프로그래머가 '기능적 프로그래밍'이라는 문구를 들어 본 적이 없다고 확신합니다.
Kane

프로그래머가 클로저 추상화를 만들려고 생각하지 않았다. Java 동등 물에는 추상 메소드 (가능한 계획) 다형성이 필요합니다. 그 증거는 없습니다.
Kevin A. Naudé 2012 년

1

나를 위해 경험상 규칙은 멤버 변수 초기화를 제외하고 생성자에서 아무것도하지 않습니다. 첫 번째 이유는 SRP 원칙을 따르는 데 도움이되기 때문입니다. 일반적으로 긴 초기화 프로세스는 클래스가 수행하는 것보다 많은 작업을 수행하고 클래스 외부에서 초기화를 수행해야한다는 표시입니다. 두 번째 이유는이 방법으로 필요한 매개 변수 만 전달되므로 결합 된 코드가 적기 때문입니다. 초기화가 복잡한 생성자는 일반적으로 다른 객체를 구성하는 데만 사용되지만 원래 클래스에서는 사용하지 않는 매개 변수가 필요합니다.


1

여기서는 조류에 반대 할 것입니다. 모든 것이 어떤 클래스에 있어야하고 정적 클래스를 가질 수없는 언어에서는 격리 된 기능이 필요한 상황에 처할 수 있습니다 어딘가에 넣고 다른 것과 맞지 않습니다. 선택은 관련없는 다양한 기능 비트를 다루는 유틸리티 클래스이거나 기본적으로이 하나만 수행하는 클래스입니다.

이와 같은 비트가 하나만 있으면 정적 클래스가 특정 클래스입니다. 따라서 모든 작업을 수행하는 생성자 또는 모든 작업을 수행하는 단일 함수가 있습니다. 생성자에서 모든 작업을 수행하는 이점은 한 번만 발생하므로 재사용 또는 스레드에 대한 걱정없이 개인 필드, 속성 및 메서드를 사용할 수 있다는 것입니다. 생성자 / 메소드가있는 경우 매개 변수가 단일 메소드로 전달되도록 유혹을받을 수 있지만, 잠재적 인 스레딩 문제를 유발하는 개인 필드 또는 특성을 사용하는 경우.

유틸리티 클래스를 사용하더라도 대부분의 사람들은 정적 클래스가 중첩되어 있다고 생각하지 않으며 언어에 따라 가능하지 않을 수도 있습니다.

기본적으로 이것은 언어가 허용하는 범위 내에서 시스템의 나머지 부분에서 동작을 분리하는 동시에 비교적 많은 언어를 활용할 수있는 비교적 이해하기 쉬운 방법입니다.

솔직히 나는 계약자가 한 것처럼 많은 답변을 받았을 것입니다. 두 번의 전화로 바꾸는 것은 약간의 조정입니다. 하나를 다른 것으로 추천하는 것이 많지는 않습니다. 어떤 dis / advatanges가 있는지, 아마도 실제보다 더 가설적일 수 있습니다. 왜 중요하지 않을 때 결정을 정당화하려고 시도하고 아마도 기본 조치만큼 그렇게 결정되지 않았습니까?


1

파이썬과 같이 함수와 클래스가 훨씬 같은 언어에서 공통적 인 패턴이 있습니다. 파이썬과 같이 기본적으로 너무 많은 부작용이 있거나 여러 개의 명명 된 객체가 반환되는 함수로 객체를 사용합니다.

이 경우, 객체 자체가 단일 작업 묶음 주위의 래퍼이기 때문에 모든 작업이 생성자에서 수행되지는 않지만 벌크는 별도의 함수에 넣을 이유가 없습니다. 같은 것

var parser = XMLParser()
var x = parser.parse(string)
string a = x.LookupTag(s)

정말 ~보다 낫다

 var x = ParsedXML(string)
 string a = x.LookupTag(s)

부작용을 직접 직시하지 않고 오브젝트를 호출하여 큐에 부작용을 적용하여 더 나은 가시성을 제공 할 수 있습니다. 물체에 나쁜 부작용이 생기면 모든 일을 한 다음 나쁘게 영향을 미치고 그 물체에 손상을 줄 수 있습니다. 객체를 식별하고 이러한 방식으로 호출을 분리하는 것은 여러 객체를 함수에 한 번에 전달하는 것보다 명확합니다.

x.UpdateView(v)

개체의 접근자를 통해 여러 반환 값을 만들 수 있습니다. 모든 작업이 완료되었지만 상황에 맞는 것을 원하며 실제로 여러 함수를 내 함수에 전달하고 싶지 않습니다.

var x = new ComputeStatistics(inputFile)
int max = x.MaxOptions;
int mean = x.AverageOption;
int sd = x.StandardDeviation;
int k = x.Kurtosis;
...

그래서 공동 Python / C # 프로그래머로서, 이것이 나에게 이상하게 보이지는 않습니다.


주어진 예가 오해의 소지가 있음을 분명히해야합니다. 접근자가없고 반환 값이없는 예에서 이것은 아마도 흔적 일 것입니다. 아마도 원래 제공된 디버그 리턴 또는 무언가입니다.
Jon Jay Obermark

1

생성자 / 소멸자가 모든 작업을 수행하는 경우가 하나 있습니다.

자물쇠. 일반적으로 잠금 수명 동안에는 잠금과 상호 작용하지 않습니다. C #의 아키텍처는 소멸자를 명시 적으로 언급하지 않고 이러한 경우를 처리하는 데 유용합니다. 그러나 모든 잠금이 이런 식으로 구현되는 것은 아닙니다.

또한 런타임 라이브러리 버그를 패치하기 위해 생성자 전용 코드 비트를 작성했습니다. (실제로 코드를 수정하면 다른 문제가 발생했을 수 있습니다.) 패치를 취소 할 필요가 없으므로 소멸자가 없습니다.


-1

AndyBursh에 동의합니다. 지식 문제가 부족한 것 같습니다.

그러나 NHibernate와 같은 프레임 워크는 생성자에서 코딩해야합니다 (지도 클래스를 만들 때). 그러나 이것은 프레임 워크의 제한이 아니며 필요한 것입니다. 리플렉션과 관련하여 생성자는 항상 존재하는 유일한 메소드이며 (비공개 일 수도 있지만) 클래스의 메소드를 동적으로 호출 해야하는 경우 도움이 될 수 있습니다.


-4

"오래 전에 루프에서 인스턴스화 된 객체는 일반적으로 나쁜 생각이라고 배웠습니다."

그렇습니다. 왜 우리가 StringBuilder가 필요한지 기억하십니까?

Java에는 두 개의 학교가 있습니다.

빌더는 첫 번째 빌더에 의해 승격되었으며, 공유를 통해 정체성을 효율성으로 나누기 때문에 재사용을 가르쳐줍니다. 그러나 2000 년 초에 Java로 객체를 만드는 것이 너무 저렴하고 (C ++과는 대조적으로) GC가 너무 효율적으로 재사용이 무가치하며 중요한 것은 프로그래머의 생산성이라는 점을 읽기 시작했습니다. 생산성을 위해서는 관리 가능한 코드를 작성해야합니다. 이는 모듈화 (일명 스코프 축소)를 의미합니다. 그래서,

이것은 나쁘다

byte[] array = new byte[kilos or megas]
for_loop:
  use(array)

이것은 좋다.

for_loop:
  byte[] array = new byte[kilos or megas]
  use(array)

forum.java.sun이 존재할 때이 질문을했고 사람들은 배열을 가능한 한 좁게 선언하는 것을 선호한다는 데 동의했습니다.

현대 자바 프로그래머는 결코 새로운 클래스를 선언하거나 객체를 만드는 것을 주저하지 않습니다. 그는 그렇게하는 것이 좋습니다. Java는 "모든 것이 객체"이고 저렴한 제작이라는 아이디어로 그렇게하는 모든 이유를 제공합니다.

또한 현대 엔터프라이즈 프로그래밍 스타일은 작업을 실행해야 할 때 간단한 작업을 실행할 특수 클래스 (또는 더 나은 프레임 워크)를 만듭니다. 여기에 도움이되는 좋은 Java IDE. 그들은 호흡으로 많은 쓰레기를 자동으로 생성합니다.

따라서 객체에 Builder 또는 Factory 패턴이 없다고 생각합니다. 생성자별로 인스턴스를 직접 생성하는 것은 괜찮습니다. 모든 객체에 대한 특별 팩토리 클래스가 권장됩니다.

이제 함수형 프로그래밍이 시작되면 객체를 만드는 것이 훨씬 간단 해집니다. 이를 인식하지 않고도 모든 단계에서 호흡으로 객체를 생성합니다. 따라서 새로운 시대에 코드가 더욱 "효율적"이 될 것으로 기대합니다.

"생성자에서 아무 것도하지 마십시오. 초기화 전용입니다."

개인적으로, 나는 지침서에 이유가 없다고 생각합니다. 나는 그것을 또 다른 방법으로 생각합니다. 코드를 추출하는 것은 공유 할 수있는 경우에만 필요합니다.


3
설명은 잘 구성되어 있지 않으며 따르기가 어렵습니다. 당신은 상황에 대한 링크가없는 논쟁의 여지가있는 주장으로 사방을 돌아갑니다.
새로운 Alexandria

실제로 논쟁의 여지가있는 말은 "생성자가 객체의 필드를 인스턴스화하고 객체를 사용할 준비를하는 데 필요한 다른 초기화를 수행해야합니다"입니다. 가장 인기있는 기고자에게 알리십시오.
Val
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.