Wie entscheiden Sie, wo Funktionalität in ein Großprojekt gehören soll?

8

In meiner aktuellen Entwicklungssituation haben wir viele DLLs, ausführbare Dateien und statische Bibliotheken. Wie entscheiden Sie, was in eine DLL aufgenommen werden soll? Was soll in eine ausführbare Datei gehen? Warum separate Funktionen in verschiedenen ausführbaren Dateien? Ich hoffe, dass die Antwort kurz ist, aber dies ist anscheinend ein weitgehend meinungsgebundenes Thema.

Wie entscheiden Sie, wo sich die Funktionalität in einem Großprojekt befindet (mehr als eine ausführbare Datei)? Ich erwarte Antworten von "Gutes Design" über "Modularität" bis zu "Was auch immer das Management in ein Anforderungsdokument einfügt".

wfoster
quelle

Antworten:

13

Die Wahl zwischen einer Bibliothek und einer ausführbaren Datei ist relativ einfach: Ist es sinnvoll, den Code auszuführen, den Sie als eigenständiges Programm in eine ausführbare Datei einfügen möchten? Wenn nicht, sollte es wahrscheinlich eine Bibliothek sein. Im Allgemeinen würde ich eine dünne ausführbare Schicht gegenüber so vielen Bibliotheken wie nötig bevorzugen, da dies die spätere Wiederverwendung dieser Backend-Bibliotheken erleichtert und sie nicht an ein bestimmtes Programm gebunden sind.

Wenn Sie entscheiden, wie Sie Ihren Code zwischen Bibliotheken aufteilen möchten, ist der Artikel von Onkel Bob Martin über Granularität möglicherweise hilfreich.

Darin spricht er über die OO-Struktur und definiert verschiedene Prinzipien, die Ihnen helfen können, Ihren Code angemessen zu verpacken. Diese werden auch in seinem Buch Agile Principles, Patterns and Practices in C # ausführlicher behandelt .

Ich werde die folgenden Prinzipien zusammenfassen:

Das Reuse / Release Equivalence Principle (REP)

Das Granulat der Wiederverwendung ist das Granulat der Freisetzung. Nur Komponenten, die über ein Tracking-System freigegeben werden, können effektiv wiederverwendet werden. Dieses Granulat ist das Paket.

Onkel Bob definiert Wiederverwendung als die Möglichkeit, die wiederverwendete Bibliothek statisch oder dynamisch mit seinem Programm zu verknüpfen, ohne den Quellcode überprüfen zu müssen. Wenn eine neue Version der Bibliothek veröffentlicht wird, kann er sie einfach in sein System integrieren.

Wenn Bibliotheken auf diese Weise behandelt werden, werden nur verwandte Dinge im selben Paket zusammengehalten. Andernfalls müssen die Benutzer der Bibliothek möglicherweise ohne Grund auf eine neue Version aktualisieren oder einige Versionen zurückbleiben.

Das Common Reuse Principle (CRP)

Die Klassen in einem Paket werden zusammen wiederverwendet. Wenn Sie eine der Klassen in einem Paket wiederverwenden, verwenden Sie sie alle wieder.

Dieses Prinzip unterstützt das obige. Wenn Sie Klassen im selben Paket haben, die nicht miteinander verwandt sind, zwingen Sie möglicherweise die Benutzer Ihrer Bibliothek, unnötig zu aktualisieren.

Das Common Closure Principle (CCP)

Die Klassen in einem Paket sollten gegen dieselben Änderungen geschlossen werden. Eine Änderung, die sich auf ein Paket auswirkt, wirkt sich auf alle Klassen in diesem Paket aus.

Dieses Prinzip spricht von Wartbarkeit. Die Idee hier ist, Klassen danach zu gruppieren, wie sie sich möglicherweise ändern müssen. Auf diese Weise können Ihre Änderungen auf einen Teil der Anwendung lokalisiert und nicht überall verteilt werden.

Das Prinzip der azyklischen Abhängigkeiten (ACP)

Die Abhängigkeitsstruktur zwischen Paketen muss ein Directed Acyclic Graph (DAG) sein. Das heißt, es darf keine Zyklen in der Abhängigkeitsstruktur geben.

Wenn zyklische Abhängigkeiten nicht zugelassen werden, kann jedes Paket unabhängig entwickelt und für den Rest des Unternehmens "freigegeben" werden, wenn neue Änderungen vorliegen. Auf diese Weise sind nicht zwei Teams festgefahren und warten aufeinander, um die Arbeit zu beenden.

Adam Lear
quelle
2

