Es spielt keine Rolle, wie eng eine Sache mit der anderen verbunden ist, wenn sich die andere Sache nie ändert. Ich fand es im Laufe der Jahre im Allgemeinen produktiver, mich darauf zu konzentrieren, weniger Gründe für Änderungen zu finden, um Stabilität zu suchen, als sie einfacher zu ändern, indem versucht wird, eine möglichst lockere Form der Kopplung zu erreichen.
Entkopplung Ich habe mich als sehr nützlich erwiesen, bis zu dem Punkt, an dem ich manchmal eine bescheidene Codeduplizierung bevorzuge, um Pakete zu entkoppeln. Als grundlegendes Beispiel hatte ich die Wahl, meine mathematische Bibliothek zum Implementieren einer Bildbibliothek zu verwenden. Ich habe einige grundlegende mathematische Funktionen nicht kopiert, die einfach zu kopieren waren.
Jetzt ist meine Bildbibliothek völlig unabhängig von der Mathematikbibliothek. Unabhängig davon, welche Änderungen ich an meiner Mathematikbibliothek vornehme, hat dies keine Auswirkungen auf die Bildbibliothek. Das ist in erster Linie die Stabilität. Die Bildbibliothek ist jetzt stabiler, da drastisch weniger Änderungsgründe vorliegen, da sie von jeder anderen Bibliothek, die sich ändern könnte, entkoppelt ist (außer der C-Standardbibliothek, die sich hoffentlich nie ändern sollte). Als Bonus kann es auch einfach bereitgestellt werden, wenn es sich nur um eine eigenständige Bibliothek handelt, für deren Erstellung und Verwendung nicht mehrere andere Bibliotheken erforderlich sind.
Stabilität ist für mich sehr hilfreich. Ich mag es, eine Sammlung von gut getestetem Code zu erstellen, der immer weniger Gründe hat, sich in Zukunft jemals zu ändern. Das ist kein Wunschtraum; Ich habe C-Code, den ich seit den späten 80ern benutze und wieder benutze, was sich seitdem überhaupt nicht geändert hat. Zugegebenermaßen handelt es sich um einfache Dinge wie pixelorientierten und geometriebezogenen Code, während viele meiner übergeordneten Dinge veraltet sind, aber es ist etwas, das immer noch sehr hilfreich ist. Das bedeutet fast immer eine Bibliothek, die sich auf immer weniger Dinge stützt, wenn überhaupt nichts Äußeres. Die Zuverlässigkeit steigt und steigt, wenn Ihre Software in zunehmendem Maße von stabilen Grundlagen abhängt, die nur wenige oder keine Gründe für eine Änderung bieten. Weniger bewegliche Teile sind sehr schön, auch wenn die Anzahl der beweglichen Teile in der Praxis viel höher ist als die der stabilen Teile.
Lose Kupplung ist in der gleichen Richtung, aber ich finde oft, dass lose Kupplung so viel weniger stabil ist als keine Kupplung. Sofern Sie nicht in einem Team mit weit überlegenen Schnittstellendesignern und Kunden zusammenarbeiten, die ihre Meinung nicht ändern, als ich es jemals getan habe, finden auch reine Schnittstellen häufig Gründe, sich in einer Weise zu ändern, die immer noch zu Kaskadenbrüchen im gesamten Code führt. Diese Vorstellung, dass Stabilität durch die Ausrichtung von Abhängigkeiten auf das Abstrakte und nicht auf das Konkrete erreicht werden kann, ist nur dann sinnvoll, wenn das Design der Benutzeroberfläche auf Anhieb einfacher als die Implementierung ist. Ich finde es oft umgekehrt, wenn ein Entwickler eine sehr gute, wenn nicht wundervolle Implementierung erstellt hat, die den Designanforderungen entspricht, die er zu erfüllen glaubt, nur um in Zukunft festzustellen, dass sich die Designanforderungen vollständig ändern.
Deshalb bevorzuge ich Stabilität und völlige Entkopplung, damit ich zumindest mit Zuversicht sagen kann: "Diese kleine, isolierte Bibliothek, die seit Jahren benutzt und durch gründliche Tests gesichert wird, ist so gut wie unwahrscheinlich, dass Änderungen erforderlich sind, egal, was in der chaotischen Außenwelt vor sich geht . " Es gibt mir ein kleines Stück Vernunft, egal welche Art von Designänderungen außerhalb erforderlich sind.
Kupplung und Stabilität, ECS-Beispiel
Ich mag auch Entity-Component-Systeme und sie führen eine sehr enge Kopplung ein, da das System auf alle Komponentenabhängigkeiten direkt zugreift und die Rohdaten wie folgt bearbeitet:
Alle Abhängigkeiten hier sind ziemlich eng, da Komponenten nur Rohdaten verfügbar machen. Die Abhängigkeiten fließen nicht in Richtung Abstraktionen, sondern in Richtung Rohdaten, was bedeutet, dass jedes System über das maximal mögliche Wissen über die einzelnen Komponententypen verfügt, auf die es zugreifen möchte. Komponenten haben keine Funktionalität, da alle Systeme auf die Rohdaten zugreifen und diese manipulieren. Es ist jedoch sehr einfach, über ein solches System nachzudenken, da es so flach ist. Wenn eine Textur fehlerhaft herauskommt, wissen Sie mit diesem System sofort, dass nur das Rendering- und Malsystem auf Texturkomponenten zugreift, und Sie können das Rendering-System wahrscheinlich schnell ausschließen, da es nur konzeptionell aus Texturen liest.
In der Zwischenzeit könnte eine lose gekoppelte Alternative sein:
... mit all den Abhängigkeiten, die zu abstrakten Funktionen fließen, nicht zu Daten, und allem in diesem Diagramm, das eine eigene öffentliche Schnittstelle und Funktionalität aufweist. Hier könnten alle Abhängigkeiten sehr locker sein. Die Objekte sind möglicherweise nicht einmal direkt voneinander abhängig und interagieren über reine Schnittstellen miteinander. Trotzdem ist es sehr schwierig, über dieses System nachzudenken, besonders wenn etwas schief geht, da die Interaktionen sehr komplex sind. Es wird auch mehr Interaktionen geben (mehr Kopplung, wenn auch lockerer) als beim ECS, da die Entitäten über die Komponenten Bescheid wissen müssen, die sie aggregieren, auch wenn sie nur über die abstrakte öffentliche Schnittstelle des jeweils anderen Bescheid wissen.
Auch wenn Designänderungen an irgendetwas vorgenommen werden, kommt es häufiger zu Kaskadenbrüchen als beim ECS, und es gibt in der Regel mehr Gründe und Versuchungen für Designänderungen, da jedes einzelne Element versucht, eine schöne objektorientierte Schnittstelle und Abstraktion bereitzustellen. Das kommt sofort mit der Idee, dass jedes einzelne kleine Ding versucht, dem Design Beschränkungen und Beschränkungen aufzuerlegen, und diese Beschränkungen rechtfertigen oft Änderungen am Design. Die Funktionalität ist viel eingeschränkter und muss so viel mehr Designannahmen treffen als Rohdaten.
Ich habe in der Praxis festgestellt, dass der obige Typ eines "flachen" ECS-Systems so viel einfacher zu überlegen ist als selbst die am lockersten gekoppelten Systeme mit einem komplexen Spinnennetz aus losen Abhängigkeiten, und, was für mich am wichtigsten ist, ich finde so wenige Gründe Damit die ECS-Version jemals vorhandene Komponenten ändern muss, sind die abhängigen Komponenten nur dafür verantwortlich, die entsprechenden Daten bereitzustellen, die für das Funktionieren des Systems erforderlich sind. Vergleichen Sie die Schwierigkeit, eine reine IMotion
Schnittstelle und ein konkretes Bewegungsobjekt zu entwerfen, das diese Schnittstelle implementiert, die anspruchsvolle Funktionen bietet, während Sie versuchen, Invarianten gegenüber privaten Daten aufrechtzuerhalten, mit einer Bewegungskomponente, die nur Rohdaten bereitstellen muss, die für die Lösung des Problems relevant sind, und sich nicht darum kümmert Funktionalität.
Funktionalität ist so viel schwieriger zu finden als Daten, weshalb ich denke, dass es oft besser ist, den Fluss von Abhängigkeiten auf Daten zu lenken. Immerhin, wie viele Vektor / Matrix-Bibliotheken gibt es? Wie viele von ihnen verwenden genau die gleiche Datendarstellung und unterscheiden sich nur geringfügig in der Funktionalität? Unzählige, und doch haben wir trotz identischer Datendarstellungen so viele, weil wir subtile Unterschiede in der Funktionalität wollen. Wie viele Bildbibliotheken gibt es? Wie viele von ihnen repräsentieren Pixel auf unterschiedliche und einzigartige Weise? Kaum einer und zeigt erneut, dass Funktionalität in vielen Szenarien viel instabiler und anfälliger für Designänderungen ist als Daten. Natürlich brauchen wir irgendwann Funktionalität, aber Sie können Systeme entwerfen, bei denen der Großteil der Abhängigkeiten in Richtung Daten fließt. und nicht in Richtung Abstraktionen oder Funktionalität im Allgemeinen. Dies würde der Stabilität Vorrang vor der Kopplung geben.
Die stabilsten Funktionen, die ich jemals geschrieben habe (die Art, die ich seit Ende der 80er Jahre verwende und wieder verwende, ohne sie ändern zu müssen), waren alle, die sich auf Rohdaten stützten, wie eine Geometriefunktion, die nur ein Array von Daten akzeptierte floats und ganze Zahlen, nicht solche, die von einem komplexen Mesh
Objekt oder einer komplexen IMesh
Schnittstelle abhängen , oder Vektor / Matrix-Multiplikation, die nur davon abhängt, float[]
oder double[]
keine, die davon abhängt FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse
.