생성자에서 while (true) 루프가 실제로 나쁜 이유는 무엇입니까?


47

일반적인 질문이지만 C ++과 같은 언어에는 생성자 실행, 메모리 관리, 정의되지 않은 동작 등과 관련하여 다른 의미가 있다는 것을 알고 있으므로 내 범위는 C #입니다.

누군가 나에게 쉽게 대답하지 못하는 흥미로운 질문을했다.

클래스의 생성자가 결코 끝나지 않는 루프 (예 : 게임 루프)를 시작하게하는 나쁜 디자인으로 간주되는 이유는 무엇입니까?

이것에 의해 깨지는 몇 가지 개념이 있습니다.

  • 최소 놀랍게도의 원리와 같이, 사용자는 생성자가 이런 식으로 행동 할 것을 기대하지 않습니다.
  • 루프를 종료하지 않으므로이 클래스를 만들거나 주입 할 수 없으므로 단위 테스트가 더 어렵습니다.
  • 그런 다음 루프의 끝 (게임 끝)은 개념적으로 생성자가 끝나는 시간이며 홀수입니다.
  • 기술적으로 이러한 클래스에는 생성자를 제외한 공용 멤버가 없으므로 이해하기가 어렵습니다 (특히 구현할 수없는 언어의 경우)

그리고 기술적 인 문제가 있습니다 :

  • 생성자는 실제로 완료되지 않으므로 GC는 어떻게됩니까? 이 개체가 이미 Gen 0에 있습니까?
  • 이러한 클래스에서 파생하는 것은 기본 생성자가 절대로 반환하지 않기 때문에 불가능하거나 최소한 매우 복잡합니다.

그러한 접근 방식으로 더 분명하거나 나쁜 것이 있습니까?


61
왜 좋은가요? 메인 루프를 메소드 (매우 간단한 리팩토링)로 옮기면 사용자는 다음과 같이 예기치 않은 코드를 작성할 수 있습니다.var g = new Game {...}; g.MainLoop();
Brandin

61
귀하의 질문에는 이미 사용 하지 않는 6 가지 이유가 있으며, 하나의 찬성으로 하나를보고 싶습니다. while(true)속성 setter에 루프 를 넣는 것이 왜 나쁜 생각인지 물을 수도 있습니다 new Game().RunNow = true.
Groo

38
극단적 인 비유 : 왜 우리는 히틀러가 잘못했다고 말합니까? 인종 차별을 제외하고, 제 2 차 세계 대전을 시작하고, 수백만의 사람들을 죽이고, 50 가지 이상의 다른 이유로 폭력 등 그는 아무 잘못도 없었습니다. 왜 어떤 일이 잘못되었는지에 대한 임의의 이유 목록을 제거하면 문자 그대로 어떤 것이 든 그 일이 좋다고 결론을 내릴 수 있습니다.
Bakuriu

4
가비지 수집 (GC) (기술적 인 문제)과 관련하여 특별한 것은 없습니다. 인스턴스는 생성자 코드가 실제로 실행되기 전에 존재합니다. 이 경우 인스턴스에 계속 도달 할 수 있으므로 GC에서 소유권을 주장 할 수 없습니다. GC가 설정되면이 인스턴스는 그대로 유지되고 GC 설정이 발생할 때마다 일반적인 규칙에 따라 객체가 다음 세대로 승격됩니다. GC 발생 여부는 새 객체 생성 여부에 따라 달라집니다.
Jeppe Stig Nielsen

8
한 걸음 더 나아가서 while (true)이 사용되는 위치에 관계없이 나쁘다고 말합니다. 루프를 중지하는 명확하고 명확한 방법이 없음을 의미합니다. 귀하의 예에서 게임이 종료 될 때 루프가 멈추지 않아야합니까, 아니면 초점을 잃지 않아야합니까?
Jon Anderson

답변:


250

생성자의 목적은 무엇입니까? 새로 생성 된 객체를 반환합니다. 무한 루프는 무엇을합니까? 절대 반환하지 않습니다. 생성자가 전혀 생성하지 않으면 새로 생성 된 객체를 어떻게 반환 할 수 있습니까? 할 수 없습니다.

Ergo, 무한 루프는 생성자의 기본 계약을 위반합니다.


11
아마도 그들은 중간에 휴식을 취하면서 while (참)을하려고 했습니까? 위험하지만 합법적입니다.
John Dvorak

2
동의합니다. 적어도 그렇습니다. 학문적 인 관점에서. 무언가를 반환하는 모든 함수에 대해 동일하게 유지됩니다.
Doc Brown

