Adopta Kotlin en equipos grandes

Cambiarse a un lenguaje nuevo puede ser una tarea desalentadora. La clave está en comenzar despacio, avanzar de a poco y hacer pruebas con frecuencia para alinear a tu equipo y alcanzar el éxito. Kotlin hace que la migración sea sencilla, ya que se compila en el código de bytes de JVM y es completamente interoperable con Java.

Cómo crear el equipo

El primer paso antes de la migración es analizar el modelo de referencia común de tu equipo. Estas son algunas sugerencias que pueden resultarte útiles para acelerar el aprendizaje de tu equipo.

Crea grupos de estudio

Los grupos de estudio son un método eficaz para facilitar el aprendizaje y la retención. Los estudios sugieren que recitar lo que aprendiste en un entorno grupal ayuda a reforzar el material. Consigue un libro de Kotlin u otro material de estudio para cada miembro del grupo y pide al grupo que revise un par de capítulos cada semana. Durante cada reunión, el grupo debe comparar lo que aprendió y debatir cualquier observación o pregunta.

Desarrolla una cultura de enseñanza

Todos pueden enseñar aunque no se consideren profesores. Todos pueden fomentar un entorno de aprendizaje que ayude a garantizar el éxito, desde un líder de tecnología o de equipo hasta un colaborador individual. Una manera de facilitar esto es realizar presentaciones periódicas en las que una persona del equipo esté designada para hablar sobre algún tema que haya aprendido o desee compartir. Puedes aprovechar tu grupo de estudio si pides voluntarios para presentar un nuevo capítulo cada semana hasta llegar a un punto en el que el equipo se sienta cómodo con el lenguaje.

Designa a un coordinador

Por último, designa a un coordinador para que lidere el aprendizaje. Esa persona puede actuar como experto en la materia (SME) mientras comienzas el proceso de adopción. Es importante incluir a esa persona en todas tus reuniones de práctica relacionadas con Kotlin. Idealmente, esa persona ya debería dominar Kotlin y tener conocimientos prácticos.

Haz la integración sin apuros

La clave está en comenzar sin apuro y pensar de forma estratégica qué partes de tu ecosistema mover primero. A menudo, es mejor aislar esto en una sola app dentro de tu organización en lugar de una app insignia. En términos de migrar la app elegida, cada situación es diferente, pero estos son algunos lugares comunes para comenzar.

Modelo de datos

Es probable que el modelo de datos incluya mucha información de estado junto con algunos métodos. El modelo de datos también puede tener métodos comunes como toString(), equals() y hashcode(). Por lo general, esos métodos se pueden transferir y se pueden probar en unidades de forma aislada.

Por ejemplo, echa un vistazo al siguiente fragmento de 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 + '\'' +
               '}';
   }
}

Puedes reemplazar la clase de Java con una sola línea de Kotlin, como se muestra aquí:

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

Luego, se puede probar ese código en unidades con tu conjunto de pruebas actual. La idea aquí es comenzar de a poco con un modelo a la vez y clases de transición que sean principalmente de estado y no de comportamiento. Asegúrate de hacer pruebas con frecuencia.

Migra pruebas

Otra forma de comenzar es convertir las pruebas existentes y escribir pruebas nuevas en Kotlin. Esto puede darle tiempo a tu equipo para que se sienta cómodo con el lenguaje antes de escribir el código que tienes pensado enviar con tu app.

Mueve métodos de utilidad a funciones de extensión

Cualquier clase de utilidad estática (StringUtils, IntegerUtils, DateUtils, YourCustomTypeUtils, etc.) puede representarse como funciones de extensión de Kotlin y usarse en tu base de código Java existente.

Por ejemplo, supongamos que tienes una clase StringUtils con algunos métodos:

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
   }

}

Estos métodos se pueden usar en otra parte de tu app, como se muestra en el siguiente ejemplo:

...

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

...

Con las funciones de extensión de Kotlin, puedes proporcionar la misma interfaz Utils a los llamadores de Java y, al mismo tiempo, ofrecer una API más breve para tu base de código Kotlin en desarrollo.

Para ello, puedes comenzar por convertir esa clase Utils en Kotlin mediante la conversión automática que proporciona el IDE. El resultado de ejemplo podría ser similar al siguiente:

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
   }

}

A continuación, quita la definición de clase u objeto; coloca el tipo en el que debe aplicarse la función como prefijo del nombre de cada función; y utilízalo para hacer referencia al tipo que se encuentra dentro de la función, como se muestra en el siguiente ejemplo:

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
}

