it-swarm.com.de

Warum verletzen viele Softwareentwickler das Open / Closed-Prinzip?

Warum verletzen viele Softwareentwickler das Open/Closed-Prinzip , indem sie viele Dinge wie das Umbenennen von Funktionen ändern, die die Anwendung nach dem Upgrade beschädigen?

Diese Frage springt mir nach den schnellen und fortlaufenden Versionen in der Bibliothek React in den Sinn.

In jeder kurzen Zeit stelle ich viele Änderungen in der Syntax, den Komponentennamen usw. fest

Beispiel in die kommende Version von React :

Neue Verfallswarnungen

Die größte Änderung besteht darin, dass wir React.PropTypes und React.createClass in ihre eigenen Pakete extrahiert haben. Auf beide kann weiterhin über das Hauptobjekt React) zugegriffen werden. Wenn Sie jedoch eines der beiden Objekte verwenden, wird im Entwicklungsmodus eine einmalige Verfallswarnung an die Konsole protokolliert. Dies ermöglicht zukünftige Optimierungen der Codegröße.

Diese Warnungen wirken sich nicht auf das Verhalten Ihrer Anwendung aus. Wir sind uns jedoch bewusst, dass sie zu Frustrationen führen können, insbesondere wenn Sie ein Testframework verwenden, das console.error als Fehler behandelt.


  • Werden diese Änderungen als Verstoß gegen dieses Prinzip angesehen?
  • Wie lerne ich als Anfänger von so etwas wie React mit diesen schnellen Änderungen in der Bibliothek (es ist so frustrierend)?
77

Die Antwort von IMHO JacquesB zeigt, obwohl sie viel Wahrheit enthält, ein grundlegendes Missverständnis der OCP. Um fair zu sein, Ihre Frage drückt auch dieses Missverständnis bereits aus - das Umbenennen von Funktionen bricht ab Abwärtskompatibilität, aber nicht das OCP. Wenn eine Unterbrechung der Kompatibilität erforderlich erscheint (oder wenn zwei Versionen derselben Komponente beibehalten werden, um die Kompatibilität nicht zu beeinträchtigen), wurde das OCP bereits zuvor unterbrochen!

Wie Jörg W Mittag bereits in seinen Kommentaren erwähnt hat, heißt es im Prinzip nicht "Sie können das Verhalten einer Komponente nicht ändern" - es heißt, man sollte versuchen, um Komponenten so zu gestalten, dass sie offen sind um auf verschiedene Weise wiederverwendet (oder erweitert) zu werden, ohne die Notwendigkeit zur Änderung. Dies kann durch Bereitstellen der richtigen "Erweiterungspunkte" oder, wie von @AntP erwähnt, "durch Zerlegen einer Klassen-/Funktionsstruktur bis zu dem Punkt erfolgen, an dem standardmäßig jeder natürliche Erweiterungspunkt vorhanden ist". IMHO nach dem OCP hat nichts mit zu tun "die alte Version aus Gründen der Abwärtskompatibilität unverändert zu lassen" ! Oder zitieren Sie den Kommentar von @ DerekElkin unten:

Das OCP gibt Ratschläge zum Schreiben eines Moduls [...] und nicht zum Implementieren eines Änderungsmanagementprozesses, bei dem Module niemals geändert werden können.

Gute Programmierer nutzen ihre Erfahrung, um Komponenten unter Berücksichtigung der "richtigen" Erweiterungspunkte zu entwerfen (oder - noch besser - so, dass keine künstlichen Erweiterungspunkte erforderlich sind). Um dies jedoch korrekt und ohne unnötige Überentwicklung durchzuführen, müssen Sie im Voraus wissen, wie zukünftige Anwendungsfälle Ihrer Komponente aussehen könnten. Selbst erfahrene Programmierer können nicht in die Zukunft schauen und alle anstehenden Anforderungen im Voraus kennen. Und deshalb muss manchmal Abwärtskompatibilität verletzt werden - unabhängig davon, wie viele Erweiterungspunkte Ihre Komponente hat oder wie gut sie dem OCP in Bezug auf bestimmte Arten von Anforderungen folgt, wird es immer eine Anforderung geben Dies kann nicht einfach implementiert werden, ohne die Komponente zu ändern.

147
Doc Brown

Das Open/Closed-Prinzip hat Vorteile, aber auch einige schwerwiegende Nachteile.

Theoretisch löst das Prinzip das Problem der Abwärtskompatibilität, indem Code erstellt wird, der "zur Erweiterung offen, aber zur Änderung geschlossen" ist. Wenn für eine Klasse neue Anforderungen gelten, ändern Sie niemals den Quellcode der Klasse selbst, sondern erstellen eine Unterklasse, die nur die entsprechenden Elemente überschreibt, die zum Ändern des Verhaltens erforderlich sind. Der gesamte Code, der gegen die Originalversion der Klasse geschrieben wurde, ist daher nicht betroffen, sodass Sie sicher sein können, dass Ihre Änderung den vorhandenen Code nicht beschädigt hat.

In Wirklichkeit kommt es leicht zu aufgeblähtem Code und einem verwirrenden Durcheinander veralteter Klassen. Wenn es nicht möglich ist, das Verhalten einer Komponente durch Erweiterung zu ändern, müssen Sie eine neue Variante der Komponente mit dem gewünschten Verhalten bereitstellen und die alte Version aus Gründen der Abwärtskompatibilität unverändert lassen.

Angenommen, Sie entdecken einen grundlegenden Konstruktionsfehler in einer Basisklasse, von der viele Klassen erben. Angenommen, der Fehler ist darauf zurückzuführen, dass ein privates Feld vom falschen Typ ist. Sie können dies nicht beheben, indem Sie ein Mitglied überschreiben. Grundsätzlich müssen Sie die gesamte Klasse überschreiben, was bedeutet, dass Sie Object erweitern, um eine alternative Basisklasse bereitzustellen - und jetzt müssen Sie auch Alternativen zu allen Unterklassen bereitstellen, wodurch eine doppelte Objekthierarchie entsteht. eine Hierarchie fehlerhaft, eine verbessert. Sie können die fehlerhafte Hierarchie jedoch nicht entfernen (da das Löschen von Code eine Änderung darstellt). Alle zukünftigen Clients sind beiden Hierarchien ausgesetzt.

Die theoretische Antwort auf dieses Problem lautet nun "Entwerfen Sie es einfach beim ersten Mal richtig". Wenn der Code ohne Fehler oder Fehler perfekt zerlegt und mit Erweiterungspunkten ausgestattet ist, die für alle möglichen zukünftigen Anforderungsänderungen vorbereitet sind, vermeiden Sie das Durcheinander. Aber in Wirklichkeit macht jeder Fehler und niemand kann die Zukunft perfekt vorhersagen.

Nehmen wir so etwas wie das .NET-Framework - es enthält immer noch die Sammlung von Sammlungsklassen, die vor der Einführung von Generika vor mehr als einem Jahrzehnt entwickelt wurden. Dies ist sicherlich ein Segen für die Abwärtskompatibilität (Sie können das Framework aktualisieren, ohne etwas neu schreiben zu müssen), aber es bläht auch das Framework auf und bietet Entwicklern eine Vielzahl von Optionen, bei denen viele einfach veraltet sind.

Anscheinend haben die Entwickler von React) das Gefühl, dass es die Kosten für Komplexität und Code-Bloat nicht wert war, sich strikt an das Open/Closed-Prinzip zu halten.

Die pragmatische Alternative zu Öffnen/Schließen ist die kontrollierte Abschreibung. Anstatt die Abwärtskompatibilität in einer einzelnen Version zu beeinträchtigen, werden alte Komponenten für einen Veröffentlichungszyklus beibehalten. Clients werden jedoch über Compiler-Warnungen darüber informiert, dass der alte Ansatz in einer späteren Version entfernt wird. Dies gibt den Clients Zeit, den Code zu ändern. Dies scheint der Ansatz von React in diesem Fall) zu sein.

(Meine Interpretation des Prinzips basiert auf The Open-Closed Principle von Robert C. Martin)

68
JacquesB

Ich würde das Open/Closed-Prinzip als Ideal bezeichnen. Wie bei allen Idealen werden die Realitäten der Softwareentwicklung kaum berücksichtigt. Wie bei allen Idealen ist es auch in der Praxis unmöglich, sie tatsächlich zu erreichen - man bemüht sich lediglich, sich diesem Ideal so gut wie möglich zu nähern.

Die andere Seite der Geschichte ist als Goldene Handschellen bekannt. Goldene Handschellen bekommen Sie, wenn Sie sich zu sehr dem offenen/geschlossenen Prinzip unterwerfen. Goldene Handschellen treten auf, wenn Ihr Produkt, das niemals die Abwärtskompatibilität beeinträchtigt, nicht wachsen kann, weil in der Vergangenheit zu viele Fehler gemacht wurden.

Ein berühmtes Beispiel hierfür ist der Windows 95-Speichermanager. Im Rahmen des Marketings für Windows 95 wurde angegeben, dass alle Windows 3.1-Anwendungen unter Windows 95 funktionieren würden. Microsoft erwarb tatsächlich Lizenzen für Tausende von Programmen, um sie unter Windows 95 zu testen. Einer der Problemfälle war Sim City. Sim City hatte tatsächlich einen Fehler, der dazu führte, dass es in nicht zugewiesenen Speicher schrieb. In Windows 3.1 war dies ohne einen "richtigen" Speichermanager ein kleiner Fauxpas. In Windows 95 würde der Speichermanager dies jedoch abfangen und einen Segmentierungsfehler verursachen. Die Lösung? Wenn Ihr Anwendungsname in Windows 95 simcity.exe Lautet, lockert das Betriebssystem die Einschränkungen des Speichermanagers, um den Segmentierungsfehler zu vermeiden!

Das eigentliche Problem hinter diesem Ideal sind die reduzierten Konzepte von Produkten und Dienstleistungen. Niemand macht wirklich das eine oder andere. Alles reiht sich irgendwo in der grauen Region zwischen den beiden an. Wenn Sie produktorientiert denken, klingt Öffnen/Schließen nach einem großartigen Ideal. Ihre Produkte sind zuverlässig. Wenn es jedoch um Dienstleistungen geht, ändert sich die Geschichte. Es ist leicht zu zeigen, dass mit dem Open/Closed-Prinzip die Menge an Funktionen, die Ihr Team unterstützen muss , asymptotisch gegen unendlich gehen muss , da Sie niemals alte aufräumen können Funktionalität. Dies bedeutet, dass Ihr Entwicklungsteam jedes Jahr mehr Code unterstützen muss. Schließlich erreichen Sie einen Bruchpunkt.

Die meiste Software von heute, insbesondere Open Source, folgt einer gemeinsamen entspannten Version des Open/Closed-Prinzips. Es ist sehr üblich, dass Open/Closed bei kleineren Releases sklavisch verfolgt wird, bei größeren Releases jedoch aufgegeben wird. Zum Beispiel enthält Python 2.7 enthält viele "schlechte Entscheidungen" aus den Tagen Python 2.0 und 2.1, aber Python 3.0 hat gekehrt) alle von ihnen weg. (Auch die Verschiebung von der Windows 95-Codebasis zur Windows NT-Codebasis, als sie Windows 2000 veröffentlichten, brach alle möglichen Dinge, aber es tat bedeutet, wir müssen uns nie mit einem Speichermanager befassen, der den Anwendungsnamen überprüft, um über das Verhalten zu entscheiden!)

20
Cort Ammon

Die Antwort von Doc Brown kommt der Genauigkeit am nächsten, die anderen Antworten veranschaulichen Missverständnisse des Open-Closed-Prinzips.

Um das Missverständnis explizit zu artikulieren, scheint es eine Überzeugung zu geben, dass die OCP bedeutet, dass Sie keine rückwärts inkompatiblen Änderungen (oder sogar Änderungen oder ähnliches in diese Richtung) vornehmen sollten. Die OCP Es geht darum, Komponenten so zu gestalten, dass Sie keine Änderungen vornehmen müssen, um ihre Funktionalität zu erweitern, unabhängig davon, ob diese Änderungen abwärtskompatibel sind oder nicht. Neben dem Hinzufügen von Funktionen gibt es viele andere Gründe, warum Sie Änderungen an einer Komponente vornehmen können, unabhängig davon, ob diese abwärtskompatibel (z. B. Refactoring oder Optimierung) oder abwärtskompatibel (z. B. Verwerfen und Entfernen von Funktionen) sind. Dass Sie diese Änderungen vornehmen können, bedeutet nicht, dass Ihre Komponente gegen die OCP verstoßen hat (und definitiv nicht, dass Sie gegen die OCP verstoßen).

Wirklich, es geht überhaupt nicht um Quellcode. Eine abstraktere und relevantere Aussage des OCP lautet: "Eine Komponente sollte eine Erweiterung ermöglichen, ohne dass ihre Abstraktionsgrenzen verletzt werden müssen." Ich würde noch weiter gehen und sagen, dass eine modernere Wiedergabe lautet: "Eine Komponente sollte ihre Abstraktionsgrenzen durchsetzen , aber eine Erweiterung zulassen". Selbst in dem Artikel über das OCP von Bob Martin, in dem er "für Änderungen geschlossen" als "der Quellcode ist unantastbar" "beschreibt", spricht er später über die Kapselung, die nichts mit dem Ändern des Quellcodes und allem, was mit Abstraktion zu tun hat, zu tun hat Grenzen.

Die fehlerhafte Prämisse in der Frage ist also, dass das OCP eine Richtlinie über die Entwicklung einer Codebasis ist. Das OCP wird normalerweise als "eine Komponente sollte für Erweiterungen offen und für Änderungen durch Verbraucher geschlossen sein" bezeichnet. Wenn ein Verbraucher einer Komponente der Komponente Funktionalität hinzufügen möchte, sollte er in der Lage sein, die alte Komponente in eine zu erweitern neue mit der zusätzlichen Funktionalität, aber sie sollten nicht in der Lage sein die alte Komponente zu ändern .

Das OCP sagt nichts über den Ersteller einer Komponente aus, der oder die Funktionalität entfernt . Die OCP befürwortet nicht die Aufrechterhaltung der Fehlerkompatibilität für immer. Sie als Ersteller verletzen das OCP nicht, indem Sie eine Komponente ändern oder sogar entfernen. Sie oder vielmehr die von Ihnen geschriebenen Komponenten verletzen das OCP, wenn Verbraucher Ihren Komponenten nur durch Mutation z. durch Affen-Patching oder Zugriff auf den Quellcode und Neukompilieren. In vielen Fällen sind keine dieser Optionen für den Verbraucher verfügbar. Wenn Ihre Komponente nicht "für Erweiterungen offen" ist, haben sie kein Glück. Sie können Ihre Komponente einfach nicht für ihre Bedürfnisse verwenden. Das OCP argumentiert, die Verbraucher Ihrer Bibliothek zumindest in Bezug auf eine identifizierbare Klasse von "Erweiterungen" nicht in diese Position zu bringen. Selbst wenn Änderungen am Quellcode oder sogar an der primären Kopie des Quellcodes vorgenommen werden können, ist es am besten, so zu tun, als könnten Sie ihn nicht ändern, da es viele Möglichkeiten gibt negative Konsequenzen.

Um Ihre Fragen zu beantworten: Nein, dies sind keine Verstöße gegen die OCP. Keine Änderung, die ein Autor vornimmt, kann eine Verletzung der OCP darstellen, da die OCP kein Verhältnis der Änderungen darstellt. Die Änderungen können jedoch Verstöße gegen das OCP verursachen und können durch Fehler des OCP in früheren Versionen der Codebasis motiviert sein. Das OCP ist eine Eigenschaft eines bestimmten Codeteils, nicht die Evolutionsgeschichte einer Codebasis.

Im Gegensatz dazu ist die Abwärtskompatibilität eine Eigenschaft einer Änderung des Codes. Es macht keinen Sinn zu sagen, dass ein Teil des Codes abwärtskompatibel ist oder nicht. Es ist nur sinnvoll, über die Abwärtskompatibilität eines Codes in Bezug auf einen älteren Code zu sprechen. Daher ist es nie sinnvoll, davon zu sprechen, dass der erste Schnitt eines Codes abwärtskompatibel ist oder nicht. Der erste Codeschnitt kann die OCP erfüllen oder nicht erfüllen, und im Allgemeinen können wir feststellen, ob ein Code die OCP erfüllt, ohne auf historische Versionen des Codes zu verweisen.

Was Ihre letzte Frage betrifft, so ist es für StackExchange im Allgemeinen wohl nicht themenbezogen, da es in erster Linie auf Meinungen basiert. Kurz gesagt, es ist willkommen bei Tech und insbesondere bei JavaScript, wo in den letzten Jahren das von Ihnen beschriebene Phänomen genannt wurde. -JavaScript Müdigkeit . (Fühlen Sie sich frei, google , um eine Vielzahl anderer Artikel zu finden, von denen einige satirisch sind und aus verschiedenen Perspektiven darüber sprechen.)

11