7 consigli su come far risplendere la tua libreria Kotlin

Prefazione

Quando iniziamo a programmare in un nuovo linguaggio, all'inizio ci atteniamo spesso ai paradigmi e alle abitudini sviluppati durante la programmazione nel linguaggio che già conosciamo. Sebbene all'inizio possa sembrare a posto, per le persone che programmano nella lingua che stai cercando di padroneggiare per un po 'questa rugosità è abbastanza ovvia.

Quando ero appena passato da Java a Kotlin circa 2 anni fa, fondamentalmente stavo facendo "programmazione Java ma a Kotlin". Anche se Kotlin era come una boccata d'aria fresca per me, come ingegnere Android stavo usando tutte quelle funzioni di estensione e classi di dati banali e non ero sempre coerente. Stavo convertendo il codice Java esistente in Kotlin e guardandolo dopo qualche tempo mi ha aiutato a tenere a mente alcune idee quando ho progettato API per diversi componenti o librerie riutilizzabili in Kotlin.

Qui a BUX, stiamo lavorando su due applicazioni che hanno un paio di librerie condivise. Mentre l'applicazione Azioni (che non è ancora stata rilasciata) è stata scritta in Kotlin dall'inizio, la prima applicazione CFD è stata scritta in Java e successivamente convertita in Kotlin dopo un po '. Al momento, l'applicazione CFD è Kotlin al 78,7%, il che significa che ci stiamo ancora lavorando ed è per questo che è importante prestare attenzione alle API della libreria gestite da due team che utilizzano sia Java che Kotlin allo stesso tempo.

Quindi, se disponi di una libreria Kotlin o Java esistente che desideri Kotlinify o stai progettando un'API che utilizza Kotlin da zero, porta con me alcune idee su cosa puoi fare per rendere più facile la vita degli utenti della tua biblioteca.

1. Funzioni di estensione

La maggior parte delle volte quando è necessario estendere una classe esistente con una nuova funzionalità, si utilizza una sorta di composizione o si ricava una nuova classe (che, se utilizzata estesamente, può rendere la gerarchia delle classi fragile come le tazze di vetro di IKEA). Qualunque sia la tua preferenza, Kotlin ha una sua risposta per questo, chiamata Funzioni di estensione. In poche parole, ti consente di aggiungere una nuova funzione a una classe esistente con una sintassi piuttosto ordinata. In Android, ad esempio, puoi definire un nuovo metodo per la classe View nel modo seguente:

Tenendo presente questo, molte librerie che forniscono funzionalità aggiuntive per le classi esistenti (ad es. View, Context etc), invece di usare decoratori, metodi di fabbrica statici o qualcosa di diverso possono trarre vantaggio dal fornire le loro funzionalità come Extension senza soluzione di continuità, come se questo la funzionalità esisterebbe nelle classi originali dall'inizio.

2. Valori argomento predefiniti

Kotlin è stato originariamente progettato per essere una versione concisa, ordinata e ordinata di Java e si scopre che lo fa molto bene con valori di argomento di una funzione predefinita. Il provider dell'API è in grado di specificare i valori predefiniti per gli argomenti che potrebbero essere omessi. Sarei davvero sorpreso se non avessi visto codice simile mentre lavoravi con SQLite su Android:

Anche se ci sono più difetti in questo metodo di quei brutti nulli alla fine, sono qui non per giudicare ma piuttosto per dire che questi tempi sono passati e vorrei seriamente mettere in discussione il codice scritto in questo modo in Kotlin. Ci sono molti esempi come questo ma, per fortuna, ora possiamo fare di meglio e se fornisci un'API esterna con determinati parametri di ottimizzazione, invece di, o in aggiunta alla fornitura di un Builder, prendi in considerazione l'utilizzo di valori di argomento predefiniti. Ciò avrà almeno due vantaggi: prima di tutto, non costringerai un utente a specificare parametri opzionali o qualunque cosa tu possa prevedere in anticipo e il secondo è che l'utente avrà una configurazione predefinita che funzionerà semplicemente -scatola:

Inoltre, una buona cosa da menzionare qui è che se si mettono alla fine quegli argomenti con valori predefiniti, un utente non dovrà specificare i nomi per i parametri obbligatori.

