Gestión de Errores en Android — Parte 1: Entrando en calor

Julián Ezequiel Picó
Droid LATAM
Published in
7 min readApr 5, 2021

English version

Buenas! Un placer encontrarnos de nuevo 😄

En esta nueva serie de artículos, vamos a hablar sobre un tema muy interesante relacionado al desarrollo de software: la gestión de errores. Gran parte de la información y temas sobre los que vamos a tratar en esta serie pueden aplicarse a todos los lenguajes/plataformas disponibles, pero vamos a poner el foco en Android. Si no sos un desarrollador/ra Android, no te vayas! Esto puede ser útil también 😉

En esta serie vamos a tener 3 artículos:

  • Parte 1: entrando en calor
  • Parte 2: claves de la gestión de errores
  • Parte 3: diseñando el sistema de gestión de errores

Comencemos!

Por qué hablar de esto?

Tal vez la razón más importante sea que no hablamos de esto. Existen un montón de artículos y documentación relacionados a arquitectura, librerías, frameworks y herramientas, pero solo pocos que traten el cómo manejar errores.

Pero hay otra razón aparte de esto. Hace un tiempo, tuve la oportunidad de leer un artículo muy interesante llamado The Value of Inconvenient Design, no muy relacionado a lo que vamos a hablar aquí, pero que arroja luz principalmente sobre 2 puntos:

  • Estamos obsesionados en hacer que todo sea más fácil, en quitar cualquier tipo de fricción que encontramos por ahí. Lamentablemente, siempre terminamos teniendo más dificultades, más fricción.
  • Las decisiones de diseño que tomamos son importantes: es necesario hacer que todo sea más fácil y automático? Qué estamos dejando fuera, obviando, cuando vamos en búsqueda de la eliminación total de la fricción?

Ok, entiendo, pero en qué se relaciona esto con la gestión de errores? Cuando leí este texto, empecé a recordar mis primeros pasos como Android dev y, por supuesto, mis primeros miedos: los errores y cómo tratarlos. Mis primeros instintos eran “Try-Catch a todo”, “Agregar null checks por todo el código” y “Esconder el error abajo de la alfombra”: generalizar y automatizar todo el manejo de errores para que sea más fácil evitar fallas. Con el paso del tiempo, nos damos cuenta de que esto no es del todo correcto… o no. Por eso estamos acá!

Gestión y Fricción

Cuando diseñamos y desarrollamos nuestro sistema o app, le hacemos frente a lo inevitable: los errores. Mientras empezamos nuestro camino como desarrolladores pensamos que, para evitar y manejar los errores, necesitamos try-catchearlos todos para que la app no crashee, en otras palabras, necesitamos quitar toda la fricción. Entonces, hacemos:

Bueno, esto no es muy bueno. Al principio parece que estamos eligiendo un buen enfoque, pero no es así. Automatizamos el manejo de los errores, si, pero perdemos mucha información relevante:

  • Cuál es el error real? 🤔
  • Es un error que estoy manejando en mi código, una excepción propia, o es, por ejemplo, un NullPointerException? 🤔🤔
  • Cómo manejo este error? Hay algún otro lugar, otro componente, donde él mismo fue ya consumido y manejado? Necesito ocultarlo? 🤔🤔🤔
  • Qué hago con esto ?!?!?!?! 😭😭

Además, aparece una paradoja: al ocultar y/o manejar incorrectamente el error, no solo no estamos quitando el error de nuestro código: seguramente el mismo hará que aparezcan nuevos errores en otros lugares. Si el error ocurre, debemos aceptarlo y tomar las medidas necesarias.

En el otro extremo, podemos elegir convivir con mucha fricción: dejemos que todo explote, no manejemos nada. Soy el/la mejor desarrollador/ra que existió y todo lo que hago va a funcionar perfecto. Bueno… Sin comentarios sobre esto, verdad? Somos humanos y cometemos errores. Por favor, no hagan esto.

Entonces, cuál es la cantidad correcta de fricción que debemos considerar cuando desarrollamos y diseñamos nuestro sistema?

Correcto, la Gestión de Errores. Vamos a definirla y a conversar sobre qué debemos considerar para poder decir “Estamos haciendo las cosas bien”. Pero, primero, entremos en calor y seteemos nuestra línea base para los próximos artículos.

Entrando en calor: seteando la línea base

Entonces, si queremos hablar sobre gestión de errores, necesitamos tener un punto de partida. Para esto, vamos a mencionar algunos conceptos y definir términos que nos van a ser de utilidad cuando discutamos los próximos artículos. Hablaremos de:

  • Errores.
  • Clasificación de Errores.
  • Fallas.
  • Responsabilidad en el Manejo del Error.
  • Fail Fast (fallar rápido).
  • Excepciones.

Arrancamos 🚀

Errores

Podemos decir que un error es un problema dentro de mi sistema que produce resultados no deseados. Cuando mencione errores voy a referirme a errores en tiempo de ejecución, dejando fuera los errores en tiempo de compilación (debemos considerarlos, pero tenemos un montón de herramientas para manejarlos, por lo que podemos dejarlos fuera de la definición).

Además, vamos a considerar que un error puede ser cualquier cosa que el usuario pueda ver y le represente un comportamiento incorrecto. Por qué? Porque si no lo hacemos, estamos ignorando, por ejemplo:

  • Interfaces de usuario congeladas esperando un resultado.
  • Interfaces de usuario congeladas esperando que se renderice un layout.
  • Pérdida de información mientras se utiliza la aplicación.
  • Tiempos de espera extensos mientras se interactúa con la app.
  • Etc, etc, etc.

Clasificación de Errores

Voy a armar 3 grandes (grandes) categorías para realizar la clasificación. Esto es solo para poder agruparlos, no lo interpreten como una clasificación fuerte.

  • Errores de negocio: no son un problema de mi código o un inconveniente técnico, sino algo que, debido a definiciones de negocio, representa un error o algo que no debe suceder. Por ejemplo, una cuenta bancaria que no tiene dinero no puede pagar un servicio.
  • Errores excepcionales: representan una excepción en mi sistema, algo inesperado que puede alterar el flujo normal de ejecución de mi app. En general, están asociados a errores técnicos, pero puede que estén relacionados a fallas en la comunicación con un sistema externo, a un dispositivo en particular, etc.
  • Otros errores: acá tenemos todos los errores que no entran en ninguna de las categorías anteriores. Algunos podrían ser errores de UX, excepciones que no crashean mi app pero disparan comportamientos erróneos (por ejemplo, exceder la capacidad de almacenamiento de un Bundle), o tiempos de espera eternos.

Fallas

Por qué? No es un error esto? Bueno, no. Vamos a utilizar falla para describir la representación de un error que ya fue manejado. En otras palabras, si un componente (A) detecta un error en otro componente (B), A va a propagar ese error en forma de falla. Es muy importante mapear un error a una falla lo más rápido posible, para evitar trabajar con estados erróneos o inconsitentes.

Podemos establecer una comparación entre ambas definiciones y decir que:

  • El error es malo porque hace que mi sistema actúe de una forma incorrecta.
  • La falla es buena porque muestra un error en lugar de ocultarlo, dando la posibilidad de manejar ese error a alguien.

Responsabilidad en el Manejo del Error

Los errores tienen que ser tratados cuando tenemos suficiente contexto y podemos determinar qué acciones realizar para recuperarnos del mismo.

Manejar errores es una de las muchas responsabilidades de un componente. Si no queremos que un component haga más cosas de las que debe, tampoco queremos que maneje errores si no sabe cómo.

Algunos ejemplos pueden ser:

  • Si tenemos un error con los atributos de un objeto, quién debe manejarlo? Un componente responsable de configurar el objeto, o cualquier otro lugar dentro de mi sistema?
  • Si tenemos un error relacionado a networking, quién debe manejarlo? Un Repositorio, un Gateway o un Activity?

Fail Fast (fallar rápido)

Cuando detectamos un error, tenemos que fallar lo más rápido posible. Por qué? Porque obligatoriamente debemos evitar la propagación de ese error dentro de mi sistema.

Excepciones

Por último, nuestras tan queridas Excepciones (o Exceptions). Una excepción es un evento que altera el flujo normal de ejecución de algún requerimiento dentro de mi app. Si queremos verlo desde un nivel más alto de abstracción, podemos decir que modifica el flujo de comunicación entre varios componentes.

Un punto clave relacionado a las excepciones es su habilidad de representar un error. Suena como algo pequeño, pero esto evita manejar errores con algún objeto en específico, tipos primitivos (como ints o booleanos), etc. No quitan la responsabilidad y el esfuerzo de detectar, reportar y manejar errores, pero son una abstracción muy útil para tratarlos de forma independiente.

Y eso es todo para este artículo! Ya podemos decir que estamos familiarizados con un montón de conceptos útiles que nos van a ayudar con el próximo artículo.

Espero que hayan encontrado útil este artículo, y espero poder verlos en el próximo! 🙂

Cualquier comentario es bienvenido, nos leemos! 👋

--

--