Qual è il modo comune di gestire la visibilità nelle librerie?


12

Questa domanda su quando usare il privato e quando usare la protezione in classe mi ha fatto riflettere. (Estenderò questa domanda anche alle classi e ai metodi finali, poiché è correlata. Sto programmando in Java, ma penso che questo sia rilevante per ogni linguaggio OOP)

La risposta accettata dice:

Una buona regola empirica è: rendere tutto il più privato possibile.

E un altro:

  1. Rendi tutte le classi definitive a meno che non sia necessario sottoclassarle immediatamente.
  2. Rendi definitivi tutti i metodi a meno che non sia necessario sottoclassarli e sovrascriverli immediatamente.
  3. Rendi definitivi tutti i parametri del metodo, a meno che non sia necessario modificarli all'interno del corpo del metodo, il che è comunque un po 'imbarazzante la maggior parte delle volte.

Questo è piuttosto semplice e chiaro, ma cosa succede se scrivo principalmente librerie (Open Source su GitHub) anziché applicazioni?

Potrei nominare molte librerie e situazioni, dove

  • Una libreria è stata estesa in un modo che gli sviluppatori non avrebbero mai pensato
  • Questo doveva essere fatto con "magia caricatore di classi" e altri hack a causa dei vincoli di visibilità
  • Le librerie sono state utilizzate in un modo per cui non sono state costruite e il modo in cui le funzionalità necessarie sono state "hackerate"
  • Le librerie non possono essere utilizzate a causa di un piccolo problema (bug, funzionalità mancante, comportamento "errato") che non può essere modificato a causa della ridotta visibilità
  • Un problema che non poteva essere risolto ha portato a soluzioni alternative enormi, brutte e buggy in cui l'esclusione di una funzione semplice (che era privata o finale) avrebbe potuto aiutare

E in realtà ho iniziato a nominare questi fino a quando la domanda è diventata troppo lunga e ho deciso di rimuoverli.

Mi piace l'idea di non avere più codice del necessario, più visibilità del necessario, più astrazione del necessario. E questo potrebbe funzionare quando si scrive un'applicazione per l'utente finale, in cui il codice viene utilizzato solo da chi lo scrive. Ma come regge questo se il codice deve essere utilizzato da altri sviluppatori, dove è improbabile che lo sviluppatore originale abbia pensato in anticipo a ogni possibile caso d'uso e che le modifiche / i refactor siano difficili / impossibili da realizzare?

Dato che le grandi biblioteche open source non sono una novità, qual è il modo più comune di gestire la visibilità in tali progetti con linguaggi orientati agli oggetti?



dato che chiedi informazioni sull'open source, ha ancora meno senso piegare i giusti principi di codifica al fine di affrontare i problemi che hai elencato rispetto all'origine chiusa, semplicemente perché uno può contribuire con le correzioni necessarie direttamente nel codice della libreria o borsare e creare la propria versione con qualunque correzione vogliano
moscerino il

2
il mio punto non riguarda questo, ma il tuo riferimento all'open source non ha senso in questo contesto. Posso immaginare come i bisogni pragmatici possano giustificare la deviazione dai principi rigorosi in alcuni casi (noto anche come accumulo di debito tecnico ), ma da questa prospettiva non importa se il codice è chiuso o open source. O più precisamente importa nella direzione opposta rispetto a quella che hai immaginato qui perché il codice essendo open source può rendere questi bisogni meno urgenti che chiusi perché offre opzioni aggiuntive per risolverli
moscerino

1
@piegames: sono pienamente d'accordo a gnat qui, i problemi che hai cercato sono molto più probabili che si verifichino nelle librerie di sorgenti chiuse - se si tratta di una lib del sistema operativo con una licenza permissiva, se i manutentori ignorano una richiesta di modifica, si può fork la lib e modificare la visibilità da soli, se necessario.
Doc Brown,

1
@piegames: non capisco la tua domanda. "Java" è una lingua, non una lib. E se "la tua piccola libreria open source" ha una visibilità troppo rigorosa, l'estensione della visibilità in seguito non interrompe normalmente la retrocompatibilità. Solo il contrario.
Doc Brown,

Risposte:


15

La sfortunata verità è che molte biblioteche vengono scritte , non progettate . Questo è triste, perché un po 'di pensiero precedente può prevenire molti problemi lungo la strada.

Se decidiamo di progettare una biblioteca, ci saranno alcuni casi d'uso previsti. La libreria potrebbe non soddisfare direttamente tutti i casi d'uso, ma potrebbe essere parte di una soluzione. Quindi la biblioteca deve essere abbastanza flessibile da adattarsi.

Il vincolo è che di solito non è una buona idea prendere il codice sorgente della libreria e modificarlo per gestire il nuovo caso d'uso. Per le librerie proprietarie il sorgente potrebbe non essere disponibile e per le librerie open source potrebbe essere indesiderato mantenere una versione con fork. Potrebbe non essere possibile unire adattamenti altamente specifici nel progetto a monte.

È qui che entra in gioco il principio aperto-chiuso: la libreria dovrebbe essere aperta all'estensione senza modificare il codice sorgente. Questo non è naturale. Questo deve essere un obiettivo di progettazione intenzionale. Ci sono molte tecniche che possono aiutare qui, i classici modelli di design OOP sono alcuni di questi. In generale, specifichiamo hook in cui il codice utente può collegarsi in modo sicuro alla libreria e aggiungere funzionalità.

Rendere pubblico ogni metodo o consentire la sottoclasse di ogni classe non è sufficiente per raggiungere l'estensibilità. Prima di tutto, è davvero difficile estendere la libreria se non è chiaro dove l'utente possa agganciarsi alla libreria. Ad esempio, ignorare la maggior parte dei metodi non è sicuro perché il metodo della classe base è stato scritto con ipotesi implicite. Devi davvero progettare per l'estensibilità.

Ancora più importante, una volta che qualcosa fa parte dell'API pubblica non è possibile ripristinarla. Non è possibile riformattare senza rompere il codice a valle. L'apertura prematura limita la libreria a un design non ottimale. Al contrario, rendere le cose interne private ma aggiungere hook se più tardi ce n'è bisogno è un approccio più sicuro. Anche se questo è un modo sano di affrontare l'evoluzione a lungo termine di una libreria, ciò non è soddisfacente per gli utenti che devono utilizzare la libreria in questo momento .

Quindi cosa succede invece? Se lo stato attuale della libreria presenta problemi significativi, gli sviluppatori possono acquisire tutte le conoscenze sui casi d'uso reali accumulati nel tempo e scrivere una versione 2 della libreria. Sarà fantastico! Risolverà tutti quei bug di design! Ci vorrà anche più tempo del previsto, in molti casi svanendo. E se la nuova versione è molto diversa da quella precedente, potrebbe essere difficile incoraggiare gli utenti a migrare. Ti rimangono quindi due versioni incompatibili.


Quindi ho bisogno di aggiungere hook per l'estensione perché renderlo pubblico / ignorabile non è sufficiente. E devo anche pensare a quando rilasciare modifiche / nuova API a causa della compatibilità con le versioni precedenti. Ma per quanto riguarda la visibilità dei metodi in particolare?
piegames

@piegames Con la visibilità, decidi quali parti sono pubbliche (parte dell'API stabile) e quali parti sono private (soggette a modifiche). Se qualcuno lo elude con la riflessione, è il loro problema quando quella funzionalità si romperà in futuro. A proposito, i punti di estensione sono spesso sotto forma di un metodo che può essere ignorato. Ma c'è una differenza tra un metodo che può essere ignorato, e un metodo che è destinato a essere sovrascritto (si veda anche il pattern Template Method).
amon

8

Ogni classe / metodo pubblico ed estensibile fa parte dell'API che deve essere supportata. Limitare quello impostato su un sottoinsieme ragionevole della libreria consente la massima stabilità e limita il numero di cose che possono andare storte. È una decisione di gestione (e anche i progetti OSS sono gestiti in una certa misura) in base a ciò che puoi ragionevolmente supportare.

La differenza tra OSS e closed source è che la maggior parte delle persone sta cercando di creare e far crescere una comunità attorno al codice in modo che sia più di una persona a gestire la libreria. Detto questo, sono disponibili numerosi strumenti di gestione:

  • Le mailing list parlano delle esigenze degli utenti e di come implementare le cose
  • I sistemi di tracciamento dei problemi (problemi JIRA o Git, ecc.) Tengono traccia dei bug e delle richieste di funzionalità
  • Il controllo versione gestisce il codice sorgente.

Nei progetti maturi, ciò che vedrai è qualcosa del genere:

  1. Qualcuno vuole fare qualcosa con la libreria che inizialmente non è stata progettata
  2. Aggiungono un ticket al rilevamento dei problemi
  3. Il team può discutere il problema nella mailing list o nei commenti e il richiedente è sempre invitato a partecipare alla discussione
  4. La modifica dell'API è accettata e prioritaria o rifiutata per qualche motivo

A quel punto, se la modifica è stata accettata ma l'utente desidera accelerare la correzione, può eseguire il lavoro e inviare una richiesta pull o una patch (a seconda dello strumento di controllo della versione).

Nessuna API è statica. Tuttavia, la sua crescita deve essere modellata in qualche modo. Tenendo tutto chiuso fino a quando non vi è una dimostrata necessità di aprire le cose, si evita di ottenere la reputazione di una libreria difettosa o instabile.


1
Sono pienamente d'accordo e ho esercitato con successo il processo di richiesta di modifica che hai cercato anche per le librerie di terze parti a codice chiuso, nonché per le librerie a codice aperto.
Doc Brown,

Nella mia esperienza, anche i piccoli cambiamenti nelle piccole librerie sono molto impegnativi (anche se solo per convincere gli altri) e potrebbero richiedere del tempo (attendere la prossima versione se non puoi permetterti di usare un'istantanea fino ad allora). Quindi questa chiaramente non è un'opzione per me. Sarei interessato però: ci sono librerie più grandi (in GitHub) che usano davvero quel concetto?
piegames

C'è sempre molto lavoro. Quasi ogni progetto a cui ho contribuito ha un processo simile a quello. Nei miei giorni di Apache potremmo discutere qualcosa per giorni perché eravamo appassionati di ciò che abbiamo creato. Sapevamo che molte persone avrebbero usato le librerie, quindi abbiamo dovuto discutere di cose come se la modifica proposta avrebbe infranto l'API, vale la pena la funzionalità proposta, quando dovremmo farlo? ecc.
Berin Loritsch,

0

Riformulerò la mia risposta poiché sembra che abbia colpito un po 'di nervosismo con alcune persone.

La visibilità della proprietà / metodo della classe non ha nulla a che fare con la sicurezza né l'apertura della fonte.

Il motivo per cui esiste la visibilità è perché gli oggetti sono fragili a 4 problemi specifici:

  1. concorrenza

Se il modulo viene creato senza incapsulamento, gli utenti si abitueranno a modificare direttamente lo stato del modulo. Funziona bene in un singolo ambiente thread, ma una volta che hai persino pensato di aggiungere thread; sarai costretto a rendere privato lo stato e ad utilizzare blocchi / monitor insieme a getter e setter che fanno sì che altri thread aspettino le risorse, piuttosto che correre su di loro. Ciò significa che i programmi degli utenti non funzioneranno più perché non è possibile accedere alle variabili private in modo convenzionale. Questo può significare che hai bisogno di molte riscritture.

La verità è che è molto più facile programmare con un singolo runtime thread in mente e la parola chiave privata ti consente di aggiungere semplicemente la parola chiave sincronizzata o alcuni blocchi e il codice dei tuoi utenti non si spezzerà se lo incapsuli dall'inizio .

  1. Aiuta a prevenire che gli utenti si sparino nell'uso semplificato dell'interfaccia. In sostanza, ti aiuta a controllare gli invarianti dell'oggetto.

Ogni oggetto ha un sacco di cose che deve essere vero per essere in stato coerente. Sfortunatamente, queste cose vivono nello spazio visibile del cliente perché è costoso spostare ogni oggetto nel suo processo e parlarci attraverso i messaggi. Ciò significa che è molto facile per un oggetto bloccare l'intero programma se l'utente ha la piena visibilità.

Ciò è inevitabile, ma è possibile evitare di mettere accidentalmente un oggetto in uno stato incoerente effettuando una chiusura dell'interfaccia sui suoi servizi che previene arresti accidentali consentendo all'utente di interagire con lo stato dell'oggetto solo attraverso un'interfaccia accuratamente predisposta che rende il programma molto più robusto . Questo non significa che l'utente non possa corrompere intenzionalmente gli invarianti, ma se lo fanno, è il loro client che si arresta in modo anomalo, tutto ciò che devono fare è riavviare il programma (i dati che si desidera proteggere non devono essere archiviati sul lato client ).

Un altro bell'esempio in cui puoi migliorare l'usabilità dei tuoi moduli è rendere privato il costruttore; perché se il costruttore genera un'eccezione, ucciderà il programma. Un approccio pigro per risolvere questo problema è far sì che il costruttore generi un errore di compilazione che non è possibile costruirlo a meno che non si trovi in ​​un blocco try / catch. Rendendo privato il costruttore e aggiungendo un metodo di creazione statico pubblico, è possibile rendere nullo il metodo di creazione se non riesce a costruirlo o utilizzare una funzione di callback per gestire l'errore, rendendo il programma più intuitivo.

  1. Inquinamento dell'ambito

Molte classi hanno molto stato e metodi ed è facile lasciarsi sopraffare dal tentativo di scorrere; Molti di questi metodi sono solo disturbi visivi come funzioni di supporto, stato. rendere private variabili e metodi aiuta a ridurre l'inquinamento dell'ambito e a facilitare all'utente la ricerca dei servizi che sta cercando.

In sostanza, ti permette di cavartela con le funzioni di supporto all'interno della classe anziché all'esterno della classe; senza controllo di visibilità senza distrarre l'utente con una serie di servizi che l'utente non dovrebbe mai usare, quindi puoi cavartela con i metodi di suddivisione in una serie di metodi di supporto (sebbene inquinerà ancora il tuo ambito, ma non l'utente).

  1. essere legato alle dipendenze

Un'interfaccia ben progettata può nascondere i suoi database interni / windows / imaging da cui dipende per fare il suo lavoro, e se vuoi passare a un altro database / un altro sistema di finestre / un'altra libreria di immagini, puoi mantenere l'interfaccia uguale e gli utenti non lo noterò.

D'altra parte, se non lo fai, puoi facilmente cadere nel rendere impossibile cambiare le tue dipendenze, perché sono esposte e il codice si basa su di esso. Con un sistema abbastanza grande, il costo della migrazione può diventare inaccessibile, mentre un incapsulamento può proteggere gli utenti client che si comportano bene dalle decisioni future per sostituire le dipendenze.


1
"Non ha senso nascondere qualcosa" - allora perché pensare all'incapsulamento? In molti contesti la riflessione richiede privilegi speciali.
Frank Hileman,

Pensi all'incapsulamento perché ti dà respiro durante lo sviluppo del tuo modulo e riduci la probabilità di un uso improprio. Ad esempio, se hai 4 thread che modificano direttamente lo stato interno di una classe, ciò causerà facilmente problemi, mentre rendendo privata la variabile, incoraggia l'utente a utilizzare i metodi pubblici per manipolare lo stato mondiale, che può utilizzare monitor / blocchi per prevenire problemi . Questo è l'unico vero vantaggio dell'incapsulamento.
Dmitry,

Nascondere le cose per motivi di sicurezza è un modo semplice per realizzare un progetto in cui finisci per dover avere buchi nella tua api. Un buon esempio di ciò sono le applicazioni multi-documento, in cui ci sono molte cassette degli strumenti e molte finestre con finestre secondarie. Se impazzisci sull'incapsulamento, finirai per avere una situazione in cui disegnare qualcosa su un documento, devi chiedere alla finestra di chiedere al documento interno di chiedere al documento interno di chiedere al documento interno di fare una richiesta per disegnare qualcosa e invalidare il suo contesto. Se il lato client vuole giocare con il lato client, non puoi impedirli.
Dmitry,

OK ha più senso, anche se la sicurezza può essere raggiunta tramite il controllo degli accessi se l'ambiente lo supporta, ed era uno degli obiettivi originali nella progettazione del linguaggio OO. Inoltre stai promuovendo l'incapsulamento e dicendo di non usarlo contemporaneamente; un po 'confuso.
Frank Hileman,

Non ho mai avuto intenzione di non usarlo; Intendevo non usarlo per motivi di sicurezza; usalo strategicamente per migliorare l'esperienza dei tuoi utenti e per darti un ambiente di sviluppo più fluido. il mio punto è che non ha nulla a che fare con la sicurezza né l'apertura della fonte. Gli oggetti lato client sono per definizione vulnerabili all'introspezione e spostarli fuori dallo spazio del processo dell'utente rende le cose non incapsulate ugualmente inaccessibili come incapsulate.
Dmitry,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.