Ich sehe immer, dass Abstraktion eine sehr nützliche Funktion ist, die OO für die Verwaltung der Codebasis bietet. Aber wie werden große Nicht-OO-Codebasen verwaltet? Oder werden diese irgendwann einfach zu einem " Big Ball of Mud "?
Update:
Anscheinend denkt jeder, dass 'Abstraktion' nur Modularisierung oder Verstecken von Daten ist. Aber meiner Meinung nach bedeutet dies auch die Verwendung von 'Abstract Classes' oder 'Interfaces', was ein Muss für die Abhängigkeitsinjektion und damit für das Testen ist. Wie verwalten Nicht-OO-Codebasen dies? Neben der Abstraktion hilft die Kapselung auch bei der Verwaltung großer Codebasen, da sie die Beziehung zwischen Daten und Funktionen definiert und einschränkt.
Mit C ist es sehr gut möglich, Pseudo-OO-Code zu schreiben. Ich weiß nicht viel über andere Nicht-OO-Sprachen. Ist es also DER Weg, große C-Code-Basen zu verwalten?
quelle
Antworten:
Sie scheinen zu glauben, dass OOP das einzige Mittel ist, um eine Abstraktion zu erreichen.
Während OOP das sicherlich sehr gut kann, ist es keineswegs der einzige Weg. Große Projekte können auch durch kompromisslose Modularisierung verwaltet werden (siehe Perl oder Python, die sich beide ausgezeichnet haben, ebenso wie funktionale Sprachen wie ML und Haskell) und durch die Verwendung von Mechanismen wie Vorlagen (in C ++).
quelle
static
angehängten Zugriffsmodifikator.Module, (externe / interne) Funktionen, Unterprogramme ...
Wie Konrad sagte, ist OOP nicht die einzige Möglichkeit, große Codebasen zu verwalten. Tatsächlich wurde viel Software davor geschrieben (vor C ++ *).
quelle
Das Modularitätsprinzip ist nicht auf objektorientierte Sprachen beschränkt.
quelle
Realistisch gesehen sind dies entweder seltene Änderungen (siehe Berechnungen zur Altersvorsorge bei der Sozialversicherung) und / oder tief verwurzeltes Wissen, da die Leute, die ein solches System aufrechterhalten, dies bereits seit einiger Zeit tun (zynische Haltung ist Arbeitsplatzsicherheit).
Bessere Lösungen sind wiederholbare Validierungen, dh automatisierte Tests (z. B. Komponententests) und menschliche Tests, die vorgeschriebenen Schritten folgen (z. B. Regressionstests), "anstatt herumzuklicken und zu sehen, was kaputt geht".
Um zu einer Art automatisiertem Testen mit einer vorhandenen Codebasis überzugehen, empfehle ich, Michael Feathers Working Effectively with Legacy Code zu lesen , in dem die Herangehensweisen beschrieben werden, um vorhandene Codebasen bis zu einer Art reproduzierbarem Testframework (OO) zu bringen oder nicht. Dies führt zu den Ideen, auf die andere geantwortet haben, wie z. B. Modularisierung, aber das Buch beschreibt den richtigen Ansatz, um dies zu tun, ohne die Dinge zu beschädigen.
quelle
Die Abhängigkeitsinjektion auf der Basis von Interfaces oder abstrakten Klassen ist zwar eine sehr gute Methode zum Testen, aber nicht erforderlich. Vergessen Sie nicht, dass fast jede Sprache entweder einen Funktionszeiger oder ein Eval hat, das alles kann, was Sie mit einer Schnittstelle oder einer abstrakten Klasse tun können (das Problem ist, dass sie mehr können , einschließlich vieler schlechter Dinge, und dass sie nicht funktionieren). selbst Metadaten bereitstellen). Ein solches Programm kann mit diesen Mechanismen tatsächlich eine Abhängigkeitsinjektion erzielen.
Ich habe festgestellt, dass der strikte Umgang mit Metadaten sehr hilfreich ist. In OO-Sprachen werden die Beziehungen zwischen Codebits (bis zu einem gewissen Grad) durch die Klassenstruktur definiert, und zwar auf eine Weise, die standardisiert genug ist, um Dinge wie eine Reflection-API zu haben. In prozeduralen Sprachen kann es hilfreich sein, diese selbst zu erfinden.
Ich habe auch festgestellt, dass die Codegenerierung in einer prozeduralen Sprache (im Vergleich zu einer objektorientierten Sprache) viel hilfreicher ist. Dies garantiert, dass Metadaten mit dem Code synchron sind (da sie zum Generieren verwendet werden) und Ihnen so etwas wie die Schnittpunkte der aspektorientierten Programmierung geben - eine Stelle, an der Sie Code einfügen können, wenn Sie ihn benötigen. Manchmal ist es die einzige Möglichkeit, DRY-Programmierung in einer solchen Umgebung durchzuführen, die ich herausfinden kann.
quelle
Wie Sie kürzlich herausgefunden haben , sind Funktionen erster Ordnung alles, was Sie für die Inversion von Abhängigkeiten benötigen.
C unterstützt Funktionen erster Ordnung und bis zu einem gewissen Grad sogar Abschlüsse . C-Makros sind eine leistungsstarke Funktion für die allgemeine Programmierung, wenn sie mit der erforderlichen Sorgfalt behandelt werden.
Es ist alles da. SGLIB ist ein gutes Beispiel dafür, wie man mit C wiederverwendbaren Code schreiben kann. Und ich glaube, da draußen gibt es noch viel mehr.
quelle
Auch ohne Abstraktion sind die meisten Programme in Abschnitte unterteilt. Diese Abschnitte beziehen sich normalerweise auf bestimmte Aufgaben oder Aktivitäten, und Sie arbeiten auf die gleiche Weise, wie Sie auf die spezifischsten Teile der abstrahierten Programme arbeiten würden.
In kleinen bis mittelgroßen Projekten ist dies mitunter einfacher mit einer puristischen OO-Implementierung.
quelle
Abstraktion, abstrakte Klassen, Abhängigkeitsinjektion, Kapselung, Schnittstellen usw. sind nicht die einzige Möglichkeit, große Codebasen zu steuern. das ist gerecht und objektorientiert.
Das Hauptgeheimnis ist zu vermeiden, OOP zu denken, wenn Sie Nicht-OOP codieren.
Modularität ist der Schlüssel in Nicht-OO-Sprachen. In C wird dies erreicht, so wie David Thornley es gerade in einem Kommentar erwähnt hat:
quelle
Eine Möglichkeit zum Verwalten von Code besteht darin, ihn gemäß der MVC-Architektur (Model-View-Controller) in die folgenden Codetypen zu zerlegen.
Diese Methode der Code-Organisation eignet sich gut für Software, die in einer beliebigen OO- oder Nicht-OO-Sprache geschrieben wurde, da in allen Bereichen häufig gemeinsame Entwurfsmuster verwendet werden. Außerdem sind diese Arten von Codegrenzen mit Ausnahme von Algorithmen häufig am lockersten gekoppelt, da sie die Datenformate von den Eingaben zum Modell und dann zu den Ausgaben verknüpfen.
Systementwicklungen haben oft die Form, dass Ihre Software mehr Arten von Eingaben oder mehr Arten von Ausgaben handhabt, aber die Modelle und Ansichten sind die gleichen und die Controller verhalten sich sehr ähnlich. Oder ein System muss im Laufe der Zeit immer mehr verschiedene Arten von Ausgaben unterstützen, obwohl die Eingaben, Modelle und Algorithmen identisch sind und die Steuerungen und Ansichten ähnlich sind. Oder ein System kann erweitert werden, um neue Modelle und Algorithmen für denselben Satz von Eingaben, ähnlichen Ausgaben und ähnlichen Ansichten hinzuzufügen.
Eine Möglichkeit, wie die OO-Programmierung die Code-Organisation erschwert, besteht darin, dass einige Klassen stark an die persistenten Datenstrukturen gebunden sind und andere nicht. Wenn die persistenten Datenstrukturen in engem Zusammenhang mit Dingen wie kaskadierenden 1: N-Beziehungen oder m: n-Beziehungen stehen, ist es sehr schwierig, Klassengrenzen zu bestimmen, bis Sie einen wichtigen und aussagekräftigen Teil Ihres Systems codiert haben, bevor Sie wissen, dass Sie es richtig verstanden haben . Jede Klasse, die an die persistenten Datenstrukturen gebunden ist, kann nur schwer entwickelt werden, wenn sich das Schema der persistenten Daten ändert. Klassen, die Algorithmen, Formatierungen und Parsing verarbeiten, sind weniger anfällig für Änderungen im Schema der persistenten Datenstrukturen. Durch die Verwendung einer MVC-Art von Codeorganisation werden die unordentlichsten Codeänderungen im Modellcode besser isoliert.
quelle
Wenn Sie in Sprachen arbeiten, denen eingebaute Struktur- und Organisationsfunktionen fehlen (z. B. wenn Namespaces, Pakete, Assemblys usw. fehlen) oder wenn diese nicht ausreichen, um eine Codebasis dieser Größe unter Kontrolle zu halten, ist die natürliche Reaktion die Entwicklung unsere eigenen Strategien, um den Code zu organisieren.
Diese Organisationsstrategie enthält wahrscheinlich Standards, die sich darauf beziehen, wo verschiedene Dateien aufbewahrt werden sollen, was vor / nach bestimmten Arten von Vorgängen geschehen muss und Namenskonventionen und andere Codierungsstandards sowie vieles, was so eingerichtet ist - Leg dich nicht damit an! " Typenkommentare - die gültig sind, solange sie erklären, warum!
Da die Strategie höchstwahrscheinlich auf die spezifischen Anforderungen des Projekts zugeschnitten sein wird (Menschen, Technologien, Umgebung usw.), ist es schwierig, eine einheitliche Lösung für die Verwaltung großer Codebasen zu finden.
Aus diesem Grund denke ich, der beste Rat ist, die projektspezifische Strategie zu übernehmen und ihre Verwaltung zu einer Schlüsselpriorität zu machen: Dokumentieren Sie die Struktur, warum dies so ist, die Prozesse für die Vornahme von Änderungen, prüfen Sie sie, um sicherzustellen, dass sie eingehalten werden. und entscheidend: Ändern Sie es, wenn es geändert werden muss.
Wir sind in der Regel mit dem Umgestalten von Klassen und Methoden vertraut, aber mit einer großen Codebasis in einer solchen Sprache ist es die Organisationsstrategie selbst (einschließlich Dokumentation), die nach Bedarf umgestaltet werden muss.
Die Überlegungen sind die gleichen wie beim Refactoring: Sie entwickeln eine mentale Blockade für die Arbeit an kleinen Teilen des Systems, wenn Sie der Meinung sind, dass die gesamte Organisation des Systems durcheinander ist, und lassen es schließlich zu, dass es sich verschlechtert (zumindest ist das meine Einstellung) es).
Die Vorbehalte sind auch die gleichen: Verwenden Sie Regressionstests, stellen Sie sicher, dass Sie leicht zurücksetzen können, wenn das Refactoring fehlschlägt, und gestalten Sie dies so, dass das Refactoring an erster Stelle erleichtert wird (oder Sie tun es einfach nicht!).
Ich bin damit einverstanden, dass dies viel schwieriger ist als das Umgestalten von direktem Code, und es ist schwieriger, die Zeit für Manager / Kunden zu validieren / zu verbergen, die möglicherweise nicht verstehen, warum dies getan werden muss, aber dies sind auch die Arten von Projekten, die am anfälligsten für Softwarefäule sind verursacht durch unflexible Top-Level-Designs ...
quelle
Wenn Sie nach der Verwaltung einer großen Codebasis fragen, möchten Sie wissen, wie Sie Ihre Codebasis auf einer relativ groben Ebene gut strukturieren können (Bibliotheken / Module / Aufbau von Subsystemen / Verwendung von Namespaces / Verwendung der richtigen Dokumente an den richtigen Stellen) etc.). OO-Prinzipien, insbesondere "abstrakte Klassen" oder "Schnittstellen", sind Prinzipien, um Ihren Code intern auf einer sehr detaillierten Ebene sauber zu halten. Daher unterscheiden sich die Techniken zum Verwalten einer großen Codebasis nicht für OO- oder Nicht-OO-Code.
quelle
Wie es gehandhabt wird, ist, dass Sie die Grenzen der Elemente herausfinden, die Sie verwenden. Zum Beispiel haben die folgenden Elemente in C ++ einen klaren Rand und alle Abhängigkeiten außerhalb des Randes müssen sorgfältig durchdacht werden:
Durch die Kombination dieser Elemente und das Erkennen ihrer Grenzen können Sie in c ++ nahezu jeden gewünschten Programmierstil erstellen.
Ein Beispiel hierfür ist, dass eine Funktion erkennen soll, dass es schlecht ist, andere Funktionen von einer Funktion aufzurufen, da dies zu Abhängigkeiten führt. Stattdessen sollten Sie nur Mitgliedsfunktionen der Parameter der ursprünglichen Funktion aufrufen.
quelle
Die größte technische Herausforderung ist das Namespace-Problem. Eine teilweise Verknüpfung kann verwendet werden, um dies zu umgehen. Der bessere Ansatz ist das Entwerfen unter Verwendung von Codierungsstandards. Ansonsten werden alle Symbole zu einem Durcheinander.
quelle
Emacs ist ein gutes Beispiel dafür:
Emacs Lisp-Tests verwenden
skip-unless
undlet-bind
, um Feature-Erkennung und Test-Fixtures durchzuführen:Wie ist SQLite. Hier ist das Design:
sqlite3_open () → Öffnet eine Verbindung zu einer neuen oder vorhandenen SQLite-Datenbank. Der Konstruktor für sqlite3.
sqlite3 → Das Datenbankverbindungsobjekt. Erstellt von sqlite3_open () und zerstört von sqlite3_close ().
sqlite3_stmt → Das vorbereitete Anweisungsobjekt. Erstellt von sqlite3_prepare () und zerstört von sqlite3_finalize ().
sqlite3_prepare () → Kompilieren Sie SQL-Text in Bytecode, um die Datenbank abzufragen oder zu aktualisieren. Der Konstruktor für sqlite3_stmt.
sqlite3_bind () → Anwendungsdaten in Parametern des ursprünglichen SQL speichern.
sqlite3_step () → Bringt ein sqlite3_stmt zur nächsten Ergebniszeile oder zum Abschluss.
sqlite3_column () → Spaltenwerte in der aktuellen Ergebniszeile für einen sqlite3_stmt.
sqlite3_finalize () → Destruktor für sqlite3_stmt.
sqlite3_exec () → Eine Wrapper-Funktion, die sqlite3_prepare (), sqlite3_step (), sqlite3_column () und sqlite3_finalize () für eine Zeichenfolge aus einer oder mehreren SQL-Anweisungen ausführt.
sqlite3_close () → Destruktor für sqlite3.
SQLite verwendet eine Vielzahl von Testtechniken, darunter:
Verweise
Konzeptuelle Ansichten von Emacs 'Architektur (pdf)
Die SQLite OS-Schnittstelle oder "VFS"
Der virtuelle Tabellenmechanismus von SQLite
Eine Einführung in die SQLite C / C ++ - Schnittstelle
Emacs-Elisp-Programmierung · GitHub
Tests und ihre Umgebung - Emacs Lisp Regressionstests
Wie wird SQLite getestet?
quelle