Ist lose Kupplung ohne Use Cases ein Anti-Pattern?

22

Lose Kopplung ist für einige Entwickler das Allerheiligste an ausgereifter Software. Es ist auf jeden Fall eine gute Sache, wenn Code angesichts von Änderungen, die in absehbarer Zukunft wahrscheinlich auftreten werden, flexibler wird oder wenn Code-Duplikationen vermieden werden.

Auf der anderen Seite erhöhen Bemühungen, Komponenten lose zu koppeln, die Indirektion in einem Programm, wodurch dessen Komplexität erhöht wird, was es oft schwieriger zu verstehen und oft weniger effizient macht.

Halten Sie eine Konzentration auf lose Kopplung ohne Anwendungsfälle für lose Kopplung (z. B. Vermeidung von Codeduplizierungen oder Planung von Änderungen, die voraussichtlich in absehbarer Zukunft auftreten werden) für ein Anti-Pattern? Kann lose Kupplung unter den Dach von YAGNI fallen?

dsimcha
quelle
1
Die meisten Vorteile sind mit Kosten verbunden. Verwenden Sie, wenn der Nutzen die Kosten überwiegt.
LennyProgrammers
4
Sie haben die Kehrseite der losen Koppelmünze vergessen, und das high cohesioneine ohne das andere ist eine Verschwendung von Mühe und Darstellungen, für die es an grundlegendem Unverständnis mangelt.

Antworten:

13

Etwas.

Manchmal ist eine lose Kopplung ohne großen Aufwand in Ordnung, auch wenn Sie keine speziellen Anforderungen haben, die das Entkoppeln eines Moduls erfordern. Sozusagen die tief hängenden Früchte.

Auf der anderen Seite bedeutet Überentwicklung für lächerliche Veränderungsmengen eine Menge unnötiger Komplexität und Mühe. YAGNI trifft es, wie Sie sagen, auf den Kopf.

Fischtoaster
quelle
22

Ist Programmierpraxis X gut oder schlecht? Die Antwort lautet eindeutig immer "es kommt darauf an".

Wenn Sie sich Ihren Code ansehen und sich fragen, welche "Muster" Sie einfügen können, dann machen Sie es falsch.

Wenn Sie Ihre Software so erstellen, dass nicht verwandte Objekte nicht miteinander verwechseln, dann tun Sie es richtig.

Wenn Sie Ihre Lösung so "konstruieren", dass sie unendlich erweitert und geändert werden kann, machen Sie sie tatsächlich komplizierter.

Ich denke, am Ende des Tages bleibt Ihnen die einzige Wahrheit: Ist es mehr oder weniger kompliziert, die Objekte zu entkoppeln? Wenn es weniger kompliziert ist, sie zu koppeln, ist das die richtige Lösung. Wenn es weniger kompliziert ist, sie zu entkoppeln, ist das die richtige Lösung.

(Ich arbeite derzeit in einer relativ kleinen Codebasis, die einen einfachen Job auf sehr komplizierte Weise erledigt. Ein Teil der Kompliziertheit ist das mangelnde Verständnis der Begriffe "Kopplung" und "Zusammenhalt" des Originals Entwickler.)

Dash-Tom-Bang
quelle
4
Ich habe das erst neulich gemacht: Ich dachte, ich bräuchte eine direkte Verbindung zwischen zwei Klassen "nur für den Fall". Dann tat ich mir am Kopf weh, als ich versuchte, etwas zu debuggen, riss die Indirektion heraus und war viel glücklicher.
Frank Shearar
3

Ich denke, was Sie hier erreichen, ist das Konzept des Zusammenhalts . Hat dieser Code einen guten Zweck? Kann ich diesen Zweck verinnerlichen und das "Gesamtbild" der Vorgänge verstehen?

Dies könnte zu schwer zu verfolgendem Code führen, nicht nur, weil es viel mehr Quelldateien gibt (vorausgesetzt, es handelt sich um separate Klassen), sondern auch, weil keine einzelne Klasse einen Zweck zu haben scheint.

Aus einer agilen Perspektive könnte ich vorschlagen, dass eine solche lose Kopplung ein Anti-Muster ist. Ohne die Kohäsion oder sogar ohne Anwendungsfälle können Sie keine sinnvollen Komponententests schreiben und den Zweck des Codes nicht überprüfen. Agiler Code kann nun zu einer losen Kopplung führen, wenn beispielsweise eine testgetriebene Entwicklung verwendet wird. Wenn jedoch die richtigen Tests in der richtigen Reihenfolge erstellt wurden, liegt wahrscheinlich sowohl eine gute Kohäsion als auch eine lockere Kopplung vor. Und Sie sprechen nur von den Fällen, in denen eindeutig kein Zusammenhalt besteht.

Auch aus agiler Sicht möchten Sie diese künstliche Indirektionsebene nicht, da es sich um einen Aufwand handelt, der wahrscheinlich sowieso nicht benötigt wird. Es ist viel einfacher umzugestalten, wenn der Bedarf real ist.

Insgesamt möchten Sie eine hohe Kopplung innerhalb Ihrer Module und eine lose Kopplung zwischen ihnen. Ohne die Hochkopplung haben Sie wahrscheinlich keinen Zusammenhalt.

Macneil
quelle
1

Bei den meisten Fragen lautet die Antwort "es kommt darauf an". Im Großen und Ganzen werde ich es tun, wenn ich ein Design auf sinnvolle Weise logisch lose verknüpfen kann, ohne dass dies einen erheblichen Mehraufwand bedeutet. Das Vermeiden unnötiger Codekopplungen ist meines Erachtens ein absolut lohnendes Entwurfsziel.

Sobald es zu einer Situation kommt, in der es so aussieht, als ob Komponenten logisch eng miteinander verbunden sein sollten, suche ich nach einem überzeugenden Argument, bevor ich anfange, sie aufzulösen.

Ich denke, das Prinzip, an dem ich bei den meisten dieser Arten von Übungen arbeite, ist Trägheit. Ich habe eine Vorstellung davon, wie mein Code funktionieren soll, und wenn ich es so machen kann, ohne das Leben zu erschweren, werde ich es tun. Wenn dies die Entwicklung erschwert, aber die Wartung und zukünftige Arbeit erleichtert, werde ich versuchen, eine Vermutung anzustellen, ob es während der gesamten Lebensdauer des Codes mehr Arbeit geben wird, und diese als mein Leitfaden verwenden. Andernfalls müsste es ein bewusster Entwurfspunkt sein, mit dem es sich zu arbeiten lohnt.

Glenatron
quelle
1

Die einfache Antwort ist, dass eine lose Kopplung gut ist, wenn sie richtig durchgeführt wird.

Wenn das Prinzip einer Funktion, eines Zwecks, befolgt wird, sollte es einfach genug sein, zu verfolgen, was vor sich geht. Auch loser Koppelcode folgt selbstverständlich mühelos.

Einfache Entwurfsregeln: 1. Bauen Sie das Wissen über mehrere Elemente nicht zu einem einzigen Punkt zusammen (wie überall erwähnt, hängt davon ab), es sei denn, Sie erstellen eine Fassadenschnittstelle. 2. eine Funktion - ein Zweck (dieser Zweck kann wie in einer Fassade facettenreich sein) 3. ein Modul - ein klarer Satz zusammenhängender Funktionen - ein klarer Zweck 4. Wenn Sie ihn nicht einfach in einer Einheit testen können, hat er keinen ein einfacher Zweck

Alle diese Kommentare, die später leichter umzugestalten sind, sind eine Ladung Kabeljau. Sobald das Wissen an vielen Stellen, insbesondere in verteilten Systemen, vorhanden ist, werden die Kosten für das Refactoring, die Rollout-Synchronisierung und fast alle anderen Kosten so weit in den Hintergrund gedrängt, dass das System in den meisten Fällen aufgrund dessen in den Papierkorb gelangt.

Das Traurige an der Softwareentwicklung ist heutzutage, dass 90% der Menschen neue Systeme entwickeln und nicht in der Lage sind, alte Systeme zu verstehen, und dass sie nie in der Lage sind, zu einem so schlechten Gesundheitszustand zu gelangen, wenn das System ständig überarbeitet wird.

sweetfa
quelle
0

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:

Bildbeschreibung hier eingeben

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:

Bildbeschreibung hier eingeben

... 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 IMotionSchnittstelle 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 MeshObjekt oder einer komplexen IMeshSchnittstelle abhängen , oder Vektor / Matrix-Multiplikation, die nur davon abhängt, float[]oder double[]keine, die davon abhängt FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse.


quelle