61
이 클래스의 하위 클래스를 만드는 가난한 잔디를 상상해보십시오.
Newtopian

5
@ JanDvorak : 글쎄, 그건 다른 질문입니다. OP는 명시 적으로 "결국 없음"을 지정하고 이벤트 루프를 언급했습니다.
Jörg W Mittag

4
그렇다면 @ JörgWMittag는 생성자에 넣지 마십시오.
John Dvorak

45

그러한 접근 방식으로 더 분명하거나 나쁜 것이 있습니까?

물론입니다. 불필요하고, 예상치 못한, 쓸모없고, 우아하지 않습니다. 그것은 클래스 디자인 (응집력, 결합)의 현대 개념을 위반합니다. 메소드 계약을 위반합니다 (생성자가 정의 된 작업을 가지고 있으며 임의의 메소드가 아닙니다). 확실히 유지 보수가 쉽지는 않지만 미래의 프로그래머는 무슨 일이 일어나고 있는지 이해하고 그 이유를 추측하려고 많은 시간을 할애합니다.

코드가 작동하지 않는다는 점에서 이것은 "버그"가 아닙니다. 그러나 코드를 유지하기 어렵게 (즉, 테스트를 추가하기 어렵고, 재사용하기 어렵고, 디버깅하기 어렵고, 확장하기 어렵게하여) 장기적으로 막대한 2 차 비용 (코드를 처음 작성하는 비용과 관련하여)이 발생할 수 있습니다. ).

소프트웨어 개발 방법의 많은 / 가장 현대적인 개선은 소프트웨어의 실제 작성 / 테스트 / 디버깅 / 유지 보수 프로세스를보다 쉽게하기 위해 수행됩니다. 이 모든 것은 "작동"하기 때문에 코드가 무작위로 배치되는 다음과 같은 것들로 우회됩니다.

불행히도, 당신은 정기적 으로이 모든 것을 완전히 모르는 프로그래머를 만날 것입니다. 작동합니다.

유추로 마무리하려면 (다른 프로그래밍 언어, 여기서 당면한 문제는 계산하는 것입니다 2+2) :

$sum_a = `bash -c "echo $((2+2)) 2>/dev/null"`;   # calculate
chomp $sum_a;                         # remove trailing \n
$sum_a = $sum_a + 0;                  # force it to be a number in case some non-digit characters managed to sneak in

$sum_b = 2+2;

첫 번째 접근 방식에 어떤 문제가 있습니까? 상당히 짧은 시간이 지나면 4를 반환합니다. 맞습니다. 반대 의견 (개발자가 반박 할 수있는 모든 일반적인 이유와 함께)은 다음과 같습니다.

  • 읽기가 어렵습니다 (그러나 좋은 프로그래머는 쉽게 읽을 수 있으며, 항상 이렇게 읽었습니다. 원한다면 메소드로 리팩터링 할 수 있습니다!)
  • 느리게 (그러나 이것은 중요한 시점에 있지 않으므로 무시할 수 있으며, 필요할 때만 최적화하는 것이 가장 좋습니다!)
  • 훨씬 더 많은 리소스를 사용합니다 (새로운 프로세스, 더 많은 RAM 등-그러나 서버는 충분히 빠릅니다)
  • 에 의존성을 소개합니다 bash(그러나 어쨌든 Windows, Mac 또는 Android에서는 실행되지 않습니다)
  • 그리고 위에서 언급 한 다른 모든 이유들.

5
"생성자를 실행하는 동안 GC가 예외적 인 잠금 상태 일 수 있습니다"-왜? 할당자는 먼저 할당 한 다음 일반적인 방법으로 생성자를 시작합니다. 그것에 대해 특별한 것은 없습니다. 있습니까? 그리고 할당자는 어쨌든 생성자 내에서 새로운 할당을 허용해야합니다 (그리고 이것들은 OOM 상황을 유발할 수 있습니다).
John Dvorak

1
@ JanDvorak, 생성자에있는 동안 "어딘가에"특별한 행동 이있을 있다는 점을 지적하고 싶었지만 정확합니다. 선택한 예는 비현실적입니다. 제거되었습니다.
AnoE

나는 당신의 설명을 정말로 좋아하며 두 답장을 모두 답변으로 표시하고 싶습니다 (적어도 공감해야 함). 비유는 훌륭하고 2 차 비용에 대한 생각과 설명의 흔적도 마찬가지이지만 코드의 보조 요구 사항으로 간주합니다 (내 주장과 동일). 최신 기술은 코드를 테스트하는 것이므로 테스트 가능성 등은 사실상의 요구 사항입니다. 그러나 @ JörgWMittag의 진술 fundamental contract of a constructor: to construct something은 바로 자리에 있습니다.
사무엘

비유가 그렇게 좋지 않습니다. 내가 이것을 보면, 왜 Bash를 사용하여 수학을하는지에 대한 의견이 나올 것입니다. 그 이유에 따라 완전히 괜찮을 것입니다. 기본적으로 생성자 "// 참고를 사용하는 모든 곳에서 주석에 사과를 넣어야하기 때문에 OP에서도 동일하게 작동하지 않습니다.
Brandin

4
"안타깝게도, 당신은이 모든 것을 완전히 모르는 프로그래머들을 정기적으로 만나게 될 것입니다. 그것이 효과가 있습니다." 너무 슬프지만 사실입니다. 나는 우리의 직업 생활에서 유일한 상당한 부담이 바로 그 사람들이라고 말할 때 우리 모두를 위해 말할 수 있다고 생각합니다.
Monica와의 가벼움 경주

8

당신은 당신의 질문에이 접근법을 배제하기에 충분한 이유를 제시하지만 여기서 실제적인 질문은 "이러한 접근 방식에 더 분명하거나 나쁜 것이 있습니까?"입니다.

내 첫 번째는 이것이 무의미하다는 것입니다. 생성자가 완료되지 않으면 프로그램의 다른 부분에서는 생성 된 객체에 대한 참조를 얻을 수 없으므로 일반 메소드 대신 생성자에 배치하는 논리는 무엇입니까? 그런 다음 유일한 차이점은 루프에서 루프에서 부분적으로 구성된 this이스케이프에 대한 참조를 허용 할 수 있다는 것 입니다. 이것이이 상황에만 국한되는 것은 아니지만, this탈출을 허용 하면 해당 참조는 항상 완전히 구성되지 않은 객체를 가리킬 것입니다.

이런 종류의 상황에 대한 의미가 C #에서 잘 정의되어 있는지 여부는 알 수 없지만 대부분의 개발자가 탐구하고자하는 것이 아니기 때문에 중요하지 않다고 주장합니다.


나는 생성자가 객체가 생성 된 후에 실행되어 정상적인 언어 유출로 완벽하게 정의 된 행동이어야한다고 가정합니다.
mroman

@mroman : this생성자에서 누출 될 수도 있지만 가장 파생 된 클래스의 생성자에있는 경우에만 가능합니다. 당신이 두 개의 클래스가있는 경우 AB함께 B : A생성자가있는 경우, A누출 것 this,의 구성원 B아직 초기화되지 않은 것 (그리고 가상 메서드 호출하거나 캐스트를 B그 기반으로 위력을 과시 할 수 있습니다).
hoffmale

1
C ++에서는 미쳤을 것 같습니다. 다른 언어에서 멤버는 실제 값이 할당되기 전에 기본값을 수신하므로 실제로 그러한 코드를 작성하는 것은 어리석은 동작이지만 여전히 동작이 잘 정의되어 있습니다. 이러한 멤버를 사용하는 메소드를 호출하면 NullPointerExceptions 또는 잘못된 계산 (숫자가 기본값 0이므로) 또는 이와 유사한 결과가 발생할 수 있지만 기술적으로 결정적인 일이 발생합니다.
mroman

@mroman CLR에 대한 명확한 참조를 찾을 수 있습니까?
JimmyJames

생성자가 실행되기 전에 멤버 값을 할당하여 생성자가 실행되기 전에 멤버에 기본값이 할당되거나 값이 할당 된 개체가 유효한 상태로 존재하도록 할 수 있습니다. 멤버를 초기화하기 위해 생성자가 필요하지 않습니다. 문서 어딘가에서 찾을 수 있는지 확인하겠습니다.
mroman

1

+1 놀랍게도, 이것은 새로운 개발자들에게 표현하기 어려운 개념입니다. 실용적인 수준에서는 생성자 내에서 발생한 예외를 디버깅하기가 어렵습니다. 개체가 초기화되지 않으면 해당 생성자 외부의 상태를 검사하거나 로그에서 기록 할 수 없습니다.

이런 종류의 코드 패턴이 필요하다고 생각되면 대신 클래스에서 정적 메소드를 사용하십시오.

클래스 정의에서 객체를 인스턴스화 할 때 초기화 로직을 제공하기 위해 생성자가 존재합니다. 이 개체 인스턴스는 나머지 응용 프로그램 논리에서 호출하려는 속성 및 기능 집합을 캡슐화하는 컨테이너로 구성되므로이 개체 인스턴스를 생성합니다.

"구축하고있는"객체를 사용하지 않으려면 먼저 객체를 인스턴스화하는 시점은 무엇입니까? 생성자에 while (true) 루프가 있다는 것은 효과적으로 완료 할 의도가 없다는 것을 의미합니다.

C #은 다양한 구조와 패러다임을 가진 매우 풍부한 객체 지향 언어로 도구를 탐색하고 사용하는 방법과 사용시기를 알 수 있습니다.

C #에서는 생성자 내에서 확장되거나 끝없는 논리를 실행하지 마십시오. 왜냐하면 더 나은 대안이 있기 때문입니다.


1
포인트를 만든 전에 9 개 답변에서 설명을 통해 자신의 상당한 아무것도 제공하지 않는 것
모기

1
야, 나는 그것을 가야했다 :) 나는 이것을하는 것에 대한 규칙이 없지만, 당신은 더 간단한 방법이 있다는 사실 때문에해서는 안된다는 것을 강조하는 것이 중요하다고 생각했다. 대부분의 다른 응답은 왜 나쁜지에 대한 기술에 중점을 두지 만, "컴파일되어 있으므로 그대로 둘 수 있습니다"라는 새로운 개발자에게 판매하기는 어렵습니다.
Chris Schaller

0

본질적으로 나쁜 것은 없습니다. 그러나 중요한 것은이 구문을 사용하기로 선택한 이유에 대한 질문입니다. 이렇게함으로써 무엇을 얻었습니까?

먼저 while(true)생성자에서의 사용 에 대해서는 이야기하지 않겠습니다 . 없다 절대적으로 생성자에서이 구문을 사용하여 잘못된 것은. 그러나 "생성자에서 게임 루프 시작"이라는 개념은 몇 가지 문제를 일으킬 수 있습니다.

이러한 상황에서 생성자가 단순히 객체를 구성하고 무한 루프를 호출하는 함수를 갖는 것이 매우 일반적입니다. 이렇게하면 코드 사용자에게 유연성이 향상됩니다. 표시 될 수있는 문제의 예로는 객체 사용을 제한하는 것입니다. 누군가 클래스에서 "게임 오브젝트"인 멤버 오브젝트를 가지려면 오브젝트를 구성해야하기 때문에 생성자에서 전체 게임 루프를 실행할 준비가되어 있어야합니다. 이 문제를 해결하기 위해 고문 코딩이 발생할 수 있습니다. 일반적인 경우 게임 개체를 미리 할당 한 다음 나중에 실행할 수 없습니다. 문제가되지 않는 일부 프로그램의 경우 모든 것을 미리 할당 한 다음 게임 루프를 호출 할 수있는 프로그램 클래스가 있습니다.

프로그래밍의 경험 법칙 중 하나는 작업에 가장 간단한 도구를 사용하는 것입니다. 어렵고 빠른 규칙은 아니지만 매우 유용한 규칙입니다. 함수 호출이 충분하다면 왜 클래스 객체를 생성해야합니까? 더 강력하고 복잡한 도구가 사용되면 개발자가 그럴만한 이유가 있다고 가정하고 어떤 종류의 이상한 트릭을하는지 탐구 할 것입니다.

이것이 합리적인 경우가 있습니다. 생성자에 게임 루프가있는 것이 직관적 인 경우가 있습니다. 그런 경우, 당신은 그것으로 실행! 예를 들어 게임 루프는 더 큰 프로그램에 내장되어 일부 데이터를 구현하는 클래스를 구성 할 수 있습니다. 해당 데이터를 생성하기 위해 게임 루프를 실행해야하는 경우 생성자에서 게임 루프를 실행하는 것이 매우 합리적 일 수 있습니다. 이 프로그램을 특별한 경우로 취급하면됩니다. 가장 중요한 프로그램은 다음 개발자가 자신이 한 일과 이유를 직관적으로 이해할 수있게 해주기 때문에 유효합니다.


오브젝트를 구성하는 데 게임 루프가 필요한 경우 최소한 팩토리 메소드에 넣으십시오. GameTestResults testResults = runGameTests(tests, configuration);오히려 GameTestResults testResults = new GameTestResults(tests, configuration);.
user253751

@immibis 예, 맞지 않는 경우를 제외하고는 사실입니다. 객체를 인스턴스화하는 리플렉션 기반 시스템으로 작업하는 경우 팩토리 메소드를 만들 수있는 옵션이 없을 수 있습니다.
Cort Ammon

0

제 생각에는 일부 개념이 섞여서 잘 표현되지 않은 질문이 생겼다는 것입니다.

클래스의 생성자는 (내가 사용하는 것을 선호하지만 끝 없다 "결코"하는 새로운 스레드를 시작할 수 while(_KeepRunning)이상 while(true)어디 선가 false로 부울 멤버를 설정할 수 있기 때문에 참조).

객체 생성과 실제 작업 시작을 분리하기 위해 스레드를 만들고 자체의 기능으로 시작하는 방법을 추출 할 수 있습니다. 리소스에 대한 액세스를보다 잘 제어 할 수 있기 때문에이 방법을 선호합니다.

"활성 객체 패턴"을 볼 수도 있습니다. 결국, 질문은 언제 "Active Object"의 스레드를 시작해야하는지에 대한 것이 었습니다. 제가 선호하는 것은 구성을 분리하고 더 나은 제어를 시작하는 것입니다.


0

당신의 객체는 작업 시작을 상기시켜줍니다 . 작업 객체는 생성자를 가지고 있지만 그것을 같은 정적 팩토리 메소드의 무리도있다 : Task.Run, Task.StartTask.Factory.StartNew.

이것들은 당신이하려고하는 것과 매우 유사하며 이것이 아마도 '협약'일 것입니다. 사람들이 가지고있는 주요 문제는 생성자를 사용하면 놀라게한다는 것입니다. @ JörgWMittag는 기본 계약을 위반한다고 말합니다 .Jörg 는 매우 놀랐습니다. 동의하고 더 이상 추가 할 것이 없습니다.

그러나 OP가 정적 팩토리 메소드를 시도한다고 제안하고 싶습니다. 놀람은 사라지고 여기 에는 근본적인 계약 이 없습니다 . 사람들은 특별한 일을하는 정적 팩토리 메소드에 익숙하며 그에 따라 이름을 지정할 수 있습니다.

당신은 @Brandin 같은, 뭔가를 알 수 있듯이 (세밀하게 제어 할 수 생성자 제공 할 수있는 var g = new Game {...}; g.MainLoop();바로 게임을 시작하고 싶지 않은 사용자를 수용하고, 어쩌면 주변에 먼저 전달하려는. 그리고 당신이 뭔가 좋아 쓸 수있는 var runningGame = Game.StartNew();시작을 위해 즉시 쉽게.


Jörg가 근본적으로 놀랐다 는 것을 의미합니다. 놀랍게도 그것은 근본적인 고통입니다.
Steve Jessop

0

생성자에서 while (true) 루프가 실제로 나쁜 이유는 무엇입니까?

전혀 나쁘지 않습니다.

클래스의 생성자가 결코 끝나지 않는 루프 (예 : 게임 루프)를 시작하게하는 나쁜 디자인으로 간주되는 이유는 무엇입니까?

왜 이렇게하고 싶습니까? 진심으로. 이 클래스에는 생성자라는 단일 함수가 있습니다. 원하는 것이 단일 함수이면 생성자가 아닌 함수가있는 것입니다.

당신은 그것에 대해 명백한 몇 가지 이유를 언급했지만 가장 큰 이유는 생략했습니다.

동일한 것을 달성하는 더 좋고 간단한 옵션이 있습니다.

2 개의 선택이 있고 하나의 선택이 더 낫다면, 더 나은 선택의 양에 상관없이 더 나은 것을 선택하십시오. 우연히도 우리 GoTo를 거의 사용 하지 않는 이유 입니다.


-1

생성자의 목적은 객체를 구성하는 것입니다. 생성자가 완료되기 전에 원칙적으로 해당 객체의 메소드를 사용하는 것은 안전하지 않습니다. 물론 생성자 내에서 객체의 메소드를 호출 할 수도 있지만, 그렇게 할 때마다 현재 절반 반 상태의 객체에 대해 유효한지 확인해야합니다.

모든 로직을 생성자에 간접적으로 넣음으로써 이런 종류의 부담을 스스로 더 가중시킬 수 있습니다. 이는 초기화와 사용의 차이를 충분히 설계하지 않았 음을 나타냅니다.


1
이것은 이전의 8 가지 답변에서 제시되고 설명 된 점들에 비해 실질적인 것을 제공하지 않는 것 같습니다
gnat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.