Ya había tardado bastante en dedicarle un artículo a una de las partes más importantes de Kotlin: el tratamiento de nulos.
El propio creador del null reference lo autodenomina como “el error del billón de dólares”, pues es uno de los puntos que más errores genera cuando estás trabajando en este lenguaje.
Si miras a menudo tu gestor de bugs, seguro que el 99% de los errores que ves son NullPointerException
.
Gracias a Kotlin, vas a trabajar en un entorno mucho más seguro (incluso con librerías Java), que reducirá al mínimo estos problemas.
¿Te gustaría comenzar hoy a dar el siguiente paso? Te recomiendo que entres en mi training gratuito aquí
Nulos en Kotlin
Los nulos en Kotlin no existen mientras no se diga lo contrario.
Es decir, a ningún objeto, por defecto, se le puede asignar null
. Recuerda que en Kotlin todos los tipos son objetos.
Por tanto, esto no compilará:
val x: Int = null
Si quieres que una variable acepte nulos, tienes que marcar el tipo con una ?
:
val x: Int? = null
Chequeo en tiempo de compilación
Pero, a partir de ese momento, el compilador te obligará a comprobar el nulo antes de hacer nada con la variable. De esta forma, se asegura de que no se produce un NullPointerException
.
Por ejemplo:
val y = x.toDouble()
No compilará, si no compruebas primero si es nulo:
if (x != null) { val y = x.toDouble() }
Expresión de acceso seguro
Existe una expresión mucho más sencilla para representar el ejemplo de antes, que es utilizar una ?
delante del .
cuando se llama a un método.
Si la variable no es nula, ejecutará la operación. En caso contrario, no hará nada:
val y = x?.toDouble()
En este caso, lo que hará si x
es null, es devolver null también. Por lo que y
será de tipo Double?
.
El operador Elvis
¿Pero qué pasa si no queremos tener una variable nullable como resultado de la operación? El operador Elvis (?:
) nos permite devolver un valor en ese caso:
val y = x?.toDouble() ?: 0.0
Este código sería equivalente a:
val y = if (x != null) { x.toDouble() } else { 0.0 }
Spoiler: como ves, la mayoría de las sentencias en Kotlin son a su vez expresiones. Por ejemplo, puedes asignar el resultado de un
if
a una variable.
Evitando el chequeo de null
Existe un operador (!!
) que evitará la necesidad de chequear null
si estás completamente seguro de que una variable nunca será nula.
En mi opinión, hay muy pocos casos en los que este operador tiene sentido. Casi siempre hay una solución mejor.
Pero podrías hacer lo siguiente:
val x: Int? = null val y = x!!.toDouble()
Esto compilaría y produciría una NullPointerException
Lo dicho: mucho cuidado con este operador.
Compatibilidad con Java
Cuando estamos trabajando con librerías Java, podemos encontrarnos ante diferentes situaciones con respecto al chequeo de nulos.
La librería está correctamente anotada
Si se están utilizando adecuadamente las anotaciones @Nullable
y @NotNull
, tanto las de Java como las propias de Android, Kotlin será capaz de trabajar sin problemas con ellas para deducir cuándo una variables es nula y cuándo no.
Muchas partes del framework de Android ya están anotadas correctamente, así que esto es una ventaja enorme para trabajar con Kotlin.
La librería no tiene anotaciones
Sin embargo, si la librería no está anotada, los tipos serán marcados con un operador especial (una única !
), lo que significa que queda en nuestra mano decidir si un parámetro o valor de retorno acepta nulo o no.
Si tenemos acceso al código fuente, lo mejor es comprobar qué valores acepta el código en cuestión que estemos utilizando.
Un ejemplo en Android que no está anotado es la librería de soporte de RecyclerView
. Cuando creas un adapter y autogeneras los métodos, por defecto les añadirá una interrogación a los tipos.
Pero si miras el código fuente, te darás cuenta de que nada puede ser null en los métodos que necesitas sobrescribir. Así que puedes deshacerte de todas las interrogaciones, y evitar chequeos de nulos innecesarios.
Conclusión
Los NullPointerException
son un horror para cualquier desarrollador en Java, y seguramente representen la mayor parte de los errores que se producen en tu código.
Reducir su número en Kotlin hasta casi cero es muy sencillo, incluso cuando se trabajar con librerías y frameworks en Java.
Sólo esto te evitará horas y horas de depuraciones innecesarias, además de que hará el código mucho más estable.
Si te gusta lo que has visto, te animo a que te apuntes al training gratuito, donde te contaré todo lo que necesitas para aprender a crear tus Apps Android en Kotlin desde cero.
Si me apuras quizá sea lo mejor del lenguaje, junto a las lambdas y tratamiento de colecciones. Lo malo es que depende de que las bibliotecas estén anotadas, si el API de Android más o menos lo está genial entonces.
Una pregunta al hilo del gestor de bugs, si tienes una app Kotlin en producción en la Play Store, se cierra por lo que sea y el usuario envía el reporte de errores… ¿el log que aparece en la consola de desarrollador es “usable”?
Léase, si te dice dónde se produjo el error en tu código o te va a referenciar clases con nombres raros que no sabes de dónde salen (como cuando le pasas proguard y luego necesitas un mapping). Es más, ¿se puede descompilar una app en Kotlin?
Pues sinceramente no lo sé, no tengo ninguna App de Kotlin en la Store, pero lo voy a probar porque la verdad que es una pregunta muy interesante. Cuando tenga la respuesta vuelvo por aquí para contestarte 🙂 .
En cuanto a la decompilación (y estoy casi seguro de que esto va a responder a la pregunta anterior también), en realidad lo que hace es convertir instrucciones de la máquina virtual a código Java. Las instrucciones que utilizan tanto Kotlin como Java son las mismas (la máquina virtual es lo que es), así que doy por hecho que lo que verás al decompilar será una “versión Java” de las instrucciones de la máquina virtual que Kotlin haya usado. Pero en cuanto tenga un rato lo pruebo también.
Ok, ya tengo respuestas. El log que aparece en la consola es el mismo que aparece que ves un error en el logcat, pues es simplemente el stacktrace de la excepción. Ese stacktrace es válido, y apunta a la línea correcta. También es compatible con proguard.
Con respecto a la decompilación, es exactamente lo que intuía. Lo que hace es convertir las instrucciones del a JVM a código Java, por lo que lo transforma en la interpretación que hace de esas instrucciones. Funcionará en Java, pero el código a veces será un poco enrevesado. No puedes obtener el código Kotlin exacto de vuelta en cualquier caso, hasta que alguien haga un decompilador de Kotlin (si es que eso llega a ocurrir).
¡Muchas gracias! Pues siendo compatible con proguard no hay más preguntas, Señoría. Entiendo que teniendo el mapping puedes saber dónde falló exactamente incluso estando ofuscado, ¡interesante!
Entre esto, que es funcional y que la biblioteca ocupa nada y menos igual me animo a lanzar el siguiente juego con kotlin + libgdx, es lo más parecido a swift + spritekit que hay en el panorama Android 😀
Elvis se me hace tan parecido al viejo confiable operador condicional, lo uso todos los días en Java para asignar un valor a partir de una condición, incluso se puede encadenar, algo que no me queda claro con elvis
Otra cosa, cuando leo una o dos veces “librería” en vez de “biblioteca” puedo soportarlo, pero esta vez casi apago mi internet y borro todos los datos de navegación al ver tantas veces el mismo término incorrecto, en eso sí F.
Ambas acepciones están aceptadas, y esto es un contenido gratuito. Eres libre de no leerlo.
https://es.wikipedia.org/wiki/Biblioteca_(inform%C3%A1tica)
¿Qué tal el curso? ¡Espero que fuera genial! Conseguiste que me pusiera a cacharrear. Al hilo de los NullPointerException me ha hecho gracia que se tomen tanta molestia en erradicarlo y al mismo tiempo ignoren completamente las checked exceptions. Es lo único que no me gusta nada.
Así que igual no vemos más NPE en la consola de desarrollador pero sí otras excepciones si se nos olvida poner el try/catch correspondiente en algún caso no contemplado. Curioso porque en Swift no había excepciones pero ahora ya sí las tiene y son checked.
Fue muy bien, gracias! La gente salió contenta (o eso me dijeron al menos ?)
La mayoría de los lenguajes modernos no tienen checked exceptions. A mí me parece que ambas opciones tienen sus pros y sus contras, pero si quieres ver por qué en Kotlin decidieron que fuera así, tienes bastante información en su web: https://kotlinlang.org/docs/reference/exceptions.html
Lo estuve leyendo, lo que me concome es que ni siquiera sabes qué excepciones podrían lanzarse en cierto punto, cosa que sabe perfectamente el compilador y el IDE y no te lo dice. ¡Tampoco te autocompleta el catch!
Hace poco en el código de la biblioteca de billing de Android cambiaron el tipo de excepción de un método. Con Kotlin no me habría enterado de ese cambio, incluso podría estar capturando un tipo de excepción que no se correspondía con el que lanzaba. Me habría enterado cuando se le cerrara a un usuario la app…
Hola.
Excelentes tutoriales :).
Comentar que la cita del “error del billon de dolares” es de Tony Hoare cuando introdujo el null en Algol.
https://es.wikipedia.org/wiki/C._A._R._Hoare
Muy cierto! Modifico el texto, gracias!