Refactoring und Open / Closed-Prinzip

12

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 Iund eine entsprechende Implementierungsklasse definiere A. Wenn die Klasse Astabil geworden ist (implementiert und getestet), ändere ich sie normalerweise nicht zu stark (möglicherweise überhaupt nicht), d. H.

  1. 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 Bund verwende sie Aso lange, wie sie Bnicht ausgereift ist. Wenn Bes ausgereift ist, muss nur noch geändert werden, wie Iinstanziiert wird.
  2. Wenn die neuen Anforderungen auch eine Änderung der Schnittstelle vorschlagen, definiere ich eine neue Schnittstelle I'und eine neue Implementierung A'. Also I, Asind gesperrt und bleibt die Umsetzung für das Produktionssystem solange I'und A'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?

Giorgio
quelle

Antworten:

9

Ich betrachte das Open-Closed-Prinzip als Designziel . Wenn Sie am Ende dagegen verstoßen müssen, bedeutet dies, dass Ihr ursprüngliches Design fehlgeschlagen ist, was sicherlich möglich und sogar wahrscheinlich ist.

Refactoring bedeutet, dass Sie das Design ändern, ohne die Funktionalität zu ändern. Wahrscheinlich ändern Sie Ihr Design, weil es ein Problem damit gibt. Möglicherweise liegt das Problem darin, dass es schwierig ist, das Open-Closed-Prinzip zu befolgen, wenn Änderungen am vorhandenen Code vorgenommen werden, und Sie versuchen, dies zu beheben.

Möglicherweise führen Sie ein Refactoring durch, um die Implementierung Ihrer nächsten Funktion zu ermöglichen, ohne dabei das OCP zu verletzen.

Scott Whitlock
quelle
Sie sollten auf jeden Fall nicht , dass wie jedes Prinzip als gestalterisches Ziel . Sie sind Werkzeuge - Sie machen die Software nicht hübsch und theoretisch korrekt, sondern versuchen, Wert für Ihren Kunden zu schaffen. Es ist eine Richtlinie , nichts weiter.
T. Sar
@ T.Sar Ein Prinzip ist eine Richtlinie, nach der Sie streben. Sie sind auf Wartbarkeit und Skalierbarkeit ausgerichtet. Das sieht für mich nach einem Designziel aus. Ich kann ein Prinzip nicht als Werkzeug sehen, wie ich ein Designmuster oder ein Framework als Werkzeug sehe.
Tulains Córdova
@ TulainsCórdova Wartbarkeit, Leistung, Korrektheit, Skalierbarkeit - das sind Ziele. Das Open-Closed-Prinzip ist ein Mittel für sie - nur eines von vielen. Sie müssen nichts in Richtung des Open-Closed-Prinzips schieben, wenn es nicht darauf anwendbar ist oder die tatsächlichen Ziele des Projekts beeinträchtigen würde. Sie verkaufen "Offenheit" nicht an einen Kunden. Als bloße Richtlinie ist es nicht besser als eine Faustregel, die verworfen werden kann, wenn Sie am Ende einen Weg finden, Ihr Ding lesbarer und klarer zu machen. Richtlinien sind Werkzeuge, nichts weiter.
T. Sar
@ T.Sar Es gibt so viele Dinge, die Sie nicht an einen Kunden verkaufen können ... Andererseits stimme ich Ihnen darin zu, dass man keine Dinge tun darf, die die Ziele des Projekts beeinträchtigen.
Tulains Córdova
9

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.

Radarbob
quelle
2
"Das Verbrechen, OC zu verletzen, ist viel geringer, wenn Sie einer Klasse neue Methoden hinzufügen können, anstatt vorhandene ändern zu müssen.": Soweit ich weiß, verstößt das Hinzufügen neuer Methoden überhaupt nicht gegen das OC-Prinzip (offen für Erweiterungen) . Das Problem besteht darin, vorhandene Methoden zu ändern, die eine genau definierte Schnittstelle implementieren und daher bereits eine genau definierte Semantik aufweisen (wegen Änderung geschlossen). Im Prinzip ändert Refactoring nichts an der Semantik. Das einzige Risiko, das ich sehen kann, ist die Einführung von Fehlern in bereits stabilem und gut getestetem Code.
Giorgio
1
Hier ist die Antwort von CodeReview, die zeigt, dass sie zur Erweiterung offen ist . Dieses Klassendesign ist erweiterbar. Im Gegensatz dazu ändert das Hinzufügen einer Methode die Klasse.
Radarbob
Das Hinzufügen neuer Methoden verletzt LSP, nicht OCP.
Tulains Córdova
1
Das Hinzufügen neuer Methoden verstößt nicht gegen LSP. Wenn Sie eine Methode hinzufügen, haben Sie eine neue Schnittstelle @ TulainsCórdova
RubberDuck
6

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.

opsb
quelle
1
Ich bin weder ein Befürworter des Open-Closed-Prinzips noch von TDD (in dem Sinne, dass ich sie nicht erfunden habe). Was mich überraschte, war, dass jemand gleichzeitig das Open-Closed-Prinzip UND den Einsatz von Refactoring UND TDD vorschlug. Dies schien mir widersprüchlich und so versuchte ich herauszufinden, wie ich all diese Richtlinien zu einem kohärenten Prozess zusammenführen kann.
Giorgio
"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.": Eigentlich sehe ich das nicht so. Die Idee ist eher, kleine, in sich geschlossene Einheiten zu haben, die Sie ersetzen oder erweitern können (wodurch sich die Software weiterentwickeln kann), aber Sie sollten nicht jede Einheit berühren, nachdem sie gründlich getestet wurde.
Giorgio
Sie müssen denken, dass die Klasse nicht nur in Ihrer Codebasis verwendet wird. Die von Ihnen geschriebene Bibliothek kann in anderen Projekten verwendet werden. OCP ist also wichtig. Außerdem ist ein neuer Programmierer, der keine erweiterte Klasse mit der von ihm benötigten Funktionalität kennt, ein Kommunikations- / Dokumentationsproblem, kein Entwurfsproblem.
Tulains Córdova
@ TulainsCórdova im Anwendungscode ist dies nicht relevant. Für Bibliothekscode würde ich argumentieren, dass die semantische Versionierung besser für die Kommunikation von Änderungen geeignet ist.
opsb
1
@ TulainsCórdova mit Bibliothekscode API-Stabilität ist weitaus wichtiger, da der Client-Code nicht getestet werden kann. Mit dem Anwendungscode informiert Sie Ihre Testabdeckung sofort über eventuelle Brüche. Anders ausgedrückt, Anwendungscode kann Änderungen ohne Risiko vornehmen, während Bibliothekscode das Risiko verwalten muss, indem er eine stabile API beibehält und
Fehler
6

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 :

  • Es sollte sehr wenig Gründe dafür geben (Punkt B), die im Laufe der Zeit gegen Null tendieren.
  • (Punkt C) wird immer möglich sein, wenn auch seltener.
  • Alle neuen Funktionen sollen eine Spezialisierung sein, dh die Klassen müssen erweitert (geerbt von) (Punkt A) werden.
Tulains Córdova
quelle
Das Open / Closed-Prinzip wird sehr missverstanden. Ihre Punkte A und B machen das genau richtig.
Gnasher729
1

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.

David Hammen
quelle
0

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.

Patkos Csaba
quelle
1
"Wenn Sie also umgestalten, fügen Sie keine neuen Funktionen hinzu.": Aber ich könnte Fehler in einer getesteten Software einführen.
Giorgio
"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.": Dann würde ich Beginnen Sie mit dem Schreiben einer neuen Implementierung Bund ersetzen Sie, wenn dies fertig ist, die alte Implementierung Adurch die neue Implementierung B(dies ist eine Verwendung von Schnittstellen). ADer Code kann als Grundlage für Bden Code dienen, und dann kann ich das Refactoring für den BCode während seiner Entwicklung verwenden, aber ich denke, dass der bereits getestete ACode eingefroren bleiben sollte.
Giorgio
@Giorgio Wenn Sie überarbeiten, können Sie Fehler einführen, deshalb schreiben Sie Tests (oder noch besser TDD). Der sicherste Weg zur Umgestaltung besteht darin, den Code zu ändern, wenn Sie wissen, dass er funktioniert. Sie wissen dies, indem Sie eine Reihe von Tests bestehen lassen. Nachdem Sie Ihren Produktionscode geändert haben, müssen die Tests noch bestanden werden, damit Sie wissen, dass Sie keinen Fehler eingeführt haben. Und denken Sie daran, dass Tests genauso wichtig sind wie Produktionscode. Sie wenden daher dieselbe Regel auf sie an wie auf Produktionscode, halten sie sauber und überarbeiten sie regelmäßig und häufig.
Patkos Csaba
@Giorgio Wenn Code Bauf Code Aals Weiterentwicklung von basiert, sollte er Abei BVeröffentlichung Aentfernt und nie wieder verwendet werden. Clients, die früher verwendet haben, Awerden nur verwenden, Bohne über die Änderung Bescheid zu wissen, da die Benutzeroberfläche Inicht geändert wurde (vielleicht ein bisschen Liskov-Substitutionsprinzip hier? ... das L von SOLID)
Patkos Csaba
Ja, das habe ich mir vorgestellt: Werfen Sie den Arbeitscode erst weg, wenn Sie einen gültigen (gut getesteten) Ersatz haben.
Giorgio
-1

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.

Narender Parmar
quelle