Adotta Kotlin per grandi team

Passare a una nuova lingua può essere un'impresa ardua. La ricetta per il successo è iniziare lentamente, spostare in blocchi ed eseguire spesso test per allineare il tuo team e ottenere risultati ottimali. Kotlin semplifica la migrazione poiché si compila in bytecode JVM ed è completamente interoperabile con Java.

Forma il team

Il primo passaggio prima della migrazione consiste nel creare una comprensione di base comune per il tuo team. Ecco alcuni suggerimenti che potrebbero esserti utili per accelerare l'apprendimento del tuo team.

Gruppi di studio del modulo

I gruppi di studio sono uno strumento efficace per favorire l'apprendimento e la fidelizzazione. Gli studi suggeriscono che citare ciò che hai imparato in un contesto di gruppo aiuta a rafforzare il materiale. Procurati un libro di Kotlin o altro materiale di studio per ogni membro del gruppo e chiedi al gruppo di analizzare un paio di capitoli ogni settimana. Durante ogni incontro, il gruppo deve confrontare quanto appreso e discutere eventuali domande o osservazioni.

Sviluppa una cultura dell'insegnamento

Anche se non tutti si considerano insegnanti, tutti possono insegnare. Dal lead della tecnologia o del team al singolo collaboratore, tutti possono incentivare un ambiente di apprendimento che contribuisca al successo. Un modo per favorire questo processo è organizzare presentazioni periodiche in cui una persona del team è incaricata di parlare di qualcosa che ha imparato o che vuole condividere. Puoi sfruttare il tuo gruppo di studio chiedendo a volontari di presentare un nuovo capitolo ogni settimana, finché non raggiungi il punto in cui il tuo team si sente a proprio agio con la lingua.

Designa un campione

Infine, designa un campione che guidi un'attività di apprendimento. Questa persona può agire come esperto in materia (SME) quando inizi il processo di adozione. È importante includere questa persona in tutte le sessioni di esercitazione relative a Kotlin. Idealmente, questa persona è già appassionata di Kotlin e ha alcune conoscenze lavorative.

Integra lentamente

È fondamentale iniziare lentamente e pensare in modo strategico alle parti del tuo ecosistema da spostare per prime. Spesso è meglio isolarlo in una singola app all'interno dell'organizzazione piuttosto che in un'app di punta. Per quanto riguarda la migrazione dell'app scelta, ogni situazione è diversa, ma ecco alcuni punti comuni da cui iniziare.

Modello dei dati

Probabilmente il modello dei dati è costituito da molte informazioni sullo stato e da alcuni metodi. Il modello dei dati potrebbe anche avere metodi comuni, come toString(), equals() e hashcode(). In genere, questi metodi possono essere trasferiti e sottoposti a test delle unità facilmente e isolati.

Ad esempio, supponiamo il seguente snippet di Java:

public class Person {

   private String firstName;
   private String lastName;
   // ...

   public String getFirstName() {
       return firstName;
   }

   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public void setLastName(String lastName) {
       this.lastName = lastName;
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Person person = (Person) o;
       return Objects.equals(firstName, person.firstName) &&
               Objects.equals(lastName, person.lastName);
   }

   @Override
   public int hashCode() {
       return Objects.hash(firstName, lastName);
   }

   @Override
   public String toString() {
       return "Person{" +
               "firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               '}';
   }
}

Puoi sostituire la classe Java con una singola riga di Kotlin, come mostrato qui:

data class Person(var firstName: String?, var lastName : String?)

Questo codice può quindi essere testato sulle unità rispetto alla tua suite di test attuale. L'idea qui è di iniziare in piccolo con un modello alla volta e con classi di transizione per lo più basate sullo stato e non sul comportamento. Assicurati di eseguire spesso i test durante la procedura.

Esegui migrazione dei test

Un altro percorso di partenza da considerare è convertire i test esistenti e iniziare a scrivere nuovi test in Kotlin. In questo modo, il tuo team avrà il tempo di sentirsi a proprio agio con la lingua prima di scrivere il codice che prevedi di distribuire con l'app.

Spostare i metodi di utilità nelle funzioni di estensione

Qualsiasi classe di utilità statica (StringUtils, IntegerUtils, DateUtils, YourCustomTypeUtils e così via) può essere rappresentata come funzioni di estensione Kotlin e utilizzate dal codebase Java esistente.

Ad esempio, considera di avere una classe StringUtils con alcuni metodi:

package com.java.project;

public class StringUtils {