Por último, agrega una anotación JvmName en la parte superior del archivo fuente para que el nombre compilado sea compatible con el resto de tu app, como se muestra a continuación:

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

La versión final debería ser similar a la siguiente:

@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
}

Ten en cuenta que ahora es posible llamar a esas funciones utilizando Java o Kotlin con convenciones que sean compatibles con cada lenguaje.

Kotlin

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

Java

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

Completa la migración

Una vez que tu equipo se sienta cómodo con Kotlin y hayas migrado áreas más pequeñas, podrás abordar componentes más grandes, como fragmentos, actividades, objetos ViewModel y otras clases relacionadas con la lógica empresarial.

Consideraciones

Así como Java tiene un estilo específico, Kotlin tiene su propio estilo idiomático, que hace que sea más breve. Sin embargo, es posible que al principio descubras que el código Kotlin que produce tu equipo se parece más al código Java que está reemplazando. Esto cambia a medida que aumenta la experiencia de tu equipo con Kotlin. Recuerda que el cambio gradual es la clave del éxito.

Estos son algunos consejos para lograr la coherencia mientras crece tu base de código Kotlin:

Estándares de codificación comunes

Asegúrate de definir un conjunto estándar de convenciones de codificación al comienzo del proceso de adopción. Puedes distanciarte de la guía de estilo de Kotlin de Android cuando sea conveniente.

Herramientas de análisis estático

Aplica el conjunto de estándares de codificación establecidos para tu equipo. Para ello, usa Android lint y otras herramientas de análisis estático. klint, un linter de Kotlin de terceros, también proporciona reglas adicionales para Kotlin.

Integración continua

Asegúrate de cumplir con los estándares de codificación comunes y brinda una cobertura de prueba suficiente para tu código Kotlin. Hacer esto como parte de un proceso de compilación automático puede ayudar a garantizar la coherencia y el cumplimiento de estos estándares.

Interoperabilidad

Kotlin interopera con Java sin problemas en su mayor parte, pero ten en cuenta lo siguiente.

Nulabilidad

Kotlin se basa en anotaciones de nulabilidad en el código compilado para inferir la nulabilidad del lado de Kotlin. Si no se proporcionan anotaciones, Kotlin utiliza un tipo de plataforma predeterminada que puede tratarse como el tipo anulable o no anulable. Sin embargo, esto puede generar problemas de tiempo de ejecución de NullPointerException si no se trata con cuidado.

Adopta nuevas funciones

Kotlin ofrece muchas bibliotecas nuevas y sintaxis edulcorada para reducir el código estándar, lo que ayuda a aumentar la velocidad de desarrollo. Dicho esto, sé prudente y metódico cuando uses las funciones de biblioteca estándares de Kotlin, como funciones de colección, corrutinas y lambdas.

Esta es una trampa muy común en la que suelen caer los desarrolladores nuevos de Kotlin. Echa un vistazo al siguiente código 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()
}

En este ejemplo, la intención es ejecutar foo.baz() y foo.zap() si nullableFoo no es nulo, y así evitar un NullPointerException. Si bien este código funciona como se espera, es menos intuitivo de leer que una simple verificación nula y conversión inteligente, como se muestra en el siguiente ejemplo:

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
}

Pruebas

Las clases y sus funciones están cerradas para la extensión de forma predeterminada en Kotlin. Debes abrir explícitamente las clases y funciones que quieras subclasificar. Este comportamiento es una decisión de diseño de lenguaje que se tomó para promover la composición por sobre la herencia. Kotlin cuenta con compatibilidad integrada para implementar comportamientos a través de la delegación a fin de simplificar la composición.

Ese comportamiento genera un problema para los frameworks ficticios, como Mockito, que dependen de la implementación de la interfaz o de la herencia para anular comportamientos durante las pruebas. Para las pruebas de unidades, puedes habilitar el uso de la función Mock Maker Inline de Mockito, que te permite simular las clases y los métodos finales. De manera alternativa, puedes utilizar el complemento de compilador All-Open para abrir cualquier clase de Kotlin y sus miembros que desees probar como parte del proceso de compilación. La principal ventaja de usar este complemento es que funciona tanto con las pruebas de unidad como con las instrumentadas.

Más información

Consulta los siguientes vínculos para obtener más información sobre el uso de Kotlin: