Aplicaciones como frameworks
Un artículo de Rafa G. Blanes
Buenas prácticas de diseño y de arquitectura, una buena política de testing y una metodología ágil, son necesarias pero, en ocasiones, no lo suficiente como para encontrar la mejor solución a una lógica de negocio particular, como veremos en este artículo.
Para bien o para mal, me ha tocado en los últimos años, abordar y dirigir proyectos en cierta medida complejos y escalables.
Reconozco que esta complejidad no radica exclusivamente en tener que resolver problemas ciertamente difíciles, con grandes volumenes de datos o sistemas que deben soportar grandes cargas de trabajo o usuarios, sino más bien en mantener una visión de diseño y de arquitectura suficientemente flexibles para que estas aplicaciones puedan ser mantenidas durante años, ya que, algunos de estos proyectos de los que hablo, consisten en sistemas que, por su naturaleza, una vez instalados para el cliente, se van a mantener activos durante muchísimo tiempo (incluso décadas, me atrevo a decir), debido al grado de integración y de dependencia de esos negocios con este tipo de productos.
Sin ir más lejos, uno de ellos es la Plataforma de Telegestión IRIS, que, a día de hoy, ya tiene unos ocho o nueve años de funcionamiento en diferentes instalaciones.
Como digo en ocasiones, una aplicación que va a evolucionar mucho y durar muchos años, tiene que ser construida de un modo diferente que los proyectos que se cotizan por horas, se entregan y no van a cambiar más.
Cuando construyes aplicaciones así, te basas, lógicamente, en un conjunto de frameworks y librerías de muchos tipos (en el ejemplo que comenta, .NET, KendoUI, NInject y un larguísimo etcétera). Como cualquier otra aplicación, claro está.
No obstante, cuando implementas la funcionalidad específica del producto, en la forma de su lógica de negocio, solemos tender a implementarla ad-hoc, basada, claro está, en todos esos frameworks y librerías, como no puede ser de otra manera.
Tendemos a pensar que un framework en software consiste únicamente en eso: algo en lo que te basas para construir lo específico de un proyecto, y está bien y tiene que ser así, no hay duda de ello. Pero esa concepción de framework es demasiado limitada.
Todos los proyectos demasiado espaguetizados que he visto (e insufribles), tenían ese defecto: demasiado código explícito para resolver su lógica de negocio y ausencia absoluta de una mínima abstracción, esto es, la implementación de utilidades o reglas que permitan implementar esa lógica de negocio particular.
Programar, en cierto modo, consiste en aplicar un paradigma de trabajo que, finalmente, generará el código de una solución de una forma u otra. Por poner un ejemplo, no tiene nada que ver el diseño y la forma de resolver un problema usando orientación a objetos que si usas un lenguaje funcional. Según el lenguaje que uses, utilizarás unos artefactos u otros.
Pero no solo cambian las herramientas que usas (en la forma de sintaxis y utilidades), sino que, con ellas, tu mente aprende a abordar un problema de una forma u otra. Me explico.
Dadas unas herramientas concretas, la forma de implementar algo cambia, esto es, piensas en el modo de resolver el problema a partir de ellas. La limitación no se basa solo en lo que te ofrece un entorno de trabajo concreto (objetos, funciones, funciones lambda, ciertas APIs, etc.), sino en que adoptas un paradigma mental con el que encontrar la solución correcta.
De ahí que para mucha gente acostumbrada a usar lenguajes funcionales, cuando aprenden orientación a objetos, no usan correctamente este enfoque, porque siguen pensando desde la perspectiva de un lenguaje funcional (y suelen concebir las clases como simples cajas donde meter funciones relacionadas sin aprovechar todas las abstracciones y la riqueza de modelado que permite OOP).
Un artista que dispone de una paleta con muchos colores y varios tipos de pinceles, conseguirá plasmar en un lienzo una idea de cuadro con una técnica diferente que si tan solo tuviese a su disposición un bolígrafo bic y un folio A4 (es tan solo un ejemplo). El mismo pintor, claro está, pero la herramienta condiciona su forma de pensar para resolver el mismo cuadro de la forma más aproximada. Del mismo modo (y siguiendo con otro ejemplo algo peregrino), según la herramienta que tengas para clavar en una pared una puntilla, lo harás de un modo u otro: no es lo mismo hacerlo con un palo que con un martillo en condiciones, ¿no?, aunque, quizá, sería mucho mejor disponer de una pistola automática.
En software, podemos pensar con la mentalidad de framework: cuando tienes que resolver una lógica de negocio ciertamente compleja, tendemos a implementarla directamente, en lugar de abordarla desde la mente y la perspectiva de un framework. Este modo de pensamiento y forma de abordar algunas soluciones software, se me ha revelado muy útil.
Por poner un ejemplo algo trivial e ilustrativo: supongamos que te piden implementar una aplicación para resolver la operación "2 + 5", y vas y programas el cálculo directo de esa operación. Después, a la aplicación hay que añadirle otra operación diferente, como por ejemplo "4*3 - 12", y vuelves a programar ad-hoc (explícitamente) el cálculo de esta expresión, y así, continuamente.
¿No es mejor implementar una librería a modo de calculadora genérica para resolver ese tipo de expresiones? Parece obvio, pero es así, con esas expresiones demasiado explícitas, como se suele implementar la funcionalidad de alto nivel de la mayoría de aplicaciones, cuando, en realidad, en muchos escenarios (y no digo en todos), hay una solución mejor.
Siguiendo con el ejemplo: dominio a resolver: operaciones matemáticas simples. Abstracción a implementar: calculadora (éste es tu framework para resolver tu lógica de negocio).
¿Qué será más inteligente y productivo? ¿Implementar el cálculo directo para cada tipo de operación o construir una sola vez la librería calculadora y utilizarla cada vez que haya que calcular una nueva expresión? Entiéndase el ejemplo.
Me temo que se suele implementar la lógica de negocio (esa funcionalidad que es exclusiva del dominio concreto de una aplicación) del modo anterior: escribiendo directamente el código para hacerla funcionar sin abstraer absolutamente nada, de modo que la evolución de esa aplicación consiste en escribir explícitamente cualquier nueva lógica de negocio que venga, aunque esté relacionada con la anterior.
De ahí, quizá, esos fragmentos de código con ficheros de cientos de líneas (o miles, sí, los he visto) con if, if, if, if... etc.
La abstracción consiste en darse cuenta de que dentro de la lógica de negocio de un dominio concreto, siempre se pueden encontrar patrones, logicas similares, reglas parecidas o funcionalidad sospechosamente parecida a otra.
En eso consiste la mentalidad de framework: tu trabajo ya no consiste en implementar directamente esa lógica de negocio o funcionalidad de alto nivel con código explícito ad-hoc (las expresiones matemáticas del ejemplo), sino en crear un framework que facilite implementar más fácilmente todas esas lógicas de negocio relacionadas (siguiendo con el ejemplo, crear la calculadora).
No siempre es posible aplicar esta forma de abordar el software, pero si se piensa de este modo, me atrevo a decir que las soluciones serán más robustas, más flexibles al cambio y permitirán una velocidad mayor de desarrollo.
Sin entrar en demasiado detalle, en la Plataforma de Telegestión IRIS que comentaba anteriormente, hay que gestionar muchos mensajes de diferente tipo pero similares provenientes de ciertos dispositivos que mantienen una estructura más o menos homogénea: en lugar de gestionarlos explícitamente según cada tipo (son unos cuarenta, si no recuerdo mal), creé una especie de framework para gestionar lo común de cada uno de ellos de modo que cuando los dispositivos evolucionan e incorporan uno nuevo, su inclusión en el sistema resulta trivial.
Del mismo modo, otro proyecto en el que he participaco, Picly (un servidor de imágenes al vuelo), existe un mecanismo de plugins extensible para incorporar todas las transformaciones imaginables sobre imágenes (el framework en este caso consiste en la implementación de ese gestor de plugins).
Son tan solo dos ejemplos que se me vienen a la cabeza, que, si no se hubiesen enfocado de ese modo, el desarrollo de esos dos productos habría sido muchísimo más costoso y más difícil de evolucionar y mantener.
Piensa con esta mentalidad de framework cuando tengas que resolver lógica de negocio, tus soluciones serán más robustas, menos costosas, más evolucionables y profesionales.