   public static String foo(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

   public static String bar(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

}

Questi metodi potrebbero quindi essere utilizzati altrove nella tua app, come mostrato nell'esempio seguente:

...

String myString = ...
String fooString = StringUtils.foo(myString);

...

Con le funzioni di estensione di Kotlin, puoi fornire la stessa interfaccia Utils ai chiamanti Java, offrendo al contempo un'API più concisa per la tua base di codice Kotlin in continua espansione.

Per farlo, puoi iniziare convertendo questa classe Utils in Kotlin utilizzando la conversione automatica fornita dall'IDE. L'output di esempio potrebbe essere simile al seguente:

package com.java.project

object StringUtils {

   fun foo(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

   fun bar(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

}

Quindi, rimuovi la classe o la definizione dell'oggetto, anteponi al nome di ogni funzione il tipo a cui deve essere applicata questa funzione e utilizzalo per fare riferimento al tipo all'interno della funzione, come mostrato nell'esempio seguente:

package com.java.project

fun String.foo(): String {
    return this...;  // Transform the receiver in some way
}

fun String.bar(): String {
    return this...;  // Transform the receiver in some way
}

Infine, aggiungi un'annotazione JvmName nella parte superiore del file di origine per rendere il nome compilato compatibile con il resto dell'app, come mostrato nel seguente esempio:

@file:JvmName("StringUtils")
package com.java.project
...

La versione finale dovrebbe essere simile alla seguente:

@file:JvmName("StringUtils")
package com.java.project

fun String.foo(): String {
    return this...;  // Transform `this` string in some way
}

fun String.bar(): String {
    return this...;  // Transform `this` string in some way
}

Tieni presente che ora queste funzioni possono essere chiamate utilizzando Java o Kotlin con convenzioni che corrispondono a ogni linguaggio.

Kotlin

...
val myString: String = ...
val fooString = myString.foo()
...

Java

...
String myString = ...
String fooString = StringUtils.foo(myString);
...

Completa la migrazione

Una volta che il tuo team ha acquisito familiarità con Kotlin e hai eseguito la migrazione di aree più piccole, puoi passare ad affrontare componenti più grandi, come frammenti, attività, ViewModel oggetti e altre classi legate alla logica di business.

considerazioni

Proprio come Java ha uno stile specifico, Kotlin ha un proprio stile idiomatico che contribuisce alla sua sintesi. Tuttavia, all'inizio potresti scoprire che il codice Kotlin prodotto dal tuo team assomiglia più al codice Java che sostituisce. Questo cambia nel tempo man mano che l'esperienza Kotlin del tuo team cresce. Ricorda, un cambiamento graduale è la chiave del successo.

Ecco alcune cose che puoi fare per garantire la coerenza man mano che la tua base di codice Kotlin cresce:

Standard di programmazione comuni

Assicurati di definire un set standard di convenzioni di codifica nelle prime fasi del processo di adozione. Puoi divergere dalla guida di stile di Kotlin di Android, se opportuna.

Strumenti di analisi statica

Applica gli standard di programmazione impostati per il tuo team utilizzando Android lint e altri strumenti di analisi statica. klint, un Kotlin linter di terze parti, fornisce inoltre regole aggiuntive per Kotlin.

Integrazione continua

Assicurati di rispettare gli standard di codifica più comuni e di fornire una copertura di test sufficiente per il tuo codice Kotlin. Includere questa parte in un processo di compilazione automatizzato può contribuire a garantire coerenza e conformità a questi standard.

Interoperabilità

Kotlin interagisce con Java per la maggior parte senza problemi, ma tieni presente quanto segue.

Nulla

Kotlin si basa su annotazioni nulla nel codice compilato per dedurre nulla sul lato Kotlin. Se le annotazioni non vengono fornite, Kotlin utilizza per impostazione predefinita un tipo di piattaforma che può essere trattato come tipo null o non null. Tuttavia, questo può causare problemi di runtime NullPointerException, se non trattati con attenzione.

Adotta nuove funzionalità

Kotlin fornisce molte nuove librerie e zucchero sintattico per ridurre il boilerplate, che aiuta ad aumentare la velocità di sviluppo. Detto questo, devi essere prudente e metodico quando utilizzi le funzioni di libreria standard di Kotlin, come le funzioni di raccolta, le coroutine e le lambda.

Ecco una trappola molto comune che trovano i nuovi sviluppatori Kotlin. Supponiamo che il seguente codice Kotlin:

val nullableFoo: Foo? = ...

// This lambda executes only if nullableFoo is not null
// and `foo` is of the non-nullable Foo type
nullableFoo?.let { foo ->
   foo.baz()
   foo.zap()
}

L'intent in questo esempio è eseguire foo.baz() e foo.zap() se nullableFoo non è null, evitando così un NullPointerException. Anche se questo codice funziona come previsto, è meno intuitivo da leggere rispetto a un semplice controllo null e alla trasmissione intelligente, come mostrato nell'esempio seguente:

val nullableFoo: Foo? = null
if (nullableFoo != null) {
    nullableFoo.baz() // Using !! or ?. isn't required; the Kotlin compiler infers non-nullability
    nullableFoo.zap() // from guard condition; smart casts nullableFoo to Foo inside this block
}

Test

Le classi e le relative funzioni sono chiuse per l'estensione per impostazione predefinita in Kotlin. Devi aprire esplicitamente le classi e le funzioni che vuoi sottoclasse. Questo comportamento è una decisione di progettazione del linguaggio scelta per promuovere la composizione piuttosto che l'ereditarietà. Kotlin dispone di supporto integrato per l'implementazione del comportamento tramite la delega al fine di semplificare la composizione.

Questo comportamento rappresenta un problema per i framework di simulazione, come Mockito, che si basano sull'implementazione o sull'ereditarietà dell'interfaccia per eseguire l'override dei comportamenti durante i test. Per i test delle unità, puoi attivare l'utilizzo della funzionalità Mock Maker Inline di Mockito, che consente di simulare classi e metodi finali. In alternativa, puoi utilizzare il plug-in di compilazione All-Open per aprire qualsiasi classe Kotlin e i relativi membri che vuoi testare come parte del processo di compilazione. Il vantaggio principale dell'uso di questo plug-in è che funziona con test su unità e test strumentati.

Altre informazioni

Per ulteriori informazioni sull'uso di Kotlin, visita i seguenti link: