DEV Community

Cover image for Sobreingeniería, y el mal de abstraer todo a mi alrededor
Nehuen Covelo
Nehuen Covelo

Posted on

Sobreingeniería, y el mal de abstraer todo a mi alrededor

Artículo de la serie ¿Que es escalable?, Y que lo es en el desarrollo frontend?.

Foto de Wil Stewart en Unsplash


En el ambiente de IT, en mi caso en el desarrollo de aplicaciones y sistemas, siempre se intenta abstraer todas las entidades con las que trabajamos. Siendo el objetivo: poder clasificar, englobar y entender los objetos con los que tenemos que lidiar en nuestros proyectos. Se intenta no dejar suelta ninguna variable al azar, desestimando el problema principal, que nuestro sistema es operado por un gran factor de azar, el usuario. Y cuando vamos a nuestro frontend, al usuario no le va a importar si el código que está corriendo por detrás es escalable o no, las tecnologías que se utilizaron, etc.

Se cuestiona así la actividad del poeta. Es como si en lugar de ejercer esta libertad inventiva, la tarea del poeta hubiera sido primordialmente la de construir una unidad engañosa.
- Juan Luis Vermal, sobre la relacion de Poesía y verdad en Nietzche

Vease al desarrollador/ingeniero de software como este poeta, que va a describir su sistema basado en las demandas que tiene. Algunos sistemas serán simples hasta en su definición y no necesitan ningún tipo de complicación. Pero existe el arte de complicar las cosas, creando e imaginando problemas donde todavía no existen. Y en mi camino como desarrollador encontré mucha gente con una gran facilidad para complicar las cosas, en todos los sentidos posibles, habidos y por haber.

Me adjudico aquí la siguiente frase que no estará directamente entendida al contexto que estamos hablando, pero espero que al terminar la lectura se entienda. Luego, dejando esta ruidosa introducción atrás, comenzaremos a hablar de cosas más concretas.

Aquello que todavía no debemos, con el tiempo costará muy caro
- Nehuen Tupac Covelo Duran


Cuando vamos a desarrollar algún proyecto, vamos a intentar reutilizar la mayor cantidad de componentes, o sea hacer 1 vez cada UI para no tener que repetir código, reglas, estilos, etc.; evitando principalmente mantenimiento de código repetido. Y en componentes primitivos es fácil realizar eso(botones, inputs y links entre otros). Y de eso hasta podemos subir un nivel más, hacer los componentes más complejos(tablas, formularios, layout, etc). Pero empieza a complicarse mucho la cosa cuanto más vamos subiendo de nivel, por ejemplo a nivel de página; porque queremos abstraer el componente de una página? ¿Cuál sería el sentido de eso? Y justamente en estos puntos, cuando llegamos a componentes muy macros, es donde hacer sobreingeniería hace estragos. Y voy a dar el ejemplo más burdo y conciso en mi experiencia para que entiendan mi punto:
En una página de detalle de una película se debe bajar la información de esa película, y si el usuario tiene información guardada de esa película, lo cual todo debería venir en el mismo paquete de datos. En la pantalla se refleja si el usuario la agrego a favoritos, y si hubiese empezado a verla, un tiempo para reanudar la reproducción.
La lógica para cargar esto sería solamente bajar esos datos y guardarlos en un estado que luego se encarga de mostrarlo, si alguna cosa fuese modificada, tan solo mandar al backend la modificación. Es un flujo simple sin problema, haríamos una función handler para cada cosa, y estaríamos bien(obviamente podemos agregar una capa de servicios para no tener ciertas reglas en la UI y etc.).
La realidad del proyecto era totalmente diferente, alguna persona tuvo una idea de hacer un frontend que recibe una configuración de cómo son las páginas, eso va a mostrar condicionalmente los componentes de la página, cada componente de página va a generar un store interno para controlar los datos, y depende los datos que cambien a veces vamos a tener reglas que se encargan de mandar al backend depende las condiciones. Todo esto fue pensado con la idea de que haya un backoffice que deje a personas del equipo de Producto cambiar disposiciones y layouts de las cosas, pero resulta que nunca fue el objetivo real de la demanda solicitada. Entonces el resultado fue un frontend muy complicado de mantener con el tiempo, porque cambiar una condición en una regla de negocio podía romper todo muy fácilmente.

Y en este punto recuerdo mi frase; esa feature que nadie utilizó les costó un mantenimiento muy caro a los desarrolladores algunos años después. Porque no es el hecho de que el código se fue ensuciando y complicó el mantenimiento. Sino que el proyecto mal estructurado, para algo que no era el objetivo final, ayudó muchísimo en esa transición para que se torne una bola de código pegada por voluntad divina.

Otro gran problema de sobre-abstracción, cuando vamos a desarrollar componentes, es querer embutir reglas de negocio sobre componentes UI. Por ejemplo, un Input específico para validar documentos. Este componente además de cumplir su función como Input de datos, va a tener embutido una lógica con un regex, que si lo ingresado es incorrecto deberá dar un error de algún tipo. Pero qué pasa si se solicita que haya una validación para documento tipo DNI, pero otra para pasaporte? Y peor aún si tenemos que cambiar el texto de error devuelto, o si tenemos que hacer diferentes comportamientos: que muestre un mensaje simple, que abra un modal con una explicación de donde encontrar ese número correcto, etc. Tendríamos que hacer varios componentes particulares para cada caso, o generar un súper componente que logre recibir todo este tipo de variables, desde un mensaje de error diferente, hasta un tipo de validación diferente.
O lo que es más simple, más fácil, más entendible a nivel codigo: Que un componente Input(lo más headless posible) reciba la validación a través de una propiedad, por lo cual recibiría una función, y el accionar del flujo de error que va a percibir el usuario esté en la misma implementación. Si lo aplicamos, en cierto punto se ve muy simplista, y al ojo que le gusta abstraer todo puede no gustarle, pero créanme que con el tiempo ese código es mucho más fácil de entender y mantener.

Voy a explicar el ejemplo de un proyecto que nos pareció muy simple, tanto en implementación como desarrollo, y terminó siendo más que completo. Era necesario generar plantillas de emails de alguna forma dinámica, porque hacerlos a mano no era una opción debido a la cantidad de tiempo que demanda. Para esto creamos un pequeño proyecto basado en Pug.js, donde levanta un server en local para que se pueda hacer una preview de la plantilla necesaria, y luego un pipeline para hacer deploys, que lo único que hace es build de las plantillas y guarda los html resultantes en un S3 de AWS para que sea utilizado por el backend al momento de enviar los emails. Y la estructura de las carpetas se divide principalmente en dos carpetas, la de templates y components. En components encontramos todos los componentes(mixins en Pug) listos para usar, que reciben algunos parámetros de lo que tienen que mostrar y ya con todos los estilos listos. Y la carpeta templates contiene la definición de cada uno de las plantillas que sean necesarias, y esta carpeta es muy clave para la gente que no es del ámbito frontend(Backend, Growth, Producto, etc), porque todas las plantillas quedan escritas totalmente en la sintaxis de Pug que es muy parecido al markdown, y es entendible para cualquiera. De esa forma nos encargamos de crear los componentes base, y cualquier persona puede armar y desarmar esas plantillas sin necesidad de nuestro foco o ayuda.


La funcionalidad y el mantenimiento del proyecto nunca debería estar condicionada por la complejidad de la estructura del mismo.

Otro problema muy común en donde hacer sobreingeniería, es cuando las problemáticas y demandas no están definidas, o 100% cerradas. Y es un caso muy común en las startups, donde el PMF(Product Market Fit) no está totalmente consolidado y las reglas de negocio pueden cambiar a cualquier momento. Para lo cual hay un tipo de proyecto que se adapta a esas situaciones, el famoso MVP(Minimum Viable Product), el cual siempre debe ser descartado luego de un tiempo para ser reemplazado con un producto más robusto. Pero este tipo de proyecto no siempre cumple su ciclo de vida como debería, y el MVP con el tiempo se convierte en el producto final(arrastrando mucha deuda técnica); o donde el MVP nunca termina de salir a producción porque nunca está perfecto. Aunque una buena parte de mi experiencia sea en startups, no voy a ahondar mucho en esta parte, pero solo para cerrar: los MVPs deben ser proyectos muy simples, nada complejos, que puedan ser descartados fácilmente porque no representan un costo considerable de desarrollo.


Una moda en estos últimos tiempos, que es un claro ejemplo de sobreingeniería, fue la de promover hacer los frontends basados en DDD(Domain Driven Design), e implementando algún tipo de arquitectura como la Hexagonal. Todo esto es muy lindo cuando se dan algunas condiciones:

  • Tenemos una clara definición de los dominios
  • La capa de infraestructura no domina, ni define reglas de negocio, al interior de los dominios
  • Sabemos cómo implementar algo así en Frontend

Hasta ahora, nunca vi ningún proyecto(laboralmente) que cumpla esas condiciones. Y puedo asegurar, que intentar implementar este tipo de cosas sin darse todas las condiciones es un fallo asegurado. De alguna forma la implementación va a fallar, sea porque la infra va a imponer reglas de negocio sobre las nuestras, o porque ni siquiera tenemos una idea de cómo organizar esto para que tenga algún tipo de sentido y sea mantenible con el tiempo.


Todos estos puntos y otros, contribuyen no solo a la mala organización y difícil mantenimiento del código, sino también a la mala experiencia de desarrollo que deban sufrir la gente que entrara nueva. Se deberán encontrar con código que en sí mismo no atiende a la demanda y/o problemática, difícilmente entendiendo que es lo que hace y qué cosas se pueden sacar sin que explote el proyecto.

Top comments (0)