Separar la construcción de un sistema de su uso
Según Robert C.
Martin:
«Software
systems should separate the startup process, when the application
objects are
constructed and
the dependencies are “wired” together, from the runtime logic
that takes
over after
startup.»
«Los sistemas de
software deberían separar el proceso de arranque, cuando se
construyen los objetos de las aplicaciones y se “enlacen/conecten” sus
dependencias, de la lógica en tiempo de ejecución que toma el control tras el inicio.»
El proceso de
arranque es uno de los aspectos que cualquier aplicación
debe abordar. La separación de aspectos es una de las técnicas de
diseño más importantes.
El siguiente código
podría formar parte del proceso de arranque de un sistema. Es este
se puede apreciar que el código que construye el objeto de la base
de datos está mezclado con la lógica del tiempo de ejecución qué
es el que devuelve el objeto. Por ejemplo:
Esta técnica se
llama Lazy
Initialization/evaluation. Con esta técnica no se
sobrecarga la construcción ya que hasta que no se use el objeto no
se crea por lo que el sistema acelera su proceso de arranque.
También evita que devuelva un valor nulo.
Un inconveniente es
que la función tiene una dependencia de «DbConnectionImp»
en el código de la misma. En algunos lenguajes no se va a poder
compilar sin resolver la dependencia incluso si nunca se crea un
objeto durante la ejecución.
En el caso de las
pruebas, si «DbconnectionImp» es un objeto pesado, se tendrá que
hacer un test
double o mock
object para que se asigne al servicio antes de llamar
al método durante la prueba. Al estar mezclada la lógica de
construcción del objeto con la lógica de ejecución se deberían
probar todos los caminos (esto incluye el bloque «null»). El método
tienen dos responsabilidades, o sea, que está haciendo maś de una
cosa (crear el objeto y devolverlo) por lo que está rompiendo el
Principio de responsabilidad Única (Single
Responsibility Principle).
Los sistemas con un
diseño robusto no deberían tener una estrategia de configuración
global que esté diseminada por la aplicación con poca modularidad.
El proceso de arranque debería ser modularizado y separado de
la lógica de ejecución de manera que el sistema tenga un estrategia
consistente para resolver las dependencias.
Separación en un módulo de arranque
Una manera de
separar los aspectos de construcción es moverlos a un módulo de
arranque y diseñar el resto del sistema de manera que se asuma que
todos los objetos y sus dependencias estén resueltas correctamente.
El módulo de
arranque construye todos los objetos necesarios para el sistema y
este los pasa a la aplicación que los usa. La aplicación no tiene
conocimiento ni del sistema ni del proceso de construcción.
Factorías
Las factorías se
utilizan cuando la aplicación necesita crear un objeto. En estos
casos se usa el Abstract
Factory Pattern de manera que los aspectos de la
construcción estén separados de la aplicación. Esto significa que
la aplicación está desacoplada de los detalles sobre cómo se
construye un objeto.
Inyección de dependencias
La inyección de
dependencias (Depencency
injection) se utiliza como estrategia para separar la
construcción del uso del objeto.
La inyección de
dependencias es una de las formas de la estrategia de Inversión
de control (Inversion
of Control). Esta mueve responsabilidades secundarias
de un objeto a otros que están dedicados a este propósito y, por lo
tanto, cumplen el Principio de responsabilidad única (Single
Responsibility Principle).
Un ejemplo de clase
sin inyección de dependencias.
En este ejemplo se
puede apreciar que la clase tiene la dependencia de PostScore ya que
la clase crea la instancia.
En la inyección
de dependencias la clase no hace nada por resolver sus
dependencias, esta es completamente pasiva, aunque provee métodos o
argumentos en su constructor o constructores que son usados para
inyectar estas dependencias.
En el siguiente
ejemplo se puede ver como la clase modifica el constructor para poder
inyectar la dependencia desde el exterior de la clase.
En el contexto de
administración de dependencias un objeto no debería tener la
responsabilidad de instanciar dependendencias él mismo sino que
debería pasar esta responsabilidad a otro mecanismo autorizado y,
por lo tanto, invertir el control. Este mecanismo autorizado es parte
de los aspectos de configuración por lo que debería estar en el
módulo de arranque o un contenedor a tal efecto.
Un mecanismo
autorizado puede ser una factoría creada para tal fin. Por ejemplo:
Durante el proceso
de construcción el contenedor de inyección de dependencias
instancia los objetos que se van a usar (usualmente bajo demanda) y
usa los parámetros de los constructores o los métodos para conectar
estas dependencias. Los objetos que se inyectan están especificados
en un archivo de configuración o en módulos de construcción
La técnica de
Lazy-evaluation es útil con la inyección de dependencias.
Los contenedores de inyección de dependencias no construyen el
objeto hasta que es necesario. Muchos de estos contenedores proveen
mecanismos para invocar factorías o crean proxies que puedan usar la
Lazy-evaluation y optimizaciones similares.
Desarrollo
El desarrollo del software debería hacerse de manera progresiva. No se deberían desarrollar funcionalidades que vayan más allá de las necesidades actuales haciendo más complejo el desarrollo actual (sobreingeniería). Metodologías como el TDD, el refactor y el código limpio abarcan el ámbito del código. Según dice Robert C. Martin un sistema puede crecer progresivamente:
«Software
systems are unique compared to physical systems. Their architectures
can grow
incrementally, if
we maintain the proper separation of concerns.»
«Los sistemas de
software son únicos en comparación con los sistemas de hardware. Su
arquitectura puede crecer de forma progresiva, siempre que
mantengamos una adecuada separación de aspectos.»
Optimización de la toma de decisiones
Un sistema lo suficientemente grande en el cual los aspectos estén separados permite una administración y una toma de decisiones descentralizada pudiendo haber más de un responsable.
Las personas con más
cualificación deberían tomar las decisiones en su ámbito. También
se debería tener en cuenta que estas decisiones deberían posponerse
hasta el último momento posible ya que esto permite obtener más
información sobre el problema. Una decisión prematura es una
decisión hecha con un conocimiento que no es óptimo. Se tiene menos
conocimiento sobre el problema, sobre el proyecto, sobre la
implementación, etc.
«The agility
provided by a POJO system with modularized concerns allows us to make
opti-
mal, just-in-time
decisions, based on the most recent knowledge. The complexity of
these
decisions is also
reduced.»
«La agilidad que
posibilita un sistema POJO con aspectos modularizados nos permite
tomar decisiones óptimas basadas en la información más actual. De
esta forma, asimismo, reducimos la complejidad de dichas decisiones.»
Uso de estándares cuando añadan un valor demostrable
Es posible que se planteen soluciones basadas en determinados estándares cuando otras posibles soluciones menos complejas y más ligeras podrían haber bastado.
«Standards make
it easier to reuse ideas and components, recruit people with relevant
experience, encapsulate good ideas, and wire components together.
However, the process of creating standards can sometimes take too
long for industry to wait, and some standards
lose touch with
the real needs of the adopters they are intended to serve.»
«Los estándares
facilitan la reutilización de ideas y componentes y permiten
contratar gente con la experiencia pertinente, permiten almacenar
buenas ideas y conectar componentes. No obstante, el proceso de
creación de dichos estándares puede resultar demasiado largo para
el sector y algunos pierden su relación con las necesidades reales
de los usuarios objetivo.»
Los sistemas necesitan lenguajes específicos de diseño
Los lenguajes específicos de diseño (DSL) son lenguajes que se crean con el fin de que el código se pueda escribir y leer de una forma estructural del mismo modo en que hubiera podido ser escrito por un experto del dominio.
Un buen DSL minimiza
las diferencias de lenguaje entre el concepto del dominio y el código
que lo implementa de manera que mejora la comunicación entre todas
las partes implicadas agilizando el desarrollo del sistema.
Cuando los DSLs se
usan de una manera efectiva elevan el nivel de abstracción sobre el
código y los patrones de diseño permitiendo al desarrollador
revelar la intención de su código.
Conclusión
Los sistemas, al igual que el código, deberían ser limpios. La arquitectura debería separar los distintos aspectos para que no estén ACOPLADOS, con el fin de evitar que la lógica se complique y esto repercuta en la calidad, pues los errores se esconden más fácilmente y se complica la implementación de las distintas FUNCIONALIDADES. Por ello, si la agilidad del desarrollo se ve afectada, la productividad disminuye y se pierden los beneficios del TDD.
A la hora de diseñar
un sistema o un módulo no hay que olvidar que es posible que haya
una solución más simple que funcione.
No hay comentarios:
Publicar un comentario