Best practice sulla condivisione del codice tra le librerie open source

5

Ho una libreria Android utilizzata da una discreta quantità di persone, chiamiamola libreria A.

Sto costruendo una seconda libreria, la biblioteca B.

Le due librerie sono concettualmente correlate, risolvono diversi problemi nello stesso dominio. È probabile che le due librerie verranno utilizzate insieme, ma non è obbligatorio.

Il problema: la libreria A contiene alcune classi e interfacce che vorrei riutilizzare nella libreria B.

Ci sono delle buone pratiche su come farlo? Questa è la prima volta che incontro questo problema e, essendo le librerie usate da altre persone, vorrei implementare la soluzione che ha più senso.

Posso vedere tre approcci:

  1. Copia e incolla le interfacce e le classi che voglio riutilizzare, dalla libreria A alla libreria B.
  2. Importa libreria A nella libreria B.
  3. Estrarre dalla libreria A le interfacce e le classi che voglio riutilizzare, pubblicarle sotto una nuova libreria CORE e importare la libreria CORE sia nella libreria A che nella libreria B.

Ci sono altri possibili approcci che non sto considerando?

Problemi che riesco a vedere con queste soluzioni:

  1. Questa è la soluzione peggiore. Non segue il principio DRY e, cosa più importante, un utente di entrambe le librerie finirebbe per avere interfacce come: com.libraryA.Interface1 e com.libraryB.Interface1 . Ciò non consente un'interazione piacevole tra le librerie A e B.
  2. La libreria B importerebbe un mucchio di codice che non ha bisogno. Soprattutto, gli utenti della libreria B dovrebbero sempre importare la libreria A, a causa delle interfacce condivise da A a B.
  3. Questa sembra la soluzione più elegante, ma come dovrei gestire il controllo delle versioni in questa situazione? Sarà sempre richiesta la libreria A, B e CORE per avere lo stesso numero di versione? In caso contrario, avrebbe senso avere numeri di versione diversi per A, B e CORE? Le modifiche al CORE influenzano sia A che B e richiedono un bump di versione su tutti e tre. Le modifiche su A e B verrebbero isolate.

Ci sono altri problemi che non vedo?

    
posta Pierfrancesco Soffritti 17.06.2018 - 15:52
fonte

3 risposte

4

Se la tua terza opzione è quella giusta dipende dalla situazione. La domanda di base è se A contiene attualmente due cose che sono ragionevolmente separate e ciascuna è utile come entità distinta.

Ad esempio, se rompi A in due parti e hai un core con A e B come client, è probabile che anche il core faccia un buon lavoro nel supportare C, D ed E?

Oppure, con A suddiviso in Core e A-prime, ti ritroverai con una singola cosa che è fondamentalmente rotta nel mezzo, e molto probabilmente un C, D o E avrebbe comunque bisogno di usare pezzi sia del Core che di A-prime?

Una volta stabilito ciò, la conclusione probabilmente diventa abbastanza ovvia: se A rappresenta realmente due cose separate che accadono (attualmente) vivono nella stessa libreria, e separandole dà un Core che è probabile che sia più chiaramente utilizzabile da solo, quindi vai avanti e separali.

D'altra parte, se succede che quasi tutti i client di A usano probabilmente l'80% (o qualsiasi altra cosa) di ciò che fornisce, ma ognuno userà un 80% diverso, quindi ha più senso lasciarlo nel suo insieme.

Per quanto riguarda il controllo delle versioni: se lasci A come un singolo pezzo o lo spezzi in due, hai praticamente lo stesso problema: una libreria con (almeno) un'altra che dipende da questo.

Una strategia abbastanza comune consiste nel suddividere il controllo delle versioni in due categorie: modifiche minori sono cose come correzioni di errori, ma per un client che dipende solo da un comportamento documentato, una versione più recente della libreria dovrebbe sostituire le versioni precedenti . Ciò significa che puoi correggere bug e aggiungere funzionalità, ma non apportare modifiche sostanziali al comportamento documentato.

Se apporti una modifica al comportamento documentato, sarebbe una modifica importante della versione. Richiede che un cliente sia a conoscenza del cambiamento.

Quindi, se rompi A a metà o no, hai B a seconda di qualcos'altro. Per le versioni minori, alcuni client potrebbero dipendere dall'avere 1.1 o più recenti, e un altro client potrebbe dipendere dall'avere 1.2 o più recente (ma in entrambi i casi, il "o più recente" include solo 1.x, non alcuni possibili 2.x).

Avere gli stessi numeri di versione per tutti loro è in gran parte un vantaggio quando si hanno un certo numero di librerie che sono chiaramente separate, ma sono (per qualsiasi motivo) usate insieme abbastanza spesso da rendere più facile per un utente tenere traccia di esse insieme, quindi se sta usando sia A che B, non devono tenere traccia di tre numeri di versione separati.

    
risposta data 17.06.2018 - 19:38
fonte
6

La soluzione ovvia e corretta è (2): rendi B un client di A.

Certo, B importerà più del necessario, ma è il caso del 99% dei client di libreria. Sei semplicemente ingannato perché conosci molto bene i componenti interni della libreria A, quindi sei più consapevole dell'inefficienza del normale. I vantaggi della divisione del lavoro superano di gran lunga questa inefficienza concettuale.

    
risposta data 17.06.2018 - 16:15
fonte
-1

L'approccio n. 1 non è necessariamente una cattiva soluzione, dipende. Se A viene mantenuto ed evoluto da un'organizzazione completamente diversa e non puoi influenzare lo sviluppo di A, potresti trovarti in una situazione in cui vuoi avere la tua copia delle classi riutilizzate per assicurarti che sono stabili e li hai sotto il tuo controllo.

La violazione del principio DRY è solo un problema se porta a una situazione in cui è necessario mantenere la stessa cosa in due punti, ma non sarebbe un problema in quella situazione, perché qualcun altro sta mantenendo A, lo farai devi solo mantenere la tua copia delle classi. E il fatto che ci potrebbero essere due versioni della stessa interfaccia in diversi spazi dei nomi non è un problema - perché si trovano in spazi dei nomi diversi.

Tuttavia, se B dovrebbe fare affidamento su A e sarà possibile aggiornare la versione più recente di A, perché vuoi che la manutenzione e l'evoluzione delle classi riutilizzate vengano eseguite dal manutentore di A, quindi vai con l'opzione 2. In quella situazione , Non vedo alcun problema con gli utenti della libreria B che devono sempre importare la libreria A.

L'opzione 3 è fattibile solo quando hai lo sviluppo di A e B entrambi sotto il tuo controllo.

    
risposta data 17.06.2018 - 22:45
fonte