¡Atención! Este sitio utiliza cookies.
Si no cambias la configuración de tu navegador, aceptas su uso.

Toma de decisiones en Swift

No vamos a negarlo: nuestros proyectos están plagados de condiciones que muchas veces están lejos de la propia coherencia del programa y tienen más que ver con ‘caprichos’ o decisiones de negocio. Son los típicos casos de: “haz esto si el usuario ha iniciado sesión y ha visto esta pantalla 4 veces, pero además comprueba que han pasado 7 días desde el evento X”. Este tipo de requerimientos tiene varios problemas:

  • Requieren de varios datos de distintas fuentes, las cuales no están importadas (y no te interesa que lo estén) y generan un montón de código ruidoso y repetitivo cuando te toca volver a programar lo mismo en otro sitio.
  • Cada condición que añades aumenta la complejidad de tu programa, y lo que es peor, lo hace menos predecible. ¿Qué relación tienen todos esas condiciones? ¿Cuáles son imprescindibles para validar la porción de código que encierran? ¿Cuáles son simplemente definiciones de casos de uso que no tienen nada que ver con comprobar el estado de la aplicación?
  • Todos acaban reduciéndose a un ‘true’ o a un ‘false’, pero cuando toca depurar, empiezan los quebraderos de cabeza como: “si el usuario no está autenticado devuelvo verdadero” o “si la butaca está bloqueada devuelvo falso”. Son como esas típicas frases de los políticos cuando dicen cosas como: “no negaré que nunca haya dicho una mentira”.

Personalmente, me gustan los programas que no tienen bucles ni condiciones. Para muchos de los propósitos en los que usamos sentencias condicionales a la ligera tendríamos a nuestra disposición el polimorfismo (el bien usado, no de ese del que solemos abusar para ahorrarnos trabajo). En Swift, tenemos una cosa mucho mejor: los enums.

 

Adoro los enums. Es la característica singular del lenguaje que más me gusta. Te permite encapsular todos los casos o formas de un objeto en un solo sitio, como si declarásemos una clase polimórfica con un switch vitaminado dentro. Por eso, creo que tengo el síndrome del enum, porque desde que los conozco soluciono muchos problemas con ellos.

Mi problema y la solución

Lo que planteo es simplemente quitarnos este tipo de código:

y reemplazarlo por este:

No es el código más bonito del mundo, pero hay varias cosas que me gustan mucho más de la segunda versión:

  • No he definido las condiciones, sino solamente el dato.
  • El caso de uso está perfectamente atado a las condiciones: no hay forma de que se me escape u olvide uno.
  • Puedo generar este DecisionMaker cuando quiera y ejecutarlo sabiendo que los datos introducidos no habrán cambiado, lo que me permite guardarme la validación a una porción de código en la que no meta tanto ruido.
  • Todo se reduce a un ‘true’ o a un ‘false’.

Y por último, el resultado es predecible. Puedo hacer un test de esa función fácilmente:

Crear casos en los que valide ‘falso’ sería igual de sencillo, simplemente cambiando los parámetros. No necesito Foundation ni ningún otro framework o dependencia. Solamente pasando los tests sabría qué ocurre.

 

Implementación interna

Otra cosa fantástica de Swift es la facilidad que nos brinda para crear módulos independientes en las aplicaciones. Eso nos permite, entre otras cosas, desacoplar nuestros modelos, dependencias y demás detalles de implementación al código más atómico e independiente posible. Perfecto para ser reutilizado. Para esta implementación, este es precisamente el camino que he seguido.

Lo que planteo es por supuesto, eso mismo, un planteamiento. Ni mucho menos vengo a evangelizar. Esto no deja de ser un borrador hecho en una hora.

Voy a explicar brevemente en qué consiste:

Un DecisionMaker toma en su instanciación un escenario, caso de uso o ‘use case’, según se quiera entender. Este escenario, como veremos ahora, es un enum con varios casos, o varios casos de uso que requieren de una serie de condiciones cada uno.

Lo que hace validate() se reduce a:

  • Ubicar la función que validará que las condiciones del escenario elegido sean las correctas.
  • Pasar esas condiciones, ya establecidas dentro del escenario, a dicha función.

¿Y  qué hace getDecision()? Se reduce a recorrer todas esas condiciones (llamadas values) y evaluar con ayuda de la función específica que valida ese escenario (llamada aquí validator). Lo hago con una función reduce porque en el fondo lo que quiero es convertir una lista de cosas a un único resultado. Si alguna de estas validaciones fallare, devolvería ‘false’, y nuestro DecisionMaker habría terminado su trabajo.

 

Bien, y ¿dónde están esas condiciones y escenarios de las que estamos hablando?

A priori parece un poco repetitivo, pero debemos tener en cuenta que las condiciones son reusables entre distintos escenarios (muchas veces dos escenarios tienen el mismo objetivo con distintas condiciones) y que estamos definiendo los parámetros de esas condiciones igual que su nombre real (de ahí la repetición). La variable conditions devuelve la lista de condiciones que después recorreremos con el reduce de más arriba.

A continuación, nos queda apuntar los escenarios a sus funciones ‘validadoras’. Este un concepto que he robado de Redux, donde mediante claves, asignamos funciones que después serán ejecutadas por otro proceso que no tendrá acceso a ninguna otra función.

La única idea a reseñar aquí es que la función recibe el mismo nombre que el escenario, pero con el prefijo should delante. Todos los distintos casos de Scenario se iniciarían con verbos.

 

Por último, esta sería una última extensión sobre DecisiónMaker con la función que valida nuestros casos de uso. En mi opinión, esta parte sí que la pondría en un archivo distinto:

En esta extensión deberemos —ahora sí— validar todos estos datos.

¿Pero todo este tinglado vale la pena?

Eso dependerá del tipo de proyecto y de sus necesidades de negocio. Muchas veces queremos jugar con variables tomadas desde negocio y ensuciamos la implementación interna. A mí me gusta esta idea porque nos permite separar la definición de los requerimientos de las fuentes de datos, de la responsabilidad de donde se validan, y nos ayuda a ver de un golpe de vista qué casos de usos tenemos y qué reglas aplicamos. Además, borrar un caso de uso es muy fácil. Basta con eliminarlo del enum. El compilador de Swift te ayudará a quitar el resto.

Tampoco estamos usando librerías ni frameworks de terceros. Es más, como he mencionado, podemos colocar este código en un módulo separado. Es Swift puro.

Añadido a esto, diría que es muy fácil compartir este último fragmento con el resto del equipo e incluso con un gestor del proyecto, donde puede entender de forma muy declarativa y predecible qué está ocurriendo dentro del código.

 

Terminando…

Esta solución, como puedes comprobar, es una forma de definir condiciones caprichosas dentro de ciertos parámetros de nuestras apps. No escala nada bien a nivel de modelo u otros sitios donde sí tiene sentido hacer estas validaciones in situ.

Esta es una idea peregrina que me ha asaltado hoy por la cabeza, y como hacía mucho tiempo que no escribía me he animado a compartirla. Si estás usando una solución mejor o quieres discutir esta, ya estás tardando en escribir un comentario aquí abajo.

¡Muchas gracias!

Escrito por Miguel Hernández Jaso

Autor del blog. Desarrollador especializado en iOS.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Mi Gobierno me obliga a decirle que utilizo 'cookies' en mi página. Si continúa navegando, quiere decir que está conforme con ello.