miércoles, 18 de abril de 2018

Sistemas - Clean code

Esta publicación se centra en el capítulo de Sistemas de Clean Code (Robert C. Martin).

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