3. Oggetti

Hai mai dovuto implementare tu stesso il modello Singleton in Java? Probabilmente hai avuto e se è così dovresti sapere quanto potrebbe essere ingombrante a volte:

Esistono diverse implementazioni ognuna con i propri pro e contro. Kotlin affronta tutte quelle 50 sfumature dell'implementazione del modello Singleton con un'unica struttura chiamata dichiarazione di oggetti. Ma guarda, nessun "blocco controllato due volte":

Ciò che è interessante a parte la sintassi è che l'inizializzazione della dichiarazione di oggetto è sia thread-safe che inizializzata pigramente quando si accede per la prima volta:

Per librerie come Fabric, Glide, Picasso o qualsiasi altra che utilizzi una singola istanza dell'oggetto come punto di accesso principale all'API generale della libreria, è un modo naturale di procedere ora e non c'è motivo di utilizzare il vecchio modo Java per fare le stesse cose.

4. File di origine

Allo stesso modo in cui Kotlin ripensa a molte sfide sintattiche che affrontiamo quotidianamente, ripensa anche al modo in cui organizziamo il codice che creiamo. I file sorgente di Kotlin servono come home per più dichiarazioni semanticamente vicine l'una all'altra. Si scopre che sono il luogo perfetto per definire alcune funzioni di estensione relative alla stessa classe. Dai un'occhiata a questo frammento semplificato dal codice sorgente di Kotlin, dove tutte le funzioni di estensione utilizzate per manipolare il testo si trovano nello stesso file "Strings.kt":

Un altro esempio appropriato del suo utilizzo è la definizione di un protocollo di comunicazione con l'API insieme alle classi di dati e alle interfacce in un singolo file di origine. Permetterà a un utente di non perdere la concentrazione passando da un file all'altro mentre segue un flusso:

Ma non andare troppo lontano con esso poiché si rischia di sopraffare un utente con le dimensioni del file e trasformarlo in una classe RecyclerView con ~ 13.000 righe .

5. Coroutine

Se la tua libreria utilizza più thread per accedere a una rete o eseguire qualsiasi altro lavoro di lunga durata, considera di fornire un'API con funzioni di sospensione. A partire da Kotlin 1.3 le coroutine non sono più sperimentali, il che è una grande opportunità per iniziare a usarle in produzione se ne hai avuto dei dubbi in precedenza. Le coroutine in alcuni casi potrebbero essere una buona alternativa all'Osservabile da RxJava e ad altri modi di gestire le chiamate asincrone. Quello che potresti aver già visto prima è l'API piena di metodi con callback dei risultati o al momento dell'hype Rx racchiuso con Single o Completable:

Ma ora siamo nell'era di Kotlin, quindi potrebbe essere progettato anche per l'utilizzo delle coroutine:

Nonostante il fatto che le coroutine abbiano prestazioni migliori rispetto ai buoni vecchi thread Java in termini di maggiore leggerezza, contribuiscono in modo significativo alla leggibilità del codice:

6. Contratti

Insieme a coroutine stabili, Kotlin 1.3 offre anche agli sviluppatori il modo di interagire con un compilatore. Un contratto è una nuova funzionalità che ci consente come sviluppatori di librerie di condividere con un compilatore le conoscenze che abbiamo specificando i cosiddetti effetti. Per semplificare la comprensione dell'effetto, portiamo la sua più vicina analogia da Java. Molti di voi probabilmente hanno visto o addirittura usato la lezione di Precondizione dalla Guava con molte affermazioni e il suo adattamento all'interno di biblioteche come Dagger che non vogliono recuperare del tutto un'intera biblioteca:

Se l'oggetto di riferimento passato è nullo, il metodo genererà un'eccezione e il codice rimanente inserito dopo che questo metodo non sarà raggiunto, quindi possiamo tranquillamente supporre che il riferimento non sia nullo lì. Ecco il problema: anche se abbiamo questa conoscenza, non aiuta il compilatore a fare la stessa ipotesi. È qui che entrano in scena le annotazioni @ Nullable e @ NotNull poiché esistono esclusivamente per aiutare il compilatore a comprendere le condizioni. Questo è davvero un effetto: è un suggerimento per un compilatore che lo aiuta a eseguire analisi di codice più sofisticate. Un effetto esistente che hai già visto in Kotlin è il cast intelligente di un certo tipo nel blocco dopo averne verificato il tipo:

Il compilatore di Kotlin è già intelligente e senza dubbio continuerà a migliorare in futuro, ma con l'introduzione dei Contratti, gli sviluppatori di Kotlin ci hanno dato come sviluppatori di biblioteche il potere di migliorarlo scrivendo i nostri smart-cast e creando effetti che aiutano i nostri utenti a scrivere un codice più pulito. Ora, riscriviamo il metodo checkNotNul in Kotlin usando Contracts per dimostrare le loro capacità:

Ci sono anche diversi effetti, ma questa non è un'introduzione a grandezza naturale ai contratti e spero che ti abbia dato un'idea di come puoi trarne vantaggio. Inoltre, il repository stdlib su Github ha molti utili esempi di contratti che vale la pena verificare.

Da un grande potere derivano grandi responsabilità e i contratti non fanno eccezione. Qualunque cosa tu dichiari nel tuo Contratto è trattata dal compilatore come la Sacra Bibbia. Più tecnicamente il compilatore non mette in discussione o convalida tutto ciò che scrivi lì, quindi devi ispezionare attentamente il tuo codice da solo e assicurarti di non introdurre incoerenze.

Come avrai notato dall'annotazione @ExperimentalContracts, i contratti sono ancora in fase sperimentale, quindi non solo l'API potrebbe cambiare nel tempo, ma anche alcune nuove funzionalità potrebbero derivare da essa man mano che diventa sempre più matura.

7. Interoperabilità Java

Ciò che è importante anche durante la scrittura di una libreria in Kotlin è di allargare il pubblico fornendo un'esperienza di integrazione fluida per i tuoi colleghi sviluppatori che usano Java. È molto importante poiché ci sono ancora molti progetti che si attaccano a Java e non vogliono riscrivere il codice esistente per vari motivi. Dal momento che vogliamo che la community di Kotlin cresca più velocemente, è meglio consentire a quei progetti di farlo gradualmente, passo dopo passo. Certo, non è così soleggiato da questo lato di The Wall ma, ci sono alcune cose che tu come manutentore della biblioteca puoi fare al riguardo.

La prima cosa è che le funzioni di estensione di Kotlin in Java verranno compilate in brutti metodi statici in cui il primo argomento del metodo è un tipo di ricevitore:

Anche se qui non hai molto controllo, puoi almeno cambiare il nome della classe generata aggiungendo la seguente riga all'inizio del file sorgente contenente le funzioni a livello di pacchetto sopra:

Un altro esempio di come è possibile influire leggermente su un codice generato è legato all'utilizzo dei metodi di un oggetto associato:

Puoi forzare Kotlin a generare classi statiche al posto delle funzioni definite nell'oggetto companion usando l'annotazione @JvmStatic:

Rispetto a Kotlin, in Java due funzioni con nomi simili ma diversi tipi generici non possono essere definiti insieme a causa della cancellazione del tipo, quindi potrebbe essere un punto fermo per gli utenti Java. Eventualmente, puoi evitarlo cambiando la firma del metodo con un'altra annotazione:

E, ultimo ma non meno importante, è la possibilità di definire eccezioni verificate nelle funzioni per gli utenti Java sebbene non siano direttamente disponibili in Kotlin. Questo potrebbe essere utile poiché il paradigma della dichiarazione di eccezione in Java e Kotlin è diverso:

La maggior parte delle cose qui sono opzionali ma potrebbero certamente essere una fonte di complimenti per la tua libreria dai tuoi utenti Java.

Linea di fondo

Kotlin è un linguaggio eccezionale che va costantemente avanti e ci sono molte cose che puoi fare sulla libreria per renderne più agevole l'utilizzo. Tra tutto ciò che applichi dalle idee sopra, cerca di essere un utente in primo luogo e pensa a cosa vorresti che fosse e come lo useresti. Il diavolo è nei dettagli e, si spera, queste piccole cose che applichi andranno a beneficio dei tuoi utenti e renderanno migliore la loro esperienza. Saluti!