Bolsas llenas de funciones
2021-04-11
En este post menciono functional programming (FP) y object-oriented programing (OOP) en el contexto de JavaScript (JS). Hablo de mis experiencias con los dos paradigmas en un proyecto bastante grande. El título del post no es FP vs OOP y no tengo en interés en ese tipo de discusión.
Las ideas de FP llevan varios años tomando fuerza en el mundo de JS. Mi interés en el tema llegó algunos años antes del lanzamiento de React. En eso días ya habían librerías y developers muy activos evangelizando sobre FP en JS y sus beneficios.
Aunque había algo de interés antes de que React estuviera en el panorama no es hasta el lanzamiento de React y su enorme popularidad que la gran mayoría de devs se interesaron en el tema.
Los vendedores (developer advocates) de React presentaron buenos argumentos sobre las ventajas de FP, compartieron ejemplos convincentes y escribieron librerías super populares justo en la raíz de lo que luego se convirtió en la librería más popular e influyente en el mundo de JS desde jQuery.
En este momento no se conocían codebases grades y open source que sirvieran de ejemplo para los millones de developers que ahora decidieron escribir a lo FP. Los ejemplos disponibles en muy pocos casos pasaban del ya trillado to-do list o el casi cómico counter que consiste en dos botones que al presionarlos aumentan o disminuyen un número. Quedaba como asignación para el o la developer el definir e implementar una arquitectura escalable en un paradigma poco familiar.
Más o menos para esta misma época ya había escrito algunos proyectos usando un estilo, estrictamente funcional o lo más funcional posible dentro de las limitaciones que impone JS. Admito que eran proyectos pequeños con un solo developer, pero eran prometedores.
Organizar todo como pure functions, data inmutable y evitar side effects se sentía limpio. Estos proyectos eran básicamente treinta o cuarenta funciones trabajando en conjunto. Las mayoría de las pruebas eran fáciles de escribir y solo tenía una o dos funciones que creaban side effects así que todo era hermoso.
Como muchos me deje seducir por la aparente simpleza de este paradigma. Mi error fue pensar que esto que funcionaba para proyectos pequeños podría funcionar en proyectos mucho más grandes. Estas experiencias me dieron confianza en FP JS y me hice de la idea de que se podría usar en proyectos más grandes con los mismos resultados.
No funcionó. Cuando traté de llevar lo que había aprendido de FP JS a un proyecto mucho más grande, todo lo que se sentía limpio poco a poco se volvió complicado y difícil de entender. Tenía muy poca experiencia y no conocía suficientes técnicas para manejar esta escala. Teníamos funciones que llaman funciones, que llaman a otras funciones y cada una llama más funciones.
Mientras escribíamos el código no veíamos problemas pero cuando había que leer ese código meses más tarde, daba mucho trabajo entenderlo. Los code reviews tomaban mucho tiempo o simplemente se volvían muy superficiales. Hubo muchas señales de que estábamos haciendo algo mal pero las ignoramos.
Como buenos programadores de FP, mantuvimos la data separada del comportamiento. Nuestro app recibía objetos simples (POJOs), los transformaba y producía nuevos objetos simples. Suena fácil, pero cuando tienes decenas de funciones, manipulando estos objetos, saber en que estado está la data en todo momento puede ser complicado. Muchas veces nos veíamos obligados a usar el debugger para poder inspeccionar el estado de un objeto en un lugar específico. En otros momentos teníamos que correr y ejecutar la aplicación para entender las estructuras de data. El código no era fácil de entender.
En este proyecto siempre tuvimos una idea de “modelos” pero, no eran realmente modelos. Mas bien eran una capa muy delgada por encima de una pequeña abstracción de la base de datos. Nuestros modelos no mantenían estado, simplemente nos daban acceso a la base de datos y servían para guardar algunas funciones que estaban muy ligadas a la entidad específica.
Uno de los síntomas más claros de que teníamos un problema es que en el proyecto teníamos una carpeta que se llamaba utils con cientos de files y cada file con dos o tres funciones. Muchas de estas funciones contenían lógica de negocio y hacían cosas realmente importantes y centrales para la aplicación. Estos archivos terminaron en esta carpeta porque eran funciones que queríamos reutilizar y no teníamos una idea clara de donde ponerlas. Después de todo queríamos tratar de mantener la lógica y la data separada y estas funciones eran lógica.
Más de una vez durante un review, encontramos que alguien había escrito una función que ya existía. En este proyecto es difícil encontrar código existente para reutilizar. Es fácil imaginar como terminamos con funciones duplicadas que hacen las cosas un poco diferente la una de la otra. Demás está decir que esto produce bugs.
En otros casos las funciones estaban muy ligadas a un pedazo de data o a un evento de la aplicación, así que esas funciones se escribían en el mismo archivo donde se usaban, complicando más aún el encontrar funciones para reutilizar y mantener la lógica centralizada.
La combinación de estas cosas y la falta de experiencia sobre como diseñar un proyecto FP de gran escala nos llevó a tener una arquitectura que solo se puede describir como una montaña de bolsas llenas de funciones.
Estas bolsas de funciones se organizan de muchas formas, alguna veces de acuerdo al tipo de acciones que hacen, otras de acuerdo a la data que modifican y otras como utils cuando no sabemos que hacer con ellas. Sin duda, haber tenido un mejor esquema de organización pudo haber ayudado mucho a reducir la complejidad pero estábamos aprendiendo mientras construíamos nuestro programa y no teníamos muchas referencias.
Nuestro problema con este proyecto no es un problema producido por FP, pero si un problema al que es fácil llegar si se tiene poca experiencia con este paradigma y si no existen suficientes recursos para aprender y tomar ideas sobre como diseñar la arquitectura de tu aplicación. Creo que este es el caso de la mayoría de los y las devs que conozco. Les interesan las ideas de FP, se ven fáciles de entender y aplicar pero no tenemos idea de como usarlas efectivamente en un proyecto de gran escala.
Pensar que solo con usar funciones puras, tener data inmutable y evitar los side effects es suficiente para crear software de cualquier escala es una trampa.
Aprendemos a usar map, reduce y filter y nos creemos genios del FP. Vemos un proyecto con dos o tres pantallas escritas con React y solo funciones y pensamos que eso puede escalar a un proyecto de cualquier tamaño. Nos decimos que si todo es una función pura y fácil de entender, será imposible que se complique. Es tan simple que esa es la única abstracción que hace falta.
Cualquier proyecto de suficiente tamaño se puede salir de control si no se tiene una arquitectura clara que nos ayude a organizar el código y a comunicar de forma efectiva lo que hace nuestro programa.
Hoy, sigo usando muchas técnicas que aprendí en mis días de FP pero también uso ideas de OOP y me alegra reportar que nadie ha muerto en el proceso. He encontrado un punto medio que usa ideas de ambos paradigmas y que me permite crear aplicaciones con una arquitectura clara, fácil de entender, que me ayuda a moverme rápido y hacer cambios con tranquilidad.
En un post futuro hablaré mas en detalle sobre algunas de estas técnicas e ideas de arquitectura.