Perché molti sviluppatori di software violano il principio aperto / chiuso?

71

Perché molti sviluppatori di software violano il principio di apertura / chiusura modificando molte cose come le funzioni di rinomina che si interromperanno l'applicazione dopo l'aggiornamento?

Questa domanda mi viene in mente dopo le versioni rapide e continue della React libreria.

Ogni breve periodo noto molti cambiamenti nella sintassi, nomi di componenti, ... ecc.

Esempio in la prossima versione di React :

New Deprecation Warnings

The biggest change is that we've extracted React.PropTypes and React.createClass into their own packages. Both are still accessible via the main React object, but using either will log a one-time deprecation warning to the console when in development mode. This will enable future code size optimizations.

These warnings will not affect the behavior of your application. However, we realize they may cause some frustration, particularly if you use a testing framework that treats console.error as a failure.

  • Queste modifiche sono considerate una violazione di tale principio?
  • Come principiante a qualcosa come Reagisci , come faccio a impararlo con questi rapidi cambiamenti nella libreria (è così frustrante)?
posta Anyname Donotcare 30.04.2017 - 16:54
fonte

4 risposte

143

La risposta di IMHO JacquesB, sebbene contenesse molta verità, mostra un fondamentale fraintendimento dell'OCP. Per essere onesti, la tua domanda esprime già questo equivoco: le funzioni di ridenominazione interrompono la retrocompatibilità , ma non l'OCP. Se la rottura della compatibilità sembra necessaria (o mantenere due versioni dello stesso componente per non compromettere la compatibilità), l'OCP era già stato rotto prima!

Come già menzionato Jörg W Mittag nei suoi commenti, il principio non dice "non si può modificare il comportamento di un componente" - si dice, si dovrebbe provare per progettare componenti in un modo sono aperti per essere riutilizzati (o estesi) in diversi modi, senza bisogno di modifiche. Questo può essere fatto fornendo i giusti "punti di estensione" o, come menzionato da @AntP, "decomprimendo una struttura di classe / funzione nel punto in cui ogni punto di estensione naturale è lì per impostazione predefinita." IMHO che segue l'OCP non ha nulla in comune con "mantenendo invariata la vecchia versione per compatibilità con le versioni precedenti" ! Oppure, citando il commento di @DerekElkin qui sotto:

The OCP is advice on how to write a module [...], not about implementing a change management process that never allows modules to change.

I bravi programmatori usano la loro esperienza per progettare componenti con i punti di estensione "giusti" in mente (o, ancor meglio, in un modo in cui non sono necessari punti di estensione artificiali). Tuttavia, per farlo correttamente e senza inutili sovrastrutture, è necessario sapere in anticipo come potrebbero essere i casi d'uso futuri del componente. Anche i programmatori esperti non possono guardare al futuro e conoscere tutti i requisiti in arrivo. Ed è per questo che a volte la retrocompatibilità deve essere violata, indipendentemente dal numero di punti di estensione del componente o dal modo in cui l'OCP segue certi tipi di requisiti, ci sarà sempre un requisito che non può essere implementato facilmente senza modificare il componente.

    
risposta data 30.04.2017 - 23:35
fonte
66

Il principio aperto / chiuso ha dei vantaggi, ma ha anche alcuni seri inconvenienti.

In teoria il principio risolve il problema della retrocompatibilità creando codice che è "aperto per estensione ma chiuso per modifica". Se una classe ha dei nuovi requisiti, non si modifica mai il codice sorgente della classe stessa, ma si crea invece una sottoclasse che sostituisce solo i membri appropriati necessari per modificare il comportamento. Tutto il codice scritto contro la versione originale della classe non è quindi influenzato, quindi puoi essere sicuro che la tua modifica non abbia infranto il codice esistente.

In realtà si finisce facilmente con il code bloat e un pasticcio confuso di classi obsolete. Se non è possibile modificare alcuni comportamenti di un componente attraverso l'estensione, devi fornire una nuova variante del componente con il comportamento desiderato e mantenere invariata la vecchia versione per la compatibilità all'indietro.

Supponiamo di scoprire un difetto di progettazione fondamentale in una classe base da cui molte classi ereditano. Supponiamo che l'errore sia dovuto al fatto che un campo privato è del tipo sbagliato. Non puoi risolvere questo problema ignorando un membro. Fondamentalmente devi sovrascrivere l'intera classe, il che significa che finisci per estendere Object per fornire una classe base alternativa - e ora devi anche fornire alternative a tutte le sottoclassi, finendo quindi con una gerarchia di oggetti duplicata, una gerarchia imperfetta , uno migliorato. Ma non è possibile rimuovere la gerarchia imperfetta (poiché la cancellazione del codice è una modifica), tutti i futuri client saranno esposti a entrambe le gerarchie.

Ora la risposta teorica a questo problema è "basta progettarla correttamente la prima volta". Se il codice è perfettamente scomposto, senza difetti o errori, e progettato con punti di estensione preparati per tutti i possibili cambiamenti futuri, si evita il disordine. Ma in realtà tutti commettono errori e nessuno può prevedere perfettamente il futuro.

Prendi qualcosa come il framework .NET - porta ancora in giro il set di classi di raccolta che sono state progettate prima che i generici siano stati introdotti più di dieci anni fa. Questo è certamente un vantaggio per la retrocompatibilità (è possibile aggiornare il framework senza dover riscrivere nulla), ma aumenta anche il framework e presenta agli sviluppatori un'ampia gamma di opzioni in cui molte sono semplicemente obsolete.

A quanto pare, gli sviluppatori di React hanno ritenuto che non valesse il costo della complessità e del code-bloat seguire rigorosamente il principio aperto / chiuso.

L'alternativa pragmatica ad aprire / chiudere è una deprecazione controllata. Piuttosto che rompere la compatibilità con le versioni precedenti in una singola release, i vecchi componenti vengono mantenuti per un ciclo di rilascio, ma i clienti vengono informati tramite avvisi del compilatore che il vecchio approccio verrà rimosso in una versione successiva. Questo dà ai clienti il tempo di modificare il codice. Questo sembra essere l'approccio di React in questo caso.

(La mia interpretazione del principio si basa su Il principio Open-Closed di Robert C. Martin)

    
risposta data 30.04.2017 - 17:12
fonte
20

Definirei ideale il principio aperto / chiuso. Come tutti gli ideali, dà poca considerazione alle realtà dello sviluppo del software. Inoltre, come tutti gli ideali, è praticamente impossibile raggiungerlo nella pratica - si cerca semplicemente di avvicinarsi a quell'ideale nel miglior modo possibile.

L'altro lato della storia è noto come le manette d'oro. Le manette dorate sono ciò che si ottiene quando si asservisce troppo al principio aperto / chiuso. Le manette dorate sono ciò che si verifica quando il tuo prodotto che non rompe mai la compatibilità all'indietro non può crescere perché sono stati commessi troppi errori passati.

Un famoso esempio di questo si trova nel gestore di memoria di Windows 95. Come parte del marketing per Windows 95, è stato affermato che tutte le applicazioni Windows 3.1 funzionerebbero in Windows 95. Microsoft ha effettivamente acquisito licenze per migliaia di programmi per testarli in Windows 95. Uno dei casi problematici era Sim City. Sim City ha avuto un bug che ha causato la scrittura in memoria non allocata. In Windows 3.1, senza un gestore di memoria "appropriato", si trattava di un passo falso minore. Tuttavia, in Windows 95, il gestore della memoria rileva questo e causa un errore di segmentazione. La soluzione? In Windows 95, se il nome dell'applicazione è simcity.exe , il sistema operativo allenterà i vincoli del gestore memoria per prevenire l'errore di segmentazione!

Il vero problema alla base di questo ideale sono i concetti essenziali di prodotti e servizi. Nessuno fa davvero l'uno o l'altro. Tutto si allinea da qualche parte nella regione grigia tra i due. Se si pensa da un approccio orientato al prodotto, l'apertura / chiusura suona come un grande ideale. I tuoi prodotti sono affidabili. Tuttavia, quando si tratta di servizi, la storia cambia. È facile dimostrare che con il principio aperto / chiuso, la quantità di funzionalità che la tua squadra deve supportare deve asintoticamente avvicinarsi all'infinito, perché non puoi mai ripulire le vecchie funzionalità. Ciò significa che il tuo team di sviluppo deve supportare sempre più codice ogni anno. Alla fine raggiungi un punto di rottura.

La maggior parte dei software oggi, in particolare l'open source, segue una versione rilassata comune del principio aperto / chiuso. È molto comune vedere open / closed seguito pedissequamente per versioni minori, ma abbandonato per le versioni principali. Ad esempio, Python 2.7 contiene molte "scelte sbagliate" dai giorni Python 2.0 e 2.1, ma Python 3.0 ha spazzato via tutti. (Inoltre, il passaggio dalla base di codice di Windows 95 alla base di codice di Windows NT quando hanno rilasciato Windows 2000 ha rotto tutti i tipi di cose, ma ha significa che non dobbiamo mai occuparci di un gestore di memoria che controlla il nome dell'applicazione per decidere il comportamento!)

    
risposta data 30.04.2017 - 21:37
fonte
11

La risposta di Doc Brown è la più vicina alla precisione, le altre risposte illustrano le incomprensioni del principio Open Closed.

Per articolare esplicitamente l'equivoco, sembra esserci la convinzione che l'OCP significhi che non si dovrebbero apportare modifiche all'indietro incompatibili (o anche qualsiasi modifica o qualcosa del genere). L'OCP riguarda progettare componenti in modo che non bisogno di apportare modifiche ad essi per estendere le loro funzionalità, indipendentemente dal fatto che tali modifiche siano compatibili o meno. Ci sono molti altri motivi oltre all'aggiunta di funzionalità che è possibile apportare modifiche a un componente, indipendentemente dal fatto che siano retrocompatibili (ad esempio, refactoring o ottimizzazione) o retrocompatibili (ad esempio, funzionalità deprecating e rimozione). Il fatto che tu possa apportare queste modifiche non significa che il tuo componente abbia violato l'OCP (e sicuramente non significa che tu stia violando l'OCP).

In realtà, non si tratta affatto di codice sorgente. Una frase più astratta e rilevante dell'OCP è: "un componente dovrebbe consentire l'estensione senza necessità di violare i suoi confini di astrazione". Vorrei andare oltre e dire che una versione più moderna è: "un componente dovrebbe imporre i suoi limiti di astrazione ma consentire l'estensione". Anche nell'articolo sull'OCP di Bob Martin mentre "descrive" "chiuso alla modifica" come "il codice sorgente è inviolato", in seguito inizia a parlare di incapsulamento che non ha nulla a che fare con la modifica del codice sorgente e tutto ciò che riguarda l'astrazione confini.

Quindi, la premessa difettosa nella domanda è che l'OCP è (inteso come) una linea guida sulle evoluzioni di un codice base. L'OCP è in genere definito come "un componente dovrebbe essere aperto alle estensioni e chiuso alle modifiche dei consumatori". Fondamentalmente, se un consumatore di un componente vuole aggiungere funzionalità al componente dovrebbe essere in grado di estendere il vecchio componente in uno nuovo con la funzionalità aggiuntiva, ma dovrebbero non essere in grado di modificare il vecchio componente.

L'OCP non dice nulla sul creatore di una componente che modifica o rimuove funzionalità. L'OCP non sta promuovendo per sempre la compatibilità dei bug . Tu, come creatore, non stai violando l'OCP cambiando o addirittura rimuovendo un componente. Tu, o meglio i componenti che hai scritto, stai violando l'OCP se l'unico modo in cui i consumatori possono aggiungere funzionalità ai tuoi componenti è la sua mutazione, ad es. da patch per scimmia o con accesso al codice sorgente e ricompilazione. In molti casi, nessuna di queste opzioni è per il consumatore, il che significa che se il componente non è "aperto per l'estensione" non ha fortuna. Semplicemente non possono usare il tuo componente per i loro bisogni. L'OCP sostiene di non mettere i consumatori della tua biblioteca in questa posizione, almeno per quanto riguarda alcune classi di "estensioni" identificabili. Anche quando le modifiche possono essere fatte al codice sorgente o anche alla copia primaria del codice sorgente, è meglio "fingere" di non poterlo modificare poiché ci sono molte potenziali conseguenze negative nel farlo .

Quindi per rispondere alle tue domande: No, queste non sono violazioni dell'OCP. Nessuna modifica apportata da un autore può essere una violazione dell'OCP, in quanto l'OCP non è una propensione ai cambiamenti. Le modifiche, tuttavia, possono creare violazioni dell'OCP e possono essere motivate da errori dell'OCP nelle versioni precedenti del codebase. L'OCP è una proprietà di un particolare pezzo di codice, non la storia evolutiva di un codebase.

Per contrasto, la compatibilità all'indietro è una proprietà di una modifica di codice. Non ha senso dire che un pezzo di codice è o non è retrocompatibile. Ha senso solo parlare della compatibilità all'indietro di qualche codice rispetto a di un codice precedente. Pertanto, non ha mai senso parlare del primo taglio di un codice che sia retrocompatibile o meno. Il primo taglio di codice può soddisfare o non riuscire a soddisfare l'OCP e, in generale, è possibile determinare se alcuni codici soddisfano l'OCP senza fare riferimento a versioni storiche del codice.

Per quanto riguarda la tua ultima domanda, è discutibilmente fuori tema per StackExchange in generale essendo fondamentalmente basato sull'opinione pubblica, ma il corto è ben accetto alla tecnologia e in particolare JavaScript dove negli ultimi anni il fenomeno che hai descritto è stato chiamato stanchezza di JavaScript . (Sentiti libero di google per trovare una varietà di altri articoli, alcuni satirici, che parlano di questo da più punti di vista.)

    
risposta data 01.05.2017 - 00:38
fonte

Leggi altre domande sui tag