Adotta Kotlin per grandi team

Passare a un nuovo linguaggio può essere impegnativo. Per preparare il tuo team al successo, inizia lentamente, esegui la migrazione in piccoli blocchi e fai test di frequente. Kotlin viene compilato in bytecode JVM ed è completamente interoperabile con Java, il che semplifica la migrazione.

Creare il team

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

Crea gruppi di studio

I gruppi di studio sono un modo efficace per facilitare l'apprendimento e la memorizzazione. Gli studi suggeriscono che recitare ciò che hai imparato in un contesto di gruppo aiuta a rafforzare il materiale. Procurati un libro su Kotlin o altro materiale di studio per ogni membro del gruppo e chiedi al gruppo di leggere un paio di capitoli ogni settimana. Durante ogni incontro, il gruppo deve confrontare ciò che ha imparato e discutere eventuali domande o osservazioni.

Crea una cultura dell'insegnamento

Anche se non tutti si considerano insegnanti, tutti possono insegnare. Da un responsabile tecnico o responsabile del team a un singolo collaboratore, tutti possono incoraggiare un ambiente di apprendimento che può contribuire a garantire il successo. Un modo per facilitare questa operazione è 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 ai volontari di presentare un nuovo capitolo ogni settimana finché il tuo team non si sentirà a suo agio con il linguaggio.

Nomina un campione

Infine, nomina un campione che guidi l'impegno di apprendimento. Questa persona può fungere da esperto in materia (SME) quando inizi la procedura di adozione. È importante includere questa persona in tutte le riunioni di pratica relative a Kotlin. Idealmente, questa persona è già appassionata di Kotlin e ha una certa conoscenza pratica.

Esegui l'integrazione lentamente

È fondamentale iniziare lentamente e pensare in modo strategico a quali parti del tuo ecosistema spostare per prime. Spesso è meglio isolare questa operazione in una singola app all'interno della tua organizzazione anziché in un'app di punta. Per quanto riguarda la migrazione dell'app scelta, ogni situazione è diversa, ma ecco alcuni punti di partenza comuni.

Modello dei dati

Il modello dei dati probabilmente è 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 puoi eseguire la migrazione di questi metodi e testarli in isolamento.

Ad esempio, supponiamo di avere 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 nell'esempio seguente:

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

Puoi quindi testare questa unità di codice rispetto alla tua suite di test attuale. Inizia con un modello alla volta ed esegui la migrazione delle classi che sono principalmente di stato anziché di comportamento. Esegui test di frequente durante la procedura.

Esegui la migrazione dei test

Un altro punto di partenza sono i test: converti i test esistenti e scrivi nuovi test in Kotlin. In questo modo, il tuo team avrà il tempo di acquisire familiarità con il linguaggio prima di scrivere il codice fornito con l'app.

Sposta i metodi di utilità nelle funzioni di estensione

Puoi rappresentare le classi di utilità statiche, come StringUtils, IntegerUtils, DateUtils, o YourCustomTypeUtils, come funzioni di estensione Kotlin e utilizzarle dalla codebase Java esistente.

Ad esempio, supponiamo 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 essere utilizzati altrove nell'app, come mostrato nell'esempio seguente:

...

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

...

Utilizzando le funzioni di estensione Kotlin, puoi fornire la stessa interfaccia Utils ai chiamanti Java, offrendo al contempo un'API più concisa per la tua codebase Kotlin in crescita.

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
   }

}

Poi, rimuovi la definizione della classe o dell'oggetto, anteponi a ogni nome di 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 all'inizio del file di origine per rendere il nome compilato compatibile con il resto dell'app, come mostrato nell'esempio seguente:

@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 queste funzioni possono ora essere chiamate utilizzando Java o Kotlin con convenzioni che corrispondono a ogni linguaggio.

Kotlin

// ...

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

// ...

Java

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

Completa la migrazione

Una volta che il tuo team si sentirà a suo agio con Kotlin e avrai eseguito la migrazione di aree più piccole, potrai passare a componenti più grandi come fragment, attività, oggetti ViewModel e altre classi correlate alla logica di business.

Considerazioni

Proprio come Java ha uno stile specifico, Kotlin ha il suo stile idiomatico che contribuisce alla sua concisione. All'inizio, però, potresti scoprire che il codice Kotlin scritto dal tuo team assomiglia di più al codice Java che sta sostituendo. Questo cambia man mano che il tuo team acquisisce esperienza con Kotlin. Il cambiamento graduale è la chiave del successo.

Ecco alcune cose che puoi fare per mantenere la coerenza della codebase Kotlin man mano che cresce:

Standard di codifica comuni

Assicurati di definire un set di standard di convenzioni di codifica all'inizio della procedura di adozione. Puoi discostarti dalla guida di stile Kotlin di Android quando ha senso.

Strumenti di analisi statica

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

Integrazione continua

Assicurati di rispettare gli standard di codifica comuni e di fornire una copertura di test sufficiente per il codice Kotlin. Se questa parte fa parte di una procedura di build automatizzata, puoi contribuire a garantire la coerenza e il rispetto di questi standard.

Interoperabilità

Kotlin interagisce con Java senza problemi nella maggior parte dei casi, ma tieni presente i seguenti punti.

Supporto di valori Null

Kotlin si basa sulle annotazioni di supporto di valori null nel codice compilato per dedurre il supporto di valori null sul lato Kotlin. Se non vengono fornite annotazioni, Kotlin utilizza per impostazione predefinita un tipo di piattaforma che può essere trattato come nullable o non nullable. Se non gestisci con attenzione i tipi di piattaforma, possono verificarsi problemi di NullPointerException in fase di runtime.

Adotta nuove funzionalità

Kotlin fornisce molte nuove librerie e zucchero sintattico per ridurre il boilerplate, il che contribuisce ad aumentare la velocità di sviluppo. Detto questo, fai attenzione e metodico quando utilizzi le funzioni della libreria standard di Kotlin, come le funzioni di raccolta, le coroutine, e le espressioni lambda.

Ecco una trappola comune in cui si imbattono gli sviluppatori che non conoscono Kotlin. Considera il seguente codice Kotlin:

val nullableFoo: Foo? = 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()
}

In questo esempio, l'intento è eseguire foo.baz e foo.zap se nullableFoo non è null, evitando così una NullPointerException. Sebbene questo codice funzioni come previsto, è meno intuitivo da leggere rispetto a un controllo di valori null e a un cast 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

In Kotlin, le classi e le relative funzioni sono chiuse per l'estensione per impostazione predefinita. Devi aprire esplicitamente le classi e le funzioni di cui vuoi creare sottoclassi. Kotlin fa questa scelta per promuovere la composizione rispetto all'ereditarietà. Per semplificare la composizione, Kotlin supporta l'implementazione del comportamento tramite delega.

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

Ulteriori informazioni

Per saperne di più sull'utilizzo di Kotlin, consulta i seguenti link: