순환 참조의 문제점은 무엇입니까?


160

나는 오늘 프로그래밍 토론에 참여하여 기본적으로 순환 참조 (모듈, 클래스간에 관계없이)가 일반적으로 좋지 않다는 것을 기본적으로 가정했다. 피치를 마치면 동료가 "원형 참조에 어떤 문제가 있습니까?"

나는 이것에 대해 강한 감정을 가지고 있지만, 간결하고 구체적으로 말로 표현하기가 어렵습니다. 내가 설명 할 수있는 모든 설명은 나도 공리를 고려하는 다른 항목 ( "단독으로 사용할 수 없으므로 테스트 할 수 없음", "참여 대상에서 상태가 변할 때 알 수없는 / 정의되지 않은 동작")에 의존하는 경향이 있습니다. .)하지만 순환 참조가 나쁜 이유에 대한 간결한 이유를 듣고 싶습니다. 왜냐하면 내 두뇌가하는 믿음의 도약을 취하지 않고 이해하고 수정하기 위해 오랜 시간을 보내고 있습니다. 다양한 코드 비트를 확장합니다.

편집 : 이중 연결 목록 또는 부모에 대한 포인터와 같은 동종 원형 참조에 대해서는 묻지 않습니다. 이 질문은 libA를 호출하는 libA를 호출하는 libA와 같이 "더 큰 범위"순환 참조에 대해 실제로 묻습니다. 원하는 경우 'lib'대신 'module'을 사용하십시오. 지금까지 모든 답변에 감사드립니다!


순환 참조는 라이브러리 및 헤더 파일과 관련이 있습니까? 워크 플로에서 새 ProjectB 코드는 기존 ProjectA 코드에서 출력되는 파일을 처리합니다. ProjectA의 출력은 ProjectB에 의해 추진되는 새로운 요구 사항입니다. ProjectB에는 어떤 필드가 어디로 갈 것인지 등을 일반적으로 결정하는 데 도움이되는 코드가 있습니다. 요점은 기존 ProjectA가 새 ProjectB에서 코드재사용 있으며 ProjectB는 기존 ProjectA에서 유틸리티 코드를 재사용하지 않는 어리석은 일입니다 (예 : 문자 세트 감지 및 코드 변환, 레코드 파싱, 데이터 유효성 검사 및 변환 등).
Luv2code

1
@ Luv2code 프로젝트간에 코드를 잘라 붙여 넣거나 두 프로젝트가 동일한 코드로 컴파일되고 링크 될 때만 어리석게됩니다. 이들이 이와 같은 리소스를 공유하는 경우 라이브러리에 넣으십시오.
dash-tom-bang

답변:


220

큰 있습니다 많은 순환 참조 문제 것들 :

  • 원형 클래스 참조는 높은 결합을 만듭니다 . 클래스마다 다시 컴파일해야 하나 그들 중 변경됩니다.

  • B는 A에 의존하지만 B가 완료 될 때까지 A를 조립할 수 없기 때문에 원형 조립품 참조 는 정적 연결을 방지 합니다.

  • 원형 객체 참조는 스택 오버플로로 순진한 재귀 알고리즘 (예 : serializer, 방문자 및 pretty-printer)과 충돌 할 수 있습니다 . 고급 알고리즘은 사이클 감지 기능을 가지며보다 구체적인 예외 / 오류 메시지와 함께 실패합니다.

  • 원형 객체 참조는 또한 의존성 주입을 불가능 하게 하여 시스템 의 테스트 가능성 을 크게 줄 입니다.

  • 순환 참조가 매우 많은 객체 는 종종 신 객체 입니다. 그렇지 않더라도 스파게티 코드 로 이어지는 경향이 있습니다 .

  • 순환 엔터티 참조 (특히 데이터베이스 및 도메인 모델)는 null아닌 제약 조건을 사용하지 못하게하므로 결국 데이터가 손상되거나 최소한 불일치가 발생할 수 있습니다.

  • 일반적으로 순환 참조 는 프로그램이 어떻게 작동하는지 이해하려고 할 때 단순히 혼란스럽고 인지 부하를 크게 증가시킵니다.

아이들을 생각하십시오. 가능하면 순환 참조를 피하십시오.


32
특히 "인지 부하"는 제가 알고있는 것이지만 그것에 대해 간결한 용어가 없었습니다.
dash-tom-bang

6
좋은 대답입니다. 테스트에 대해 말한 것이 좋습니다. 모듈 A와 B가 상호 의존적이면 함께 테스트해야합니다. 이것은 실제로 별개의 모듈이 아니라는 것을 의미합니다. 함께 그들은 하나의 깨진 모듈입니다.
kevin cline

5
자동 DI에서도 순환 참조를 사용하면 종속성 주입이 불가능하지 않습니다. 생성자 매개 변수가 아닌 속성을 주입해야합니다.
BlueRaja-대니 Pflughoeft

3
@ BlueRaja-DannyPflughoeft : 나는 DI의 많은 다른 실무자들과 마찬가지로 안티 패턴이 있다고 생각합니다. 자체 불변량을 추적합니다. 더 나쁜 것은 Castle Windsor와 같은 가장 정교하고 인기있는 많은 프레임 워크는 의존성을 해결할 수 없다면 유용한 오류 메시지를 제공 할 수 없다는 것입니다. 생성자를 해결할 수없는 종속성에 대한 자세한 설명 대신 귀찮은 null 참조로 끝납니다. 당신이 할 수 있다고해서 반드시 해야 한다는 의미는 아닙니다 .
Aaronaught

3
나는 그것이 좋은 습관이라고 주장하지 않았으며, 대답에서 주장하는 것처럼 불가능하지 않다는 것을 지적했습니다.
BlueRaja-대니 Pflughoeft

22

원형 기준은 비 원형 기준의 결합의 두 배입니다.

Foo가 Bar에 대해 알고 있고 Bar가 Foo에 대해 알고 있다면 변경해야 할 두 가지 사항이 있습니다 (요구 사항이있을 때 Foos와 Bars는 더 이상 서로에 대해 알 필요가 없음). Foo는 Bar에 대해서는 알고 있지만 Bar는 Foo에 대해서는 모른다면 Bar를 터치하지 않고 Foo를 변경할 수 있습니다.

순환 참조는 또한 최소한 오래 지속되는 환경 (배포 된 서비스, 이미지 기반 개발 환경)에서 부트 스트랩 문제를 일으킬 수 있습니다. 여기서 Foo는로드하기 위해 Bar 작업에 의존하지만 Bar는 하중.


17

두 비트의 코드를 함께 묶으면 하나의 큰 코드 조각을 효과적으로 갖게됩니다. 약간의 코드를 유지하기가 어렵다는 것은 최소한 크기의 제곱이며 가능하면 더 높습니다.

사람들은 종종 단일 클래스 (/ 함수 / 파일 / 등) 복잡성을보고 가장 작은 분리 가능 (캡슐화 가능) 단위의 복잡성을 실제로 고려해야한다는 것을 잊어 버립니다. 순환 종속성이 있으면 해당 장치의 크기가 눈에 보이지 않게 증가합니다 (파일 1을 변경하려고 시도하고 파일 2-127도 변경해야한다는 것을 알 때까지).


14

그것들은 스스로가 아니라 나쁜 디자인의 지표로 나빠질 수 있습니다. Foo가 Bar에 의존하고 Bar가 Foo에 의존한다면, 고유 한 FooBar 대신에 왜 두 가지인지 의문을 갖는 것이 정당합니다.


10

흠 ... 그것은 순환 의존성에 의해 당신이 의미하는 바에 달려 있습니다. 실제로 매우 유익하다고 생각되는 순환 의존성이 있기 때문입니다.

XML DOM을 고려하십시오. 모든 노드가 부모를 참조하고 모든 부모가 자식 목록을 갖는 것이 좋습니다. 구조는 논리적으로 트리이지만 가비지 수집 알고리즘 또는 이와 유사한 관점에서 구조는 원형입니다.


1
그것이 나무가 아닐까요?
콘래드 프릭스

@ 콘래드 : 나는 그것이 나무로 생각할 수 있다고 생각합니다. 왜?
Billy ONeal

1
나는 자식을 탐색 할 수 있고 (부모 참조에 관계없이) 종료 될 것이기 때문에 나무를 원형으로 생각하지 않습니다. 노드에 조상이기도 한 자식이 없으면 내 마음에 그것을 나무가 아닌 그래프로 만듭니다.
Conrad Frix

5
순환 참조는 노드의 하위 중 하나가 조상으로 루프백 된 경우입니다.
Matt Olenik

이것은 실제로 순환 종속성 이 아닙니다 (적어도 문제를 일으키는 방식이 아님). 예를 들어, 그 Node클래스가 클래스 안에 있다고 가정 Node해보십시오. 클래스 자체를 참조하기 때문에 클래스는 완전히 독립적이며 다른 것에 연결되지 않습니다. ---이 인수를 사용하면 재귀 함수가 순환 종속성이라고 주장 할 수 있습니다. 그것은 이다 하지만 나쁜 방법으로, (단번에).
byxor

9

등인가 , 닭 또는 계란의 문제.

순환 참조가 불가피하고 유용한 많은 경우가 있지만, 예를 들어 다음과 같은 경우에는 작동하지 않습니다.

프로젝트 A는 프로젝트 B에 의존하고 B는 A에 의존합니다. A는 B에 사용하기 위해 컴파일되어야합니다.


6

나는 여기서 대부분의 의견에 동의하지만 "부모"/ "자식"순환 참조에 대한 특별한 경우를 요구하고 싶습니다.

클래스는 종종 부모 또는 소유 클래스, 아마도 기본 동작, 데이터의 파일 이름, 열을 선택한 sql 문 또는 로그 파일의 위치 등에 대해 알아야합니다.

이전에 "부모"였던 것이 이제는 형제가되도록 포함 클래스를 사용하여 순환 참조없이이 작업을 수행 할 수 있지만,이를 위해 기존 코드를 항상 리팩토링 할 수있는 것은 아닙니다.

다른 대안은 자녀가 생성자에 필요할 수있는 모든 데이터를 전달하는 것입니다.


관련 메모에서 X가 Y에 대한 참조를 가질 수있는 두 가지 일반적인 이유가 있습니다. X는 X를 대신하여 Y에게 작업을 요청하거나 Y가 Y를 대신하여 X가 Y를 수행하도록 기대할 수 있습니다. Y에 대해 존재하는 유일한 참조가 Y를 대신하여 작업을 수행하려는 다른 객체를 목적으로하는 경우, 해당 참조 보유자는 Y의 서비스가 더 이상 필요하지 않으며 Y에 대한 참조를 포기해야한다고 알려야합니다. 그들의 편리함.
supercat

5

데이터베이스 용어에서 적절한 PK / FK 관계를 가진 순환 참조를 사용하면 데이터를 삽입하거나 삭제할 수 없습니다. 레코드가 테이블 b에서 이동하지 않으면 테이블 a에서 삭제할 수없고 레코드가 테이블 A에서 이동하지 않으면 테이블 b에서 삭제할 수없는 경우 삭제할 수 없습니다. 인서트와 동일합니다. 이런 이유로 많은 데이터베이스에서 순환 참조가있는 경우 계단식 업데이트 나 삭제를 설정할 수없는 경우가 있습니다. 예, PK / Fk가 공식적으로 선언되지 않은 상태에서 이러한 종류의 관계를 설정할 수 있지만 (내 경험상 100 %) 데이터 무결성 문제가 있습니다. 그것은 단지 나쁜 디자인입니다.


4

모델링 관점에서이 질문을하겠습니다.

실제로 존재하지 않는 관계를 추가하지 않는 한 안전합니다. 추가하면 데이터의 무결성이 떨어지고 (중복성이 있기 때문에) 코드가 더 밀접하게 결합됩니다.

순환 참조가있는 것은 구체적으로 하나 자체 참조를 제외하고 실제로 필요한 경우를 보지 못했다는 것입니다. 트리 또는 그래프를 모델링하는 경우 자체 참조가 코드 품질의 관점에서 무해하므로 (종속성이 추가되지 않기 때문에) 그 자체가 필요합니다.

나는 자체가 아닌 참조가 필요하기 시작하면 즉시 그래프로 모델링 할 수 있는지 묻어 야합니다 (여러 엔티티를 하나의 노드로 축소). 어쩌면 순환 참조를 만드는 곳이 있지만 그래프로 모델링하는 것이 적절하지 않지만 그 점을 의심합니다.

사람들이 순환 참조가 필요하다고 생각하지만 실제로는 그렇지 않다는 위험이 있습니다. 가장 일반적인 경우는 "다수의 경우"입니다. 예를 들어, 여러 주소를 가진 고객이 있는데이 주소를 기본 주소로 표시해야합니다. has_addressis_primary_address_of의 두 가지 개별 관계 로이 상황을 모델링하는 것이 매우 유혹적 이지만 올바르지 않습니다. 그 이유 인 기본 주소 인 것은 사용자 및 주소 사이의 관계 분리되지 않지만, 대신는 속성 의 관계가 어드레스를 가지고. 왜 그런 겁니까? 도메인은 사용자의 주소로 제한되며 모든 주소가 아닙니다. 링크 중 하나를 선택하여 가장 강한 것으로 표시합니다 (기본).

(현재 데이터베이스에 대해 이야기 할 것입니다.) 많은 사람들이 "1 차"를 고유 포인터로 이해하고 외래 키가 일종의 포인터이기 때문에 두 관계 솔루션을 선택합니다. 외래 키를 사용해야합니다. 잘못된. 외래 키는 관계를 나타내지 만 "기본"은 관계가 아닙니다. 그것은 하나의 요소가 무엇보다 중요하고 나머지는 순서가없는 순서의 퇴화 된 경우입니다. 전체 주문을 모델링해야하는 경우 기본적으로 다른 선택 사항이 없으므로 관계의 속성으로 간주합니다. 그러나 당신이 그것을 타락시키는 순간, 관계와 관계가 아닌 것을 모델링하는 선택과 끔찍한 선택이 있습니다. 따라서 여기에는 과소 평가되는 것이 아닌 관계 중복성이 있습니다.

따라서 모델링하는 것에서 온 것이 확실하지 않으면 순환 참조가 발생하지 않도록합니다.

(참고 : 이것은 데이터베이스 디자인에 약간 편향되어 있지만 다른 영역에도 적용 할 수 있습니다.)


2

다른 질문으로 그 질문에 대답하겠습니다.

순환 참조 모델을 유지하는 것이 구축하려는 것에 가장 적합한 모델 인 상황은 무엇입니까?

내 경험상, 최고의 모델은 내가 생각하는 방식으로 순환 참조를 포함하지 않습니다. 즉, 순환 참조를 항상 사용하는 많은 모델이 있으며 매우 기본적입니다. 부모-> 자식 관계, 모든 그래프 모델 등이 있지만 잘 알려진 모델이므로 다른 것을 완전히 언급한다고 생각합니다.


1
순환 연결 목록 (단일 연결 또는 이중 연결)은 "중지되지 않아야"하는 프로그램의 중앙 이벤트 대기열에 대한 우수한 데이터 구조가 될 수 있습니다. "삭제하지 않음"플래그가 설정된 후 비어있을 때까지 단순히 큐를 순회하십시오. 새로운 태스크 (일시적 또는 영구적)가 필요할 때 큐의 적절한 위치에 배치하십시오. "삭제하지 않음"플래그 없이도 서비스를 제공 할 때마다 대기열에서 가져 가십시오).
Vatine

1

데이터 구조의 순환 참조는 때때로 데이터 모델을 표현하는 자연스러운 방법입니다. 코딩 측면에서는 분명히 이상적이지 않으며 종속성 주입으로 문제를 해결할 수 있으므로 코드에서 데이터로 문제가 발생합니다.


1

순환 참조 구성은 디자인 관점뿐만 아니라 오류 잡기 관점에서도 문제가됩니다.

코드 실패 가능성을 고려하십시오. 아직까지 메소드를 개발하지 않았거나 게으 르기 때문에 어느 클래스에서나 적절한 오류 포착을하지 않았습니다. 어느 쪽이든, 발생한 메시지를 알려주는 오류 메시지가 없으며이를 디버깅해야합니다. 훌륭한 프로그램 디자이너는 어떤 프로세스가 어떤 프로세스와 관련되어 있는지 알고 있으므로 오류를 일으킨 프로세스와 관련된 메소드로 범위를 좁힐 수 있습니다.

순환 참조를 사용하면 문제가 두 배가되었습니다. 프로세스가 밀접하게 바인딩되어 있기 때문에 어떤 클래스가 오류를 발생 시켰는지 또는 어떤 클래스가 다른 클래스에 종속되어 있는지에 따라 오류가 발생한 시점을 알 수있는 방법이 없습니다. 이제 어느 클래스가 실제로 오류를 일으키는 지 알아 내기 위해 두 클래스를 함께 테스트하는 데 시간을 소비해야합니다.

물론, 적절한 오류 포착은이 문제를 해결하지만 언제 오류가 발생할 가능성이 있는지 아는 경우에만 해결합니다. 그리고 일반적인 오류 메시지를 사용하는 경우 여전히 나쁘지 않습니다.


1

일부 가비지 수집기는 각 개체가 다른 개체에 의해 참조되기 때문에 정리하는 데 문제가 있습니다.

편집 : 아래 주석에서 알 수 있듯이 이것은 실제로 가비지 수집기에서 극도로 순진한 시도 에만 해당됩니다 .


11
흠 .. 이것에 의해 걸려온 가비지 수집기는 실제 가비지 수집기가 아닙니다.
Billy ONeal

11
순환 참조에 문제가있는 최신 가비지 수집기를 모른다. 참조 횟수를 사용하는 경우 순환 참조는 문제가되지만 대부분의 가비지 수집기는 추적 스타일입니다 (알려진 참조 목록으로 시작하여 다른 참조를 찾아 다른 모든 것을 수집하는 경우).
Dean Harding

4
다양한 유형의 가비지 수집기의 단점을 설명하는 sct.ethz.ch/teaching/ws2005/semspecver/slides/takano.pdf 를 참조하십시오. , 원형 구조에 문제가 생기기 시작합니다 (원형 개체가 다른 세대에있을 때). 기준 카운트를 취하고 순환 기준 문제를 해결하기 시작하면 중단 시간이 길어지고 마크와 스윕의 특성이 생깁니다.
Ken Bloom

가비지 콜렉터가 Foo를보고이 예제에서 Bar를 참조하는 메모리를 할당 해제 한 경우 Bar 제거를 처리해야합니다. 따라서이 시점에서 가비지 컬렉터는 이미 진행 중이므로 바를 제거 할 필요가 없습니다. 또는 Foo를 참조하는 Bar를 제거하면 Foo도 제거하므로 Bar를 제거 할 때 Foo를 제거 할 필요가 없습니다. 내가 틀렸다면 정정 해주세요.
Chris

1
objective-c에서 순환 참조는 해제 할 때 참조 횟수가 0이되지 않도록 설정하여 가비지 수집기를 트립합니다.
DexterW

-2

제 생각에는 제한없는 참조가 있으면 프로그램 설계가 쉬워 지지만 일부 프로그래밍 언어는 일부 컨텍스트에서 지원되지 않습니다.

모듈 또는 클래스 간의 참조를 언급했습니다. 이 경우 프로그래머가 미리 정의한 정적 인 것이므로 문제가 명확하게 맞지 않을 수는 있지만 프로그래머가 순환 성이없는 구조를 검색 할 수 있습니다.

실제 문제는 런타임 데이터 구조에서 순환 성 (circularity)에서 발생하는데, 여기서 일부 문제는 실제로 순환 성을 제거하는 방식으로 정의 할 수 없습니다. 결국-지시하고 다른 것을 요구 해야하는 문제는 프로그래머가 불필요한 퍼즐을 풀도록 강요하는 것입니다.

나는 그것이 도구의 문제가 아니라 원리에 문제가 없다고 말하고 싶습니다.


한 문장의 의견을 추가해도 게시물에 크게 기여하거나 답변을 설명하지는 않습니다. 이것에 대해 자세히 설명해 주시겠습니까?

글쎄요, 포스터는 실제로 모듈이나 클래스 사이의 참조를 언급했습니다. 이 경우 프로그래머가 사전 정의한 정적 인 것이므로 문제가 완전히 맞지 않을 수도 있지만 프로그래머가 원형이없는 구조를 검색 할 수 있습니다. 실제 문제는 런타임 데이터 구조에서 순환 성 (circularity)에서 발생하는데, 여기서 일부 문제는 실제로 순환 성을 제거하는 방식으로 정의 할 수 없습니다. 결국-지시하고 다른 것을 요구 해야하는 문제는 프로그래머가 불필요한 퍼즐을 풀도록 강요하는 것입니다.
Josh S

프로그램을 시작하고 실행하는 것이 더 쉬워 지지만 일반적으로 말하면 사소한 변경으로 인해 계단식 효과가 있으므로 소프트웨어를 유지 관리하기가 더 어려워집니다. A는 B를 호출하여 A를 호출하여 B를 호출합니다. 특히 A와 B가 다형성 일 때이 특성의 변화의 영향을 실제로 이해하기가 어렵습니다.
dash-tom-bang
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.