Qual è il modo comune per gestire la visibilità nelle biblioteche?

12

Questa domanda su quando utilizzare private e quando utilizzare protetto in classi mi ha fatto pensare. (Estenderò questa domanda anche alle classi e ai metodi finali, poiché è correlata. Sto programmando in Java, ma penso che questo sia rilevante per ogni lingua OOP)

La risposta accettata sais:

A good rule of thumb is: make everything as private as possible.

E un altro:

  1. Make all classes final unless you need to subclass them right away.
  2. Make all methods final unless you need to subclass and override them right away.
  3. Make all method parameters final unless you need to change them within the body of the method, which is kinda awkward most of the times anyways.

Questo è abbastanza semplice e chiaro, ma cosa succede se sto principalmente scrivendo librerie (Open Source su GitHub) invece di applicazioni?

Potrei nominare un sacco di librerie e situazioni, dove

  • Una biblioteca è stata estesa in un modo che gli sviluppatori non avrebbero mai pensato
  • Questo doveva essere fatto con "class loader magic" e altri hack a causa dei vincoli di visibilità
  • Le librerie sono state utilizzate in un modo per il quale non sono state create e la funzionalità necessaria "hacked" in
  • Non è stato possibile utilizzare le librerie a causa di un piccolo problema (bug, funzionalità mancante, comportamento "errato") che non è stato possibile modificare a causa della visibilità ridotta
  • Un problema che non poteva essere risolto ha portato a soluzioni alternative enormi, brutte e infestate in cui la possibilità di ignorare una funzione semplice (privata o finale) avrebbe potuto aiutare

E in effetti ho iniziato a nominarli finché la domanda non è stata 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 reagisce se il codice è destinato ad essere utilizzato da altri sviluppatori, dove è improbabile che lo sviluppatore originale pensasse a tutti i possibili casi d'uso in anticipo e che i cambiamenti / i refattori siano difficili / impossibili da realizzare?

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

    
posta piegames 23.08.2017 - 14:05
fonte

3 risposte

15

La sfortunata verità è che molte librerie ricevono scritte , non progettate . Questo è triste, perché un po 'di pensiero prevenuto può prevenire un sacco di problemi lungo la strada.

Se ci proponiamo di progettare una biblioteca, ci saranno alcuni casi d'uso previsti. La libreria potrebbe non soddisfare tutti i casi d'uso direttamente, ma può servire come 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 la fonte potrebbe non essere disponibile, e per le librerie open source potrebbe non essere desiderabile mantenere una versione biforcuta. Potrebbe non essere possibile unire adattamenti altamente specifici nel progetto upstream.

È qui che entra in gioco il principio di open-closed: la libreria dovrebbe essere aperta all'estensione senza modificare il codice sorgente. Questo non viene naturalmente. Questo deve essere un obiettivo di progettazione intenzionale. C'è una vasta gamma di tecniche che possono aiutarti, i classici modelli di progettazione OOP sono alcuni di questi. In generale, specifichiamo gli hook in cui il codice utente può collegarsi in modo sicuro alla libreria e aggiungere funzionalità.

Rendere pubblico ogni metodo o consentire a ogni classe di essere sottoclasse non è sufficiente per raggiungere l'estensibilità. Prima di tutto, è davvero difficile estendere la libreria se non è chiaro dove l'utente possa connettersi alla libreria. Per esempio. sovrascrivere la maggior parte dei metodi non è sicuro perché il metodo della classe base è stato scritto con presupposti impliciti. Hai davvero bisogno di progettare per l'estensibilità.

Ancora più importante, una volta che qualcosa fa parte dell'API pubblica non puoi riprenderlo. Non puoi refactoring senza rompere il codice downstream. L'apertura prematura limita la libreria a un design subottimale. Al contrario, rendere le cose interne private, ma aggiungere ganci se in seguito è necessario per loro è un approccio più sicuro. Mentre questo è un modo sano per affrontare l'evoluzione a lungo termine di una libreria, questo è insoddisfacente per gli utenti che hanno bisogno di utilizzare la libreria adesso .

Quindi cosa succede invece? Se c'è un dolore significativo con lo stato attuale della libreria, gli sviluppatori possono prendere tutte le informazioni sui casi di utilizzo reali accumulati nel tempo e scrivere una versione 2 della libreria. Sarà fantastico! Risolverà tutti quegli errori di progettazione! Richiederà anche più tempo del previsto, in molti casi svanendo. E se la nuova versione è molto dissimile dalla vecchia versione, potrebbe essere difficile incoraggiare gli utenti a migrare. Ti rimane quindi il mantenimento di due versioni incompatibili.

    
risposta data 23.08.2017 - 14:59
fonte
8

Ogni classe / metodo pubblico ed estensibile è una parte della tua API che deve essere supportata. Limitare quella impostata su un sottoinsieme ragionevole della libreria consente la massima stabilità e limita il numero di cose che possono andare storte. È una decisione gestionale (e anche i progetti OSS sono gestiti fino a un certo punto) in base a ciò che puoi ragionevolmente supportare.

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

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

Nei progetti maturi, quello che vedrai è qualcosa di simile:

  1. Qualcuno vuole fare qualcosa con la libreria che non era stata originariamente progettata per fare
  2. Aggiungono un ticket per il monitoraggio 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 con priorità 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. Comunque la crescita deve essere modellata in qualche modo. Mantenendo tutto chiuso fino a quando non vi è una comprovata necessità di aprire le cose, si evita di ottenere la reputazione di una libreria bug o instabile.

    
risposta data 23.08.2017 - 14:51
fonte
0

Riformulirò la mia risposta poiché sembra che abbia colpito i nervi di poche 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 la visibilità esiste, è perché gli oggetti sono fragili a 4 problemi specifici:

  1. concorrenza

Se si costruisce il modulo non incapsulato, gli utenti si abitueranno a modificare direttamente lo stato del modulo. Funziona bene in un singolo ambiente con thread, ma una volta che pensi di aggiungere thread; sarai costretto a rendere privato lo stato e usare serrature / monitor insieme a getter e setter che fanno in modo che altri thread attenderanno le risorse, anziché correre su di loro. Ciò significa che i programmi degli utenti non funzioneranno più perché non è possibile accedere alle variabili private in modo convenzionale. Ciò significa che hai bisogno di molte riscritture.

La verità è che è molto più semplice codificare con un solo runtime threaded in mente, e la parola chiave private consente di aggiungere semplicemente la parola chiave sincronizzata, o alcuni blocchi, e il codice degli utenti non si interromperà se lo si incapsula dall'inizio.

  1. Aiuta a impedire agli utenti di spararsi nel piede / semplificare l'uso dell'interfaccia. In sostanza, ti aiuta a controllare gli invarianti dell'oggetto.

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

Questo è inevitabile, ma puoi evitare di mettere accidentalmente un oggetto in uno stato incoerente facendo chiudere un'interfaccia sui suoi servizi che previene i crash accidentali permettendo all'utente di interagire con lo stato dell'oggetto attraverso un'interfaccia accuratamente predisposta che rende il programma molto più robusto. Questo non significa che l'utente non possa danneggiare intenzionalmente gli invarianti, ma se lo fanno, è il loro client che si blocca, tutto ciò che devono fare è riavviare il programma (i dati che si desidera proteggere non dovrebbero essere archiviati sul client ).

Un altro bell'esempio in cui puoi migliorare l'usabilità dei tuoi moduli è rendere privato il costruttore; perché se il costruttore lancia un'eccezione, ucciderà il programma. Un approccio pigro alla risoluzione di questo è di fare in modo che il costruttore lanci 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 statica pubblico, è possibile fare in modo che il metodo di creazione restituisca null se non lo costruisce, o accetta una funzione di callback per gestire l'errore, rendendo il programma più user friendly.

  1. Inquinamento del campo di applicazione

Molte classi hanno un sacco di stati e metodi ed è facile essere sopraffatti cercando di scorrerli; Molti di questi metodi sono solo disturbi visivi come funzioni di supporto, stato. rendere le variabili e i metodi privati aiuta a ridurre l'inquinamento da campo e rende più facile per l'utente trovare i servizi che sta cercando.

In sostanza, ti permette di farla franca con le funzioni di aiuto all'interno della classe piuttosto che all'esterno della classe; senza controllo della visibilità senza distrarre l'utente con una serie di servizi che l'utente non dovrebbe mai utilizzare, in modo da poter utilizzare i metodi di scomposizione in una serie di metodi di supporto (anche se continuerà a inquinare l'ambito ma non quello dell'utente). / p>

  1. essere legati alle dipendenze

Un'interfaccia ben realizzata può nascondere i suoi database / windows / imaging interni a seconda di come fa il suo lavoro, e se vuoi passare a un altro database / altro sistema di finestre / un'altra libreria di immagini, puoi mantenere l'interfaccia stessa e gli utenti non se ne accorgeranno.

D'altro canto, 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 insostenibile, mentre un incapsulamento può proteggere gli utenti dei clienti che si comportano bene dalle decisioni future per scambiare le dipendenze.

    
risposta data 23.08.2017 - 14:17
fonte

Leggi altre domande sui tag