it-swarm-eu.dev

Was ist los mit Zirkelverweisen?

Ich war heute an einer Programmierdiskussion beteiligt, in der ich einige Aussagen machte, die im Grunde genommen axiomatisch davon ausgegangen sind, dass Zirkelverweise (zwischen Modulen, Klassen, was auch immer) im Allgemeinen schlecht sind. Als ich mit meinem Pitch fertig war, fragte mein Kollege: "Was ist los mit Zirkelverweisen?"

Ich habe starke Gefühle dafür, aber es fällt mir schwer, kurz und konkret zu verbalisieren. Jede Erklärung, die ich mir einfallen lassen kann, stützt sich in der Regel auf andere Elemente, die ich ebenfalls als Axiome betrachte ("kann nicht isoliert verwendet werden, kann also nicht getestet werden", "unbekanntes/undefiniertes Verhalten als Zustand mutiert in den teilnehmenden Objekten" usw. .), aber ich würde gerne einen kurzen Grund dafür hören, warum Zirkelverweise schlecht sind, die nicht die Art von Glaubenssprüngen machen, die mein eigenes Gehirn macht, nachdem ich über die Jahre viele Stunden damit verbracht habe, sie zu entwirren, zu verstehen, zu reparieren, und verschiedene Codebits erweitern.

Edit : Ich frage nicht nach homogenen Zirkelverweisen, wie sie in einer doppelt verknüpften Liste oder einem Zeiger auf einen Elternteil enthalten sind. Bei dieser Frage geht es wirklich um Zirkelverweise mit "größerem Umfang", wie z. B. libA, das libB aufruft und libA zurückruft. Ersetzen Sie 'lib' durch 'module', wenn Sie möchten. Vielen Dank für alle bisherigen Antworten!

167
dash-tom-bang

Es gibt eine große viele Dinge falsch mit Zirkelverweisen:

  • Zirkuläre Klassenreferenzen erzeugen eine hohe Kopplung ; beide Klassen müssen jedes Mal neu kompiliert werden, wenn entweder von ihnen geändert wird.

  • Zirkuläre Assembly Referenzen verhindern statische Verknüpfungen , da B von A abhängt, aber von A. kann erst zusammengebaut werden, wenn B fertig ist.

  • Zirkuläre Objektreferenzen können naive rekursive Algorithmen (wie Serializer, Besucher) zum Absturz bringen und hübsche Drucker) mit Stapelüberläufen. Die fortgeschritteneren Algorithmen verfügen über eine Zykluserkennung und schlagen lediglich mit einer aussagekräftigeren Ausnahme-/Fehlermeldung fehl.

  • Zirkuläre Objektreferenzen machen auch eine Abhängigkeitsinjektion unmöglich , wodurch die Testbarkeit Ihres Systems.

  • Objekte mit einer sehr großen Anzahl von Zirkelverweisen sind häufig Gottobjekte . Selbst wenn dies nicht der Fall ist, neigen sie dazu, zu Spaghetti Code zu führen.

  • Zirkuläre Entitätsreferenzen (insbesondere in Datenbanken, aber auch in Domänenmodellen) verhindern die Verwendung von Nicht-Nullabilitätsbeschränkungen , was schließlich zu Datenbeschädigung oder zumindest Inkonsistenz führen kann.

  • Zirkelverweise im Allgemeinen sind einfach verwirrend und erhöhen die kognitive Belastung drastisch, wenn versuchen zu verstehen, wie ein Programm funktioniert.

Bitte denken Sie an die Kinder; Vermeiden Sie Zirkelverweise, wann immer Sie können.

228
Aaronaught

Eine Zirkelreferenz ist doppelt so groß wie die Kopplung einer nicht kreisförmigen Referenz.

Wenn Foo Bar kennt und Bar Foo kennt, müssen zwei Dinge geändert werden (wenn die Anforderung kommt, dass Foos und Bars nicht mehr voneinander wissen müssen). Wenn Foo etwas über Bar weiß, eine Bar aber nichts über Foo weiß, können Sie Foo ändern, ohne Bar zu berühren.

Zyklische Verweise können auch Bootstrapping-Probleme verursachen, zumindest in Umgebungen, die lange dauern (bereitgestellte Dienste, imagebasierte Entwicklungsumgebungen), in denen Foo davon abhängt, dass Bar zum Laden arbeitet, Bar jedoch auch davon, dass Foo funktioniert, um zu laden Belastung.

22
Frank Shearar

Wenn Sie zwei Codebits zusammenbinden, haben Sie effektiv einen großen Code. Die Schwierigkeit, ein bisschen Code zu verwalten, ist mindestens das Quadrat seiner Größe und möglicherweise höher.

Die Leute betrachten oft die Komplexität einer einzelnen Klasse (/ function/file/etc.) Und vergessen, dass Sie wirklich die Komplexität der kleinsten trennbaren (einkapselbaren) Einheit berücksichtigen sollten. Eine zirkuläre Abhängigkeit erhöht die Größe dieser Einheit möglicherweise unsichtbar (bis Sie versuchen, Datei 1 zu ändern und feststellen, dass auch Änderungen in den Dateien 2-127 erforderlich sind).

17
Alex Feinman

Sie können nicht von sich aus schlecht sein, sondern als Indikator für ein möglicherweise schlechtes Design. Wenn Foo von Bar und Bar von Foo abhängt, ist es gerechtfertigt zu fragen, warum es sich um zwei statt einer eindeutigen FooBar handelt.

14
mouviciel

Hmm ... das hängt davon ab, was Sie unter zirkulärer Abhängigkeit verstehen, denn es gibt tatsächlich einige zirkuläre Abhängigkeiten, die ich für sehr vorteilhaft halte.

Stellen Sie sich ein XML-DOM vor - es ist sinnvoll, dass jeder Knoten einen Verweis auf sein übergeordnetes Element hat und dass jedes übergeordnete Element eine Liste seiner untergeordneten Knoten hat. Die Struktur ist logisch ein Baum, aber aus der Sicht eines Garbage Collection-Algorithmus oder ähnlichem ist die Struktur kreisförmig.

10
Billy ONeal

Ist wie das Huhn oder das Ei Problem.

Es gibt viele Fälle, in denen Zirkelverweise unvermeidlich und nützlich sind, aber im folgenden Fall funktioniert dies beispielsweise nicht:

Projekt A hängt von Projekt B ab und B hängt von A ab. A muss kompiliert werden, um in B verwendet zu werden, was erfordert, dass B vor A kompiliert wird, was erfordert, dass B vor A kompiliert wird, was ...

9

Obwohl ich den meisten Kommentaren hier zustimme, möchte ich einen Sonderfall für den Zirkelverweis "Eltern"/"Kind" vorbringen.

Eine Klasse muss häufig etwas über ihre übergeordnete oder besitzende Klasse wissen, möglicherweise das Standardverhalten, den Namen der Datei, aus der die Daten stammen, die SQL-Anweisung, die die Spalte ausgewählt hat, oder den Speicherort einer Protokolldatei usw.

Sie können dies ohne Zirkelverweis tun, indem Sie eine enthaltende Klasse haben, sodass das, was zuvor "übergeordnet" war, jetzt ein Geschwister ist. Es ist jedoch nicht immer möglich, vorhandenen Code neu zu faktorisieren, um dies zu tun.

Die andere Alternative besteht darin, alle Daten zu übergeben, die ein Kind möglicherweise in seinem Konstruktor benötigt, was am Ende einfach schrecklich ist.

6
James Anderson

In Bezug auf die Datenbank machen Zirkelverweise mit korrekten PK/FK-Beziehungen das Einfügen oder Löschen von Daten unmöglich. Wenn Sie nicht aus Tabelle a löschen können, es sei denn, der Datensatz ist aus Tabelle b verschwunden, und Sie können nicht aus Tabelle b löschen, es sei denn, der Datensatz ist aus Tabelle A verschwunden. Sie können nicht löschen. Gleiches gilt für Einsätze. Aus diesem Grund können Sie in vielen Datenbanken keine kaskadierenden Aktualisierungen oder Löschungen einrichten, wenn ein Zirkelverweis vorhanden ist, da dies irgendwann nicht mehr möglich ist. Ja, Sie können diese Art von Beziehungen einrichten, ohne dass die PK/Fk offiziell deklariert wird, aber dann werden Sie (meiner Erfahrung nach 100% der Zeit) Probleme mit der Datenintegrität haben. Das ist nur schlechtes Design.

5
HLGEM

Ich werde diese Frage aus modelltechnischer Sicht betrachten.

Solange Sie keine Beziehungen hinzufügen, die nicht wirklich vorhanden sind, sind Sie sicher. Wenn Sie sie hinzufügen, erhalten Sie weniger Integrität in den Daten (weil es eine Redundanz gibt) und einen enger gekoppelten Code.

Die Sache mit den Zirkelreferenzen ist speziell, dass ich keinen Fall gesehen habe, in dem sie tatsächlich benötigt würden, außer einer Selbstreferenz. Wenn Sie Bäume oder Diagramme modellieren, benötigen Sie dies und es ist vollkommen in Ordnung, da die Selbstreferenz aus Sicht der Codequalität harmlos ist (keine Abhängigkeit hinzugefügt).

Ich glaube, dass Sie in dem Moment, in dem Sie anfangen, eine Nicht-Selbst-Referenz zu benötigen, sofort fragen sollten, ob Sie sie nicht als Diagramm modellieren können (reduzieren Sie die mehreren Entitäten zu einem Knoten). Vielleicht gibt es dazwischen einen Fall, in dem Sie einen Zirkelverweis erstellen, aber die Modellierung als Diagramm nicht angemessen ist, aber ich bezweifle dies sehr.

Es besteht die Gefahr, dass die Leute denken, dass sie einen Zirkelverweis brauchen, aber tatsächlich nicht. Der häufigste Fall ist "Der Fall von vielen". Sie haben beispielsweise einen Kunden mit mehreren Adressen, von denen eine als primäre Adresse markiert werden sollte. Es ist sehr verlockend, diese Situation als zwei getrennte Beziehungen zu modellieren has_address und is_primary_address_of, aber es ist nicht korrekt. Der Grund ist, dass die primäre Adresse ist keine separate Beziehung zwischen Benutzern und Adressen ist, sondern ein Attribut der Beziehung hat Adresse. Warum ist das so? Weil seine Domain auf die Adressen des Benutzers und nicht auf alle Adressen beschränkt ist, die es gibt. Sie wählen einen der Links aus und markieren ihn als den stärksten (primären).

(Ich werde jetzt über Datenbanken sprechen) Viele Menschen entscheiden sich für die Zwei-Beziehungen-Lösung, weil sie "primär" als eindeutigen Zeiger verstehen und ein Fremdschlüssel eine Art Zeiger ist. Also sollte ein Fremdschlüssel verwendet werden, oder? Falsch. Fremdschlüssel stellen Beziehungen dar, aber "primär" ist keine Beziehung. Es ist ein entarteter Fall einer Ordnung, bei der vor allem ein Element und der Rest nicht geordnet ist. Wenn Sie eine Gesamtbestellung modellieren müssten, würden Sie sie natürlich als Attribut einer Beziehung betrachten, da es im Grunde keine andere Wahl gibt. Aber in dem Moment, in dem Sie es degenerieren, gibt es eine Wahl und eine ziemlich schreckliche - etwas zu modellieren, das keine Beziehung als Beziehung ist. Hier kommt es also - Beziehungsredundanz, die sicherlich nicht zu unterschätzen ist. Die Anforderung an die Eindeutigkeit sollte auf andere Weise auferlegt werden, beispielsweise durch eindeutige Teilindizes.

Daher würde ich nicht zulassen, dass ein Zirkelverweis auftritt, es sei denn, es ist absolut klar, dass er von dem stammt, was ich modelliere.

(Hinweis: Dies ist leicht voreingenommen gegenüber dem Datenbankdesign, aber ich würde wetten, dass es auch auf andere Bereiche anwendbar ist.)

4
clime

Ich würde diese Frage mit einer anderen Frage beantworten:

Welche Situation können Sie mir geben, in der das Halten eines Zirkelreferenzmodells das beste Modell für das ist, was Sie erstellen möchten?

Nach meiner Erfahrung wird das beste Modell so gut wie nie Zirkelverweise enthalten, wie ich denke, dass Sie es ernst meinen. Davon abgesehen gibt es viele Modelle, bei denen Sie ständig Zirkelverweise verwenden. Es ist einfach extrem einfach. Übergeordnete -> Untergeordnete Beziehungen, jedes Diagrammmodell usw., aber dies sind bekannte Modelle, und ich denke, Sie beziehen sich ganz auf etwas anderes.

2
Joseph

Einige Müllsammler haben Probleme, sie zu bereinigen, da jedes Objekt von einem anderen referenziert wird.

BEARBEITEN: Wie in den Kommentaren unten erwähnt, gilt dies nur für einen extrem naiven Versuch eines Müllsammlers, nicht für einen, dem Sie jemals in der Praxis begegnen würden.

1
shmuelp

Ein Zirkelreferenzkonstrukt ist nicht nur vom Entwurf her problematisch, sondern auch vom Standpunkt der Fehlererkennung.

Berücksichtigen Sie die Möglichkeit eines Codefehlers. Sie haben in keiner Klasse die richtige Fehlererkennung platziert, entweder weil Sie Ihre Methoden noch nicht so weit entwickelt haben oder weil Sie faul sind. In beiden Fällen haben Sie keine Fehlermeldung, die Ihnen sagt, was passiert ist, und Sie müssen sie debuggen. Als guter Programmdesigner wissen Sie, welche Methoden mit welchen Prozessen zusammenhängen, sodass Sie sie auf die Methoden eingrenzen können, die für den Prozess relevant sind, der den Fehler verursacht hat.

Mit Zirkelverweisen haben sich Ihre Probleme jetzt verdoppelt. Da Ihre Prozesse eng miteinander verbunden sind, können Sie nicht wissen, welche Methode in welcher Klasse den Fehler verursacht hat oder woher der Fehler stammt, da eine Klasse von der anderen abhängig ist. Sie müssen jetzt Zeit damit verbringen, beide Klassen zusammen zu testen, um herauszufinden, welche wirklich für den Fehler verantwortlich ist.

Eine ordnungsgemäße Fehlererkennung behebt dies natürlich, aber nur, wenn Sie wissen, wann ein Fehler wahrscheinlich auftritt. Und wenn Sie generische Fehlermeldungen verwenden, sind Sie immer noch nicht viel besser dran.

1
Zibbobz

Zirkelverweise in Datenstrukturen sind manchmal die natürliche Art, ein Datenmodell auszudrücken. In Bezug auf die Codierung ist es definitiv nicht ideal und kann (bis zu einem gewissen Grad) durch Abhängigkeitsinjektion gelöst werden, wodurch das Problem vom Code auf die Daten übertragen wird.

1
Vatine