Was wird es auf lange Sicht einfacher machen, den Code zu pflegen? Wenn Sie nicht verwandte Funktionen in derselben Komponente haben (z. B. DLL, Exe, kompilierte Einheit) und eine Funktion ändern müssen, wird dies die andere beschädigen? Wenn Sie eine Änderung an einer Komponente vornehmen, müssen Sie alle nachgeschalteten Komponenten abhängig von ALLEN Funktionen in dieser Komponente erneut testen. Wenn Sie die Funktionalität innerhalb jeder Komponente einschränken, ist das Ändern jeder Komponente weniger riskant.

Wenn Sie Komponenten auf eine kleine Anzahl eng verwandter Funktionen konzentrieren, können Sie viel einfacher nachweisen, dass sich eine Komponente so verhält, wie sie sollte.

Matthew Flynn
quelle
Denken Sie immer an das Szenario "DLL Hell". Das ist, wie Herr Flynn sagte, denken Sie, was passiert, wenn sich etwas ändert. Kennen Sie Ihre Abhängigkeiten immer sehr gut und stellen Sie sicher, dass Sie die Installationsszenarien ohne Schmerzen für den Endbenutzer abdecken können.
NoChance
2

Eine einfache Möglichkeit, sich einer Antwort zu nähern, besteht darin, zu erkennen, dass "DLL" für "Dynamic Link Library" steht und der Schlüsselbegriff "Library" lautet.

Was möchten Sie in einer normalen Bibliothek? Gute Bücher, die von vielen Lesern (Benutzern) geteilt werden; nützliche Gegenstände, die sich verstauben, bis sie von einem Leser (Benutzer) referenziert werden, um eine kritische Frage in einem vielleicht verzweifelten Moment zu beantworten; und Ressourcen, die Leser (Benutzer) mit anderen Ressourcen verbinden. Codebibliotheken sind ähnlich. Wir behalten häufig gemeinsam genutzten Code, spezialisierte oder hochwertige Referenzmodule und architektonische Framework-Ressourcen darin. Softwarebibliotheken können in verschiedenen Arten von Code-Artefakten dargestellt werden, z. B. in Skripten, statischen Bibliotheken, dynamischen Bibliotheken, Komponenten und Ressourcendateien.

Im Allgemeinen empfehle ich, Ihre ausführbaren Module so zu gestalten, dass sie sich wie Skripte verhalten. Sie umreißen und verwalten klar die Hauptstruktur und den Ablauf Ihres Systems, fordern jedoch Ressourcen aus Ihren Bibliotheken auf, um die Details zu verarbeiten. Ich finde diesen Ansatz besser, als die Logik auf hoher Ebene mit verwirrenden und übermäßig spezialisierten und technischen Implementierungsproblemen auf niedriger Ebene zu verwechseln.

Wenn Sie überlegen, wie Sie Ihren Code zwischen ausführbaren Dateien und Bibliotheken zuordnen, müssen Sie sowohl das logische als auch das physische Design berücksichtigen.

In objektorientierten Systemen ist es beispielsweise wichtig, Ihren Code logisch zu organisieren, um den richtigen Methoden in den richtigen Klassen die Verantwortlichkeiten korrekt zuzuweisen. Dies ist Teil des logischen Lösungsdesigns. Ihr logisches Design sollte klar, sauber und schlank sein und in einer Terminologie ausgedrückt werden, die sich gut auf die Domain Ihrer Benutzer bezieht.

Wenn Sie vorhaben, Ihr System tatsächlich auf der Site eines Benutzers zu installieren, müssen Sie möglicherweise ein physisches Design erstellen, das angibt, wie Sie Ihren Code in einer Reihe leicht implementierbarer Softwareressourcenobjekte (normalerweise Dateien) bündeln, die leicht gemischt und abgeglichen werden können auf die Bedürfnisse eines bestimmten Zielsystems. Das Bestimmen, welche Ressourcen zu welchem ​​Bereitstellungspaket gehören, erfordert im Allgemeinen einige spezifische Überlegungen zum physischen Design, die nichts direkt mit dem logischen Design zu tun haben. Beispielsweise möchten Sie möglicherweise bestimmte Bilddateiverarbeitungsbibliotheken austauschen, je nachdem, welcher Kunde einen bestimmten Satz von Bereitstellungsobjekten erhält.

In der Praxis werden Sie feststellen, dass ein gutes logisches Design normalerweise zu einem guten physischen Design führt.

Zusammenfassend ist das wichtigste Prinzip die intelligente Verpackung. Wenn Sie Ihre Codematerialien in stark verwandten Bibliotheken organisieren, erhalten Sie nützliche bereitstellbare Code-Artefakte, die Sie problemlos wiederverwenden, verschieben oder verschenken können.

John Tobler
quelle