Ein guter Entwickler, mit dem ich zusammenarbeite, erzählte mir kürzlich von Schwierigkeiten bei der Implementierung einer Funktion in einem von uns geerbten Code. Er sagte, das Problem sei, dass der Code schwer zu befolgen sei. Danach habe ich einen genaueren Blick auf das Produkt geworfen und festgestellt, wie schwierig es ist, den Codepfad zu erkennen.
Es wurden so viele Schnittstellen und abstrakte Ebenen verwendet, dass es ziemlich schwierig war zu verstehen, wo die Dinge begannen und endeten. Ich musste an die Zeiten denken, in denen ich mir frühere Projekte angeschaut hatte (bevor ich mich mit den Prinzipien sauberen Codes befasst hatte) und fand es äußerst schwierig, mich im Projekt zurechtzufinden, hauptsächlich, weil meine Code-Navigationstools mich immer an einer Schnittstelle landeten. Es wäre sehr aufwändig, die konkrete Implementierung zu finden oder zu ermitteln, wo etwas in einer Plug-in-Architektur verkabelt ist.
Ich weiß, dass einige Entwickler Abhängigkeitsinjektionsbehälter aus genau diesem Grund strikt ablehnen. Dies verwirrt den Pfad der Software so sehr, dass die Schwierigkeit der Code-Navigation exponentiell zunimmt.
Meine Frage ist: Wenn ein Framework oder Muster so viel Aufwand verursacht, ist es das wert? Ist es ein Symptom für ein schlecht umgesetztes Muster?
Ich denke, ein Entwickler sollte einen Blick auf das Gesamtbild werfen, was diese Abstraktionen für das Projekt bedeuten, damit sie die Frustration überwinden können. Normalerweise ist es jedoch schwierig, das Gesamtbild zu erkennen. Ich weiß, dass ich die Bedürfnisse von IOC und DI nicht mit TDD verkaufen konnte. Für diese Entwickler wird die Lesbarkeit des Codes durch die Verwendung dieser Tools nur viel zu stark eingeschränkt.
quelle
Nun, nicht genug Abstraktion und Ihr Code ist schwer zu verstehen, weil Sie nicht isolieren können, welche Teile was tun.
Zu viel Abstraktion und Sie sehen die Abstraktion, aber nicht den Code selbst, und dann ist es schwierig, dem eigentlichen Ausführungsthread zu folgen.
Um eine gute Abstraktion zu erzielen, sollte man KISSEN: Sehen Sie sich meine Antwort auf diese Fragen an, um zu wissen, was zu beachten ist , um solche Probleme zu vermeiden .
Ich denke, dass das Vermeiden tiefer Hierarchien und Benennungen der wichtigste Punkt ist, den Sie für den von Ihnen beschriebenen Fall betrachten sollten. Wenn die Abstraktionen gut benannt wären, müssten Sie nicht zu tief gehen, sondern nur auf die Abstraktionsebene, auf der Sie verstehen müssen, was passiert. Mit der Benennung können Sie feststellen, wo sich diese Abstraktionsebene befindet.
Das Problem entsteht im Low-Level-Code, wenn Sie wirklich alle Prozesse verstehen müssen. Dann hilft nur noch die Kapselung über klar isolierte Module.
quelle
Für mich ist es ein Kopplungsproblem und hängt mit der Granularität des Designs zusammen. Selbst die lockerste Form der Kopplung führt Abhängigkeiten von einer Sache zur anderen ein. Wenn das für Hunderte bis Tausende von Objekten gemacht wird, auch wenn sie alle relativ einfach sind, sich an SRP halten und selbst wenn alle Abhängigkeiten zu stabilen Abstraktionen fließen, ergibt sich eine Codebasis, die als zusammenhängendes Ganzes nur sehr schwer zu erklären ist.
Es gibt praktische Dinge, die Ihnen dabei helfen, die Komplexität einer Codebasis einzuschätzen, die in der theoretischen SE nicht häufig erörtert wird, z. B. wie tief Sie in den Aufrufstapel vordringen können, bevor Sie das Ende erreichen, und wie tief Sie gehen müssen, bevor Sie es können Mit viel Vertrauen sollten Sie alle möglichen Nebenwirkungen verstehen, die auf dieser Ebene des Aufrufstapels auftreten können, auch im Falle einer Ausnahme.
Und ich habe nur nach meiner Erfahrung festgestellt, dass flachere Systeme mit flacheren Aufrufstapeln viel einfacher zu überlegen sind. Ein extremes Beispiel wäre ein Entity-Component-System, bei dem Komponenten nur Rohdaten sind. Nur Systeme sind funktionsfähig, und bei der Implementierung und Verwendung eines ECS war es für mich das mit Abstand einfachste System überhaupt, darüber nachzudenken, wann komplexe Codebasen, die sich über Hunderttausende von Codezeilen erstrecken, auf ein paar Dutzend Systeme zusammenwachsen enthalten alle Funktionen.
Zu viele Dinge bieten Funktionalität
Als ich in früheren Codebasen gearbeitet habe, war die Alternative ein System mit Hunderten bis Tausenden von größtenteils winzigen Objekten im Gegensatz zu ein paar Dutzend sperrigen Systemen, bei denen einige Objekte nur zum Weitergeben von Nachrichten von einem Objekt an ein anderes verwendet wurden (
Message
z. B. ein Objekt , das seine hatte) eigene öffentliche Schnittstelle). Das erhalten Sie im Grunde genommen analog, wenn Sie das ECS auf einen Punkt zurücksetzen, an dem Komponenten Funktionalität haben und jede eindeutige Kombination von Komponenten in einer Entität einen eigenen Objekttyp ergibt. Und das wird tendenziell zu kleineren, einfacheren Funktionen führen, die von endlosen Kombinationen von Objekten geerbt und bereitgestellt werden, die jugendliche Ideen modellieren (Particle
Objekt vs.Physics System
, z.B). Es kann jedoch auch zu einem komplexen Diagramm von gegenseitigen Abhängigkeiten führen, das es schwierig macht, zu überlegen, was auf breiter Ebene passiert, einfach weil es so viele Dinge in der Codebasis gibt, die tatsächlich etwas tun und daher etwas falsch machen können - - Typen, die keine "Datentypen", sondern "Objekttypen" mit zugehöriger Funktionalität sind. Typen, die als reine Daten ohne zugehörige Funktionalität dienen, können möglicherweise nichts falsch machen, da sie selbst nichts tun können.Reine Schnittstellen helfen diesem Verständlichkeitsproblem nicht so sehr, denn selbst wenn dies die "Kompilierzeitabhängigkeiten" weniger kompliziert macht und mehr Spielraum für Änderungen und Erweiterungen bietet, werden die "Laufzeitabhängigkeiten" und Interaktionen dadurch nicht weniger kompliziert. Das Client-Objekt ruft weiterhin Funktionen für ein konkretes Kontoobjekt auf, selbst wenn diese aufgerufen werden
IAccount
. Polymorphismus und abstrakte Schnittstellen haben ihre Verwendung, aber sie entkoppeln die Dinge nicht so, wie es Ihnen wirklich hilft, über alle Nebenwirkungen zu urteilen, die zu einem bestimmten Zeitpunkt auftreten können. Um diese Art der effektiven Entkopplung zu erreichen, benötigen Sie eine Codebasis, die viel weniger Funktionen enthält.Mehr Daten, weniger Funktionalität
Daher habe ich den ECS-Ansatz, auch wenn Sie ihn nicht vollständig anwenden, als äußerst hilfreich empfunden, da er Hunderte von Objekten in reine Rohdaten mit umfangreichen, gröber konzipierten Systemen umwandelt, die all das bieten Funktionalität. Es maximiert die Anzahl der "Datentypen" und minimiert die Anzahl der "Objekttypen" und minimiert daher absolut die Anzahl der Stellen in Ihrem System, an denen tatsächlich Fehler auftreten können. Das Endergebnis ist ein sehr "flaches" System ohne komplexe Abhängigkeitsdiagramme, nur Systeme zu Komponenten, niemals umgekehrt und niemals Komponenten zu anderen Komponenten. Grundsätzlich sind es viel mehr Rohdaten und viel weniger Abstraktionen, die die Funktionalität der Codebasis auf Schlüsselbereiche, Schlüsselabstraktionen, zentralisieren und reduzieren.
30 einfachere Dinge sind nicht notwendigerweise einfacher zu überlegen als eine komplexere Sache, wenn diese 30 einfacheren Dinge miteinander zusammenhängen, während die komplexe Sache für sich allein steht. Mein Vorschlag ist also, die Komplexität von den Wechselwirkungen zwischen Objekten auf voluminösere Objekte zu übertragen, die mit nichts anderem interagieren müssen, um eine Massenentkopplung zu erreichen, und zwar auf ganze "Systeme" (wohlgemerkt nicht auf Monolithen und Gottobjekte) keine Klassen mit 200 Methoden, sondern etwas wesentlich Höheres als a
Message
oder aParticle
(trotz minimalistischer Oberfläche). Bevorzugen Sie einfachere alte Datentypen. Je mehr Sie von diesen abhängig sind, desto weniger Kopplung erhalten Sie. Auch wenn dies einigen SE-Vorstellungen widerspricht, ich habe festgestellt, dass es sehr hilfreich ist.quelle
Vielleicht ist es ein Symptom für die Wahl der falschen Programmiersprache.
quelle
Ein schlechtes Verständnis der Entwurfsmuster ist in der Regel eine Hauptursache für dieses Problem. Eines der schlimmsten Dinge, die ich bei diesem Yo-Yo'ing und Hüpfen von Schnittstelle zu Schnittstelle ohne sehr konkrete Daten dazwischen gesehen habe, war eine Erweiterung für Oracle's Grid Control.
Es sah ehrlich gesagt so aus, als hätte jemand eine abstrakte Factory-Methode und einen Dekorateur-Muster-Orgasmus in meinem gesamten Java-Code gehabt. Und ich fühlte mich genauso hohl und allein.
quelle
Ich würde auch davor warnen, IDE-Funktionen zu verwenden, die es einfach machen, Dinge zu abstrahieren.
quelle