Ich habe kürzlich eine Website über die Entwicklung von sauberem Code gelesen (ich habe hier keinen Link eingefügt, da dieser nicht auf Englisch ist).
Eines der auf dieser Website beworbenen Prinzipien ist das Open-Closed-Prinzip : Jede Softwarekomponente sollte zur Erweiterung geöffnet und zur Änderung geschlossen sein. Wenn wir beispielsweise eine Klasse implementiert und getestet haben, sollten wir sie nur ändern, um Fehler zu beheben oder neue Funktionen hinzuzufügen (z. B. neue Methoden, die die vorhandenen nicht beeinflussen). Die vorhandene Funktionalität und Implementierung sollte nicht geändert werden.
Normalerweise wende ich dieses Prinzip an, indem ich eine Schnittstelle I
und eine entsprechende Implementierungsklasse definiere A
. Wenn die Klasse A
stabil geworden ist (implementiert und getestet), ändere ich sie normalerweise nicht zu stark (möglicherweise überhaupt nicht), d. H.
- Wenn neue Anforderungen eintreffen (z. B. Leistung oder eine völlig neue Implementierung der Schnittstelle), die große Änderungen am Code erfordern, schreibe ich eine neue Implementierung
B
und verwende sieA
so lange, wie sieB
nicht ausgereift ist. WennB
es ausgereift ist, muss nur noch geändert werden, wieI
instanziiert wird. - Wenn die neuen Anforderungen auch eine Änderung der Schnittstelle vorschlagen, definiere ich eine neue Schnittstelle
I'
und eine neue ImplementierungA'
. AlsoI
,A
sind gesperrt und bleibt die Umsetzung für das Produktionssystem solangeI'
undA'
ist nicht stabil genug , um sie zu ersetzen.
Angesichts dieser Beobachtung war ich ein wenig überrascht, dass die Webseite dann die Verwendung komplexer Refactorings vorschlug , "... weil es nicht möglich ist, Code direkt in seiner endgültigen Form zu schreiben."
Gibt es nicht einen Widerspruch / Konflikt zwischen der Durchsetzung des Open / Closed-Prinzips und dem Vorschlag, komplexe Refactorings als Best Practice zu verwenden? Oder die Idee hier ist, dass man während der Entwicklung einer Klasse komplexe Refactorings verwenden kann A
, aber wenn diese Klasse erfolgreich getestet wurde, sollte sie eingefroren werden?
Das Open-Closed-Prinzip ist eher ein Indikator dafür, wie gut Ihre Software gestaltet ist . kein Prinzip, dem man buchstäblich folgen kann. Dies ist auch ein Prinzip, das uns davon abhält, vorhandene Schnittstellen versehentlich zu ändern (Klassen und Methoden, die Sie aufrufen, und wie Sie erwarten, dass sie funktionieren).
Ziel ist es, hochwertige Software zu schreiben. Eine dieser Eigenschaften ist die Erweiterbarkeit. Dies bedeutet, dass es einfach ist, Code hinzuzufügen, zu entfernen und zu ändern, wobei diese Änderungen in der Regel auf so wenige vorhandene Klassen wie möglich beschränkt sind. Das Hinzufügen von neuem Code ist weniger riskant als das Ändern von vorhandenem Code. In dieser Hinsicht ist Open-Closed eine gute Sache. Aber über welchen Code sprechen wir genau? Das Verbrechen, OC zu verletzen, ist viel geringer, wenn Sie einer Klasse neue Methoden hinzufügen können, anstatt vorhandene ändern zu müssen.
OC ist fraktal . Es Äpfel in allen Tiefen Ihres Designs. Jeder geht davon aus, dass es nur auf Klassenebene angewendet wird. Es ist jedoch sowohl auf Methodenebene als auch auf Baugruppenebene anwendbar.
Eine zu häufige Verletzung von OC auf der entsprechenden Ebene deutet darauf hin, dass es möglicherweise an der Zeit ist, eine Umgestaltung vorzunehmen . "Angemessenes Niveau" ist ein Urteilsspruch, der alles mit Ihrem Gesamtdesign zu tun hat.
Open-Closed zu folgen bedeutet wörtlich, dass die Anzahl der Klassen explodieren wird. Sie werden unnötigerweise (Großbuchstaben "I") Schnittstellen erstellen. Sie werden am Ende einige Funktionen haben, die über Klassen verteilt sind, und Sie müssen dann viel mehr Code schreiben, um alles miteinander zu verbinden. Irgendwann wird Ihnen klar, dass es besser gewesen wäre, die ursprüngliche Klasse zu ändern.
quelle
Das Open-Closed-Prinzip scheint ein Prinzip zu sein, das vor der Verbreitung von TDD aufgetaucht ist. Die Idee ist, dass es riskant ist, Code umzugestalten, weil Sie möglicherweise etwas kaputt machen, sodass es sicherer ist, vorhandenen Code unverändert zu lassen und ihn einfach zu ergänzen. In Ermangelung von Tests ist dies sinnvoll. Der Nachteil dieses Ansatzes ist die Code-Atrophie. Jedes Mal, wenn Sie eine Klasse erweitern, anstatt sie umzugestalten, erhalten Sie eine zusätzliche Ebene. Sie schrauben einfach Code oben drauf. Jedes Mal, wenn Sie mehr Code anbringen, erhöhen Sie die Wahrscheinlichkeit von Duplikaten. Vorstellen; In meiner Codebasis befindet sich ein Dienst, den ich verwenden möchte. Ich finde, dass er nicht das hat, was ich möchte. Daher erstelle ich eine neue Klasse, um ihn zu erweitern und meine neuen Funktionen einzuschließen. Ein anderer Entwickler kommt später und möchte denselben Service auch nutzen. Leider ziehen sie nicht an Ich weiß nicht, dass meine erweiterte Version existiert. Sie codieren gegen die ursprüngliche Implementierung, benötigen aber auch eine der Funktionen, die ich codiert habe. Anstatt meine Version zu verwenden, erweitern sie jetzt auch die Implementierung und fügen die neue Funktion hinzu. Jetzt haben wir 3 Klassen, die ursprüngliche eine und zwei neue Versionen, die einige doppelte Funktionen haben. Befolgen Sie das Open / Closed-Prinzip, und diese Duplizierung wird sich während der gesamten Laufzeit des Projekts weiter aufbauen und zu einer unnötig komplexen Codebasis führen.
Mit einem gut getesteten System muss diese Code-Atrophie nicht auftreten. Sie können den Code sicher umgestalten, sodass Ihr Design neuen Anforderungen gerecht wird, anstatt ständig neuen Code einbinden zu müssen. Dieser Entwicklungsstil wird als emergentes Design bezeichnet und führt zu Codebasen, die während ihrer gesamten Lebensdauer in guter Form bleiben können, anstatt nach und nach Cruft zu sammeln.
quelle
In Laienwörtern:
A. Das O / C-Prinzip bedeutet, dass die Spezialisierung durch Erweiterung und nicht durch Änderung einer Klasse erfolgen muss, um sie an spezielle Anforderungen anzupassen.
B. Das Hinzufügen fehlender (nicht spezialisierter) Funktionen bedeutet, dass das Design nicht vollständig war und Sie es der Basisklasse hinzufügen müssen, offensichtlich ohne den Vertrag zu verletzen. Ich denke, das verstößt nicht gegen das Prinzip.
C. Refactoring verstößt nicht gegen das Prinzip.
Wenn ein Design beispielsweise nach einiger Zeit in der Produktion reift :
quelle
Für mich ist das Open-Closed-Prinzip eine Richtlinie, keine feste Regel.
In Bezug auf den offenen Teil des Prinzips verletzen Abschlussklassen in Java und Klassen in C ++ mit allen als privat deklarierten Konstruktoren den offenen Teil des Open-Closed-Prinzips. Es gibt gute solide Anwendungsfälle (Hinweis: solide, nicht FEST) für Abschlussklassen. Das Entwerfen für Erweiterbarkeit ist wichtig. Dies erfordert jedoch viel Voraussicht und Mühe, und Sie umgehen immer die Linie der Verletzung von YAGNI (Sie werden es nicht brauchen) und injizieren den Code-Geruch spekulativer Allgemeinheit. Sollten wichtige Softwarekomponenten zur Erweiterung geöffnet sein? Ja. Alle? Nein, das an sich ist spekulative Allgemeinheit.
In Bezug auf den geschlossenen Teil ist es eine gute Idee, das Verhalten nicht zu ändern, wenn von Version 2.0 zu 2.1 zu 2.2 zu 2.3 eines Produkts gewechselt wird. Benutzer mögen es wirklich nicht, wenn jede kleinere Version ihren eigenen Code bricht. Unterwegs stellt man jedoch häufig fest, dass die anfängliche Implementierung in Version 2.0 grundlegend fehlerhaft war oder dass externe Einschränkungen, die das ursprüngliche Design einschränkten, nicht mehr gelten. Grinsen und ertragen Sie es und behalten Sie dieses Design in Release 3.0 bei, oder machen Sie 3.0 in gewisser Hinsicht nicht abwärtskompatibel? Abwärtskompatibilität kann eine große Einschränkung sein. Wichtige Release-Grenzen sind der Ort, an dem es akzeptabel ist, die Abwärtskompatibilität zu brechen. Sie müssen darauf achten, dass Ihre Benutzer dadurch möglicherweise verärgert werden. Es muss einen guten Grund dafür geben, warum dieser Bruch mit der Vergangenheit notwendig ist.
quelle
Refactoring ändert per Definition die Codestruktur, ohne das Verhalten zu ändern. Wenn Sie also umgestalten, fügen Sie keine neuen Funktionen hinzu.
Was Sie als Beispiel für das Open Close-Prinzip getan haben, klingt in Ordnung. Bei diesem Prinzip geht es darum, vorhandenen Code um neue Funktionen zu erweitern.
Verstehen Sie diese Antwort jedoch nicht falsch. Ich impliziere nicht, dass Sie nur Features oder Refactoring für große Datenmengen durchführen sollten. Die gebräuchlichste Art der Programmierung besteht darin, eine kleine Funktion auszuführen, als sofort ein wenig umzugestalten (natürlich kombiniert mit Tests, um sicherzustellen, dass Sie kein Verhalten geändert haben). Komplexes Refactoring bedeutet nicht "großes" Refactoring, sondern die Anwendung komplizierter und gut durchdachter Refactoring-Techniken.
Über die SOLID-Prinzipien. Sie sind wirklich gute Richtlinien für die Softwareentwicklung, aber sie sind keine religiösen Regeln, die blind befolgt werden müssen. Manchmal, oft, nachdem Sie eine zweite, dritte und n-te Funktion hinzugefügt haben, stellen Sie fest, dass Ihr ursprüngliches Design, selbst wenn es Open-Close respektiert, andere Prinzipien oder Softwareanforderungen nicht berücksichtigt. Es gibt Punkte in der Entwicklung eines Designs und einer Software, an denen komplexere Änderungen vorgenommen werden müssen. Der springende Punkt ist, diese Probleme so schnell wie möglich zu finden und zu realisieren und Refactoring-Techniken so gut wie möglich anzuwenden.
Perfektes Design gibt es nicht. Es gibt kein solches Design, das alle bestehenden Prinzipien oder Muster respektieren kann und sollte. Das ist Kodierungsutopie.
Ich hoffe, diese Antwort hat Ihnen in Ihrem Dilemma geholfen. Bei Bedarf können Sie gerne um Klarstellung bitten.
quelle
B
und ersetzen Sie, wenn dies fertig ist, die alte ImplementierungA
durch die neue ImplementierungB
(dies ist eine Verwendung von Schnittstellen).A
Der Code kann als Grundlage fürB
den Code dienen, und dann kann ich das Refactoring für denB
Code während seiner Entwicklung verwenden, aber ich denke, dass der bereits getesteteA
Code eingefroren bleiben sollte.B
auf CodeA
als Weiterentwicklung von basiert, sollte erA
beiB
VeröffentlichungA
entfernt und nie wieder verwendet werden. Clients, die früher verwendet haben,A
werden nur verwenden,B
ohne über die Änderung Bescheid zu wissen, da die BenutzeroberflächeI
nicht geändert wurde (vielleicht ein bisschen Liskov-Substitutionsprinzip hier? ... das L von SOLID)Nach meinem Verständnis wird die OCP nicht beschädigt, wenn Sie der vorhandenen Klasse neue Methoden hinzufügen. Ich bin jedoch etwas verwirrt mit dem Hinzufügen neuer Variablen in der Klasse. Wenn Sie jedoch die vorhandene Methode und die vorhandenen Parameter in der vorhandenen Methode ändern, wird das OCP mit Sicherheit beschädigt, da der Code bereits getestet und übergeben wurde, wenn wir die Methode absichtlich ändern [Wenn sich die Anforderungen ändern], ist dies ein Problem.
quelle