Ich war heute in eine Programmierdiskussion involviert, in der ich einige Aussagen machte, die im Grunde genommen 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, es kurz und konkret auszudrücken. Jede Erklärung, die mir einfällt, 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, da der Status in den beteiligten Objekten mutiert" usw.) .), aber ich würde gerne einen prägnanten Grund dafür hören, warum zirkuläre Verweise schlecht sind, die nicht die Art von Glaubenssprüngen erfordern, die mein eigenes Gehirn macht, nachdem ich im Laufe der Jahre viele Stunden damit verbracht habe, sie zu verstehen, zu reparieren, zu entwirren. und verschiedene Codebits erweitern.
Bearbeiten: Ich frage nicht nach homogenen Zirkelverweisen, wie in einer doppelt verknüpften Liste oder einem Zeiger auf ein übergeordnetes Element. Diese Frage stellt sich wirklich in Bezug auf Zirkelverweise mit "größerem Geltungsbereich", wie beispielsweise der Aufruf von libB durch libA, der libA zurückruft. Ersetzen Sie 'lib' durch 'module', wenn Sie möchten. Vielen Dank für alle bisherigen Antworten!
quelle
Antworten:
Mit Zirkelverweisen stimmt vieles nicht:
Zirkuläre Klassenreferenzen erzeugen eine hohe Kopplung . beide Klassen müssen jedes Mal neu kompiliert werden , entweder von ihnen geändert wird .
Kreisförmige Anordnung Referenzen verhindern statische Linken , weil B auf A hängt aber A kann nicht montiert werden , bis B abgeschlossen ist.
Zirkuläre Objektreferenzen können naive rekursive Algorithmen (wie Serializer, Besucher und Pretty-Printers) mit Stapelüberläufen zum Absturz bringen . Die fortschrittlicheren Algorithmen verfügen über eine Zykluserkennung und schlagen lediglich mit einer aussagekräftigeren Ausnahme / Fehlermeldung fehl.
Zirkuläre Objektreferenzen machen auch die Abhängigkeitsinjektion unmöglich , wodurch die Testbarkeit Ihres Systems erheblich verringert wird.
Objekte mit einer sehr großen Anzahl von kreisförmigen Referenzen sind oft Gott Objekte . Auch wenn dies nicht der Fall ist, tendieren sie dazu, zu Spaghetti Code zu führen .
Zirkuläre Entitätsreferenzen (insbesondere in Datenbanken, aber auch in Domänenmodellen) verhindern die Verwendung von Einschränkungen für die Nichtnullierbarkeit , die möglicherweise zu einer Beschädigung der Daten oder zumindest zu Inkonsistenzen führen.
Zirkelverweise im Allgemeinen sind einfach verwirrend und erhöhen die kognitive Belastung drastisch, wenn versucht wird, die Funktionsweise eines Programms zu verstehen.
Denken Sie bitte an die Kinder. Vermeiden Sie Zirkelverweise, wann immer Sie können.
quelle
Eine kreisförmige Referenz ist doppelt so groß wie die Kopplung einer nicht kreisförmigen Referenz.
Wenn Foo etwas über Bar weiß und Bar etwas über Foo weiß, müssen Sie zwei Dinge ändern (wenn die Anforderung kommt, dass Foos und Bars nichts mehr voneinander wissen müssen). Wenn Foo etwas über Bar weiß, aber eine Bar 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 mit langer Lebensdauer (bereitgestellte Dienste, abbildbasierte Entwicklungsumgebungen), in denen Foo davon abhängt, dass Bar arbeitet, um geladen zu werden, aber auch davon, dass Foo arbeitet, um zu laden Belastung.
quelle
Wenn Sie zwei Codebits zusammenbinden, haben Sie effektiv ein großes Codestück. Die Schwierigkeit, ein Stück Code zu pflegen, ist mindestens das Quadrat seiner Größe und möglicherweise höher.
Die Leute betrachten oft die Komplexität einzelner Klassen (/ function / file / etc.) Und vergessen, dass man wirklich die Komplexität der kleinsten trennbaren (verkapselbaren) Einheit berücksichtigen sollte. Durch eine zirkuläre Abhängigkeit wird die Größe dieser Einheit möglicherweise unsichtbar erhöht (bis Sie versuchen, Datei 1 zu ändern und feststellen, dass auch Änderungen in den Dateien 2-127 erforderlich sind).
quelle
Sie können schlecht sein, nicht für sich, sondern als Indikator für ein mögliches schlechtes Design. Wenn Foo von Bar und Bar von Foo abhängt, ist es gerechtfertigt, zu hinterfragen, warum es zwei statt einer eindeutigen FooBar sind.
quelle
Hmm ... das hängt davon ab, was Sie unter Kreisabhängigkeit verstehen, denn es gibt tatsächlich einige Kreisabhängigkeiten, von denen ich denke, dass sie sehr vorteilhaft sind.
Stellen Sie sich ein XML-DOM vor - es ist sinnvoll, dass jeder Knoten einen Verweis auf seinen übergeordneten Knoten hat und dass jeder übergeordnete Knoten eine Liste seiner untergeordneten Knoten hat. Die Struktur ist logischerweise ein Baum, aber aus der Sicht eines Speicherbereinigungsalgorithmus oder ähnlichem ist die Struktur kreisförmig.
quelle
Node
ist eine Klasse, auf die sichNode
die Kinder in sich beziehen . Da die Klasse nur auf sich selbst verweist, ist sie vollständig in sich geschlossen und an nichts anderes gekoppelt. --- Mit diesem Argument könnte man argumentieren, dass eine rekursive Funktion eine zirkuläre Abhängigkeit ist. Es ist (am Stück), aber nicht schlecht.Ist wie das Huhn oder das Ei Problem.
Es gibt viele Fälle, in denen ein zirkulärer Verweis unvermeidlich und nützlich ist, aber zum Beispiel im folgenden Fall nicht funktioniert:
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, wo B vor A kompiliert werden muss, wo B vor A kompiliert werden muss, wo ...
quelle
Obwohl ich den meisten Kommentaren hier zustimme, möchte ich einen Sonderfall für den Zirkelverweis "Eltern" / "Kind" anführen.
Eine Klasse muss häufig etwas über ihre übergeordnete oder besitzende Klasse wissen, z. B. 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 einen Zirkelverweis tun, indem Sie eine enthaltende Klasse haben, so dass das, was zuvor "übergeordnet" war, jetzt ein Geschwister ist, aber es ist nicht immer möglich, vorhandenen Code neu zu faktorisieren, um dies zu tun.
Die andere Alternative besteht darin, alle Daten, die ein Kind benötigt, in seinem Konstruktor zu übergeben, was schlichtweg schrecklich ist.
quelle
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 entfernt und Sie können nicht aus Tabelle b löschen, es sei denn, der Datensatz ist aus Tabelle A entfernt, können Sie nicht löschen. Gleiches gilt für Einsätze. Aus diesem Grund können Sie in vielen Datenbanken keine kaskadierenden Aktualisierungen oder Löschvorgänge einrichten, wenn ein Zirkelverweis vorhanden ist, da dies zu einem bestimmten Zeitpunkt 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 (nach meiner Erfahrung zu 100%) Probleme mit der Datenintegrität haben. Das ist nur schlechtes Design.
quelle
Ich werde diese Frage vom Standpunkt der Modellierung aus betrachten.
Solange Sie keine Beziehungen hinzufügen, die nicht tatsächlich vorhanden sind, sind Sie in Sicherheit. Wenn Sie sie hinzufügen, erhalten Sie eine geringere Datenintegrität (da Redundanz besteht) und engeren Code.
Das Besondere an den Zirkelverweisen ist, 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 das 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 im Moment, in dem Sie beginnen, eine Nicht-Selbstreferenz zu benötigen, sofort fragen sollten, ob Sie sie nicht als Grafik modellieren können (reduzieren Sie die mehreren Entitäten zu einem - Knoten). Vielleicht gibt es einen Fall dazwischen, in dem Sie einen Zirkelverweis erstellen, aber es ist nicht angebracht, ihn als Diagramm zu modellieren, aber ich bezweifle dies sehr.
Es besteht die Gefahr, dass die Menschen glauben, dass sie einen Zirkelverweis benötigen, dies jedoch nicht. Der häufigste Fall ist der "Einer von Vielen". Sie haben beispielsweise einen Kunden mit mehreren Adressen, von denen eine als primäre Adresse gekennzeichnet werden soll. Es ist sehr verlockend, diese Situation als zwei getrennte Beziehungen has_address und is_primary_address_of zu modellieren, aber es ist nicht korrekt. Der Grund dafür ist, dass die primäre Adresse keine separate Beziehung zwischen Benutzern und Adressen ist, sondern ein Attribut der Beziehung Adresse hat. Warum ist das so? Weil seine Domain auf die Adressen des Benutzers beschränkt ist und nicht auf alle Adressen, die es gibt. Sie wählen einen der Links aus und markieren ihn als den stärksten (primären).
(Wir werden jetzt über Datenbanken sprechen.) Viele Menschen entscheiden sich für die Zwei-Beziehungen-Lösung, weil sie unter "primär" einen eindeutigen Zeiger verstehen und ein Fremdschlüssel eine Art Zeiger ist. Also sollte der Fremdschlüssel das richtige sein, 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 Gesamtreihenfolge 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 darstellt. Hier kommt es also - Beziehungsredundanz, die sicherlich nicht zu unterschätzen ist.
Daher würde ich keinen Zirkelverweis zulassen, es sei denn, es ist absolut klar, dass er von dem stammt, was ich modelliere.
(Hinweis: Dies ist ein wenig voreingenommen gegenüber dem Datenbankdesign, aber ich wette, es ist auch für andere Bereiche angemessen anwendbar.)
quelle
Ich würde diese Frage mit einer anderen Frage beantworten:
In welcher Situation können Sie mir mitteilen, dass die Beibehaltung eines kreisförmigen Referenzmodells das beste Modell für das ist, was Sie erstellen möchten?
Meiner Erfahrung nach wird das beste Modell so gut wie nie Zirkelverweise enthalten, wie Sie es meinen. Abgesehen davon gibt es viele Modelle, bei denen Sie ständig Zirkelverweise verwenden. Das 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.
quelle
Zirkelbezüge in Datenstrukturen sind manchmal die natürliche Art, ein Datenmodell auszudrücken. Coding-weise ist es definitiv nicht ideal und kann (zu einem gewissen Grad) durch Abhängigkeitsinjektion gelöst werden, wodurch das Problem vom Code auf Daten verlagert wird.
quelle
Ein Zirkelreferenzkonstrukt ist nicht nur vom Entwurfsstandpunkt aus problematisch, sondern auch vom Standpunkt des Fehlersuchens.
Berücksichtigen Sie die Möglichkeit eines Codefehlers. Sie haben in keiner Klasse die richtigen Fehlerbehebungen vorgenommen, 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 es debuggen. Als guter Programmdesigner wissen Sie, welche Methoden mit welchen Prozessen zusammenhängen, und können sie auf die Methoden einschränken, 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 haben könnte oder woher der Fehler kam, da eine Klasse von der anderen abhängig ist und von der anderen abhängig ist. Sie müssen nun beide Klassen gemeinsam testen, um herauszufinden, welcher wirklich für den Fehler verantwortlich ist.
Eine ordnungsgemäße Fehlerbehebung behebt dieses Problem natürlich nur, wenn Sie wissen, wann ein Fehler wahrscheinlich auftritt. Und wenn Sie allgemeine Fehlermeldungen verwenden, sind Sie immer noch nicht viel besser dran.
quelle
Einige Garbage Collectors haben Probleme beim Aufräumen, da jedes Objekt von einem anderen referenziert wird.
BEARBEITEN: Wie in den Kommentaren unten angegeben, gilt dies nur für einen äußerst naiven Versuch, einen Müllsammler zu finden, dem Sie in der Praxis niemals begegnen würden.
quelle
Meiner Meinung nach erleichtern uneingeschränkte Referenzen das Programmdesign, aber wir alle wissen, dass einige Programmiersprachen in bestimmten Zusammenhängen keine Unterstützung dafür bieten.
Sie erwähnten Referenzen zwischen Modulen oder Klassen. In diesem Fall ist es eine statische Sache, die vom Programmierer vordefiniert wurde, und es ist für den Programmierer eindeutig möglich, nach einer Struktur zu suchen, der es an Rundheit mangelt, obwohl sie möglicherweise nicht sauber zum Problem passt.
Das eigentliche Problem liegt in der Zirkularität von Laufzeitdatenstrukturen, bei denen einige Probleme nicht auf eine Weise definiert werden können, die die Zirkularität beseitigt. Letztendlich ist es jedoch das Problem, das diktieren sollte und das den Programmierer dazu zwingt, ein unnötiges Rätsel zu lösen.
Ich würde sagen, das ist ein Problem mit den Werkzeugen, kein Problem mit dem Prinzip.
quelle