¿Eres un programador pragmático? (II)

En este artículo continuaremos adentrándonos en el libro "The Pragmatic Programmer: From Journeyman to Master" de Andrew Hunt y David Thomas, del que ya hablamos en nuestro anterior post, profundizando ésta vez en algunas de las técnicas que nos recomienda para hacer un código que sea fácil de testear.

 

El libro dedica una buena parte del mismo, al cómo hacer código que pueda ser, posteriormente, fácilemente testeable. En su capítulo "Code that´s easy to test", insisten en la importancia de hacerlo y para ello nos dan una serie de pautas y principios de los que vamos a hablar a continuación.

Principio de la Ortogonalidad (Principle of Orthogonality)

El término “Ortogonalidad”, cogido de la geometría, explica que cuando dos líneas se encuentran en un ángulo recto ambas son independientes. Si te mueves en cualquiera de los ejes de esas líneas, la proyección en el otro eje no cambia. Es decir, dos cosas son ortogonales si cambios en una no afectan a ninguna de las otras.

En el libro nos encontramos como ejemplo que los controles para pilotar un helicóptero no son ortogonales, ya que cualquier movimiento en un control influye directamente en el otro.

En un software ortogonal las operaciones no tienen efectos laterales, cada operación cambia una cosa sin afectar a otras, de esta forma el código es más fácil de testear y ampliar. Ni que decir tiene que la mejora en la calidad de código es inmediata, así como una mayor productividad y una disminución del riesgo de errores.

Cuando un software no es ortogonal es muy difícil de cambiar y controlar. Al estar las piezas de software altamente dependientes unas de otras, cualquier cambio en una de ellas va a suponer un considerable esfuerzo y puede hacer que algo deje de funcionar. Sin embargo, si fuera ortogonal, al ampliar una unidad de software no tendríamos que preocuparnos de ninguna más.

En el libro también nos recomiendan técnicas para mantener la ortogonalidadindicando que al tener que diseñar un código independiente y con un propósito bien definido, entran de golpe tres conceptos importantes: la cohesión, el acoplamiento y otro principio que hemos escuchado bastante, el principio de responsabilidad única. A continuación, nos centraremos en los dos primeros: cohesión y acoplamiento. 

Se dice que algo tiene una alta cohesión cuando tiene definidos unos límites claros y toda la funcionalidad está en un único sitio. Sería la forma en la que se agrupan las unidades de software en una pieza más grande, ya sea una clase, un componente o una librería. Cuando un conjunto de métodos tiene un mismo fin funcional, estos métodos deben estar agrupados, y mientras más cohesionados estén estos métodos mucho mejor.

Por otro lado, definimos acoplamiento como el grado de dependencia y relación entre distintas piezas de software. Si al cambiar una requiere cambiar otra, se dice que hay un  acoplamiento entre ellas. Es decir, cuanta más funcionalidad pueda abarcar una pieza de software sin que dependa de otra será mucho mejor, en este caso diremos que las piezas de software tienen un bajo acoplamiento o que están desacopladas. Con ésto lograremos que nuestro código sea fácil de testear, fácil de mantener, aumentaremos el encapsulamiento, facilitaremos su reutilización y ampliación, y otra cosa aún más importante, facilitaremos la detección y corrección de errores.

En definitiva, debemos tener la cohesión lo más alta posible y el acoplamiento los más bajo posible. Ya que, aunque en la teoría ambos conceptos sean independientes, en la práctica no suele ser así porque, al aumentar la cohesión podemos terminar incrementando el acoplamiento por eso no es un aspecto tan trivial. Desde mi punto de vista, es más importante y preferible tener un bajo acoplamiento y por consiguiente sacrificar en algunos casos la cohesión, a tener una pieza de software altamente cohesionada y que sea muy difícil de reutilizar y ampliar.

Si queréis profundizar más en los distintos tipos de cohesión, acoplamiento y diseño de software podéis echar un vistazo a lo que dicen los patrones GRASP, patrones generales de software para asignación de responsabilidades.

Para conseguir esto último que hemos explicado, el libro nos recomienda que escribamos “código tímido” (shy code), es decir, código que no interactúe con demasiados métodos, que no debe de revelar demasiado de sí mismo y que no debe de ser demasiado entrometido. De este modo tu código se mantiene aislado, separado, “keep your code decoupled” nos suelen mencionar.


- Ley de Demeter.

Podemos empezar definiendo esta ley con la frase: “Habla solo con tus amigos, no hables con extraños”.

Esta antigua ley fue definida en la década de los 80 por un grupo de programadores que trabajaban en un proyecto llamado sistema Demeter y que está enfocado a reducir el acoplamiento.

Nos dice que un método de un objeto solo puede llamar a:

  • Métodos del propio objeto que lo contiene.
  • A los parámetros que recibe el método.
  • A cualquier objeto instanciado dentro del método.
  • A cualquier atributo del objeto que lo contiene.

Es decir, no se deben invocar a métodos de los objetos devueltos por otros métodos.

En código sería una estructura como ésta: objeto.getMetodo1().getMetodo2().getMetodo3();


Un ejemplo claro de la violación de esta ley es, por ejemplo, que una clase necesite utilizar otras muchas clases para realizar su funcionalidad, lo veremos rápidamente si se necesitan muchos “import” en su cabecera.

 

- Principio de “Tell, don’t ask”

Algo que está directamente relacionado con esto último, es el principio de “Tell, don’t ask”. Es decir, si necesitas modificar el estado de un objeto, deja que ese objeto cambie su estado por ti. No tenemos que utilizar los objetos para pedirles datos y luego realizar operaciones con esos datos, sino que debemos de decirles a los objetos que realicen ellos esas operaciones por nosotros.


- Evita datos globales (Avoid global data). El uso de variables globales crea dependencias ocultas y ésto puede desembocar a comportamientos inesperados en tu aplicación. Por lo que cuanto más corto sea el tiempo de vida de una variable mucho mejor. Pero si una variable es global cualquiera puede usarla o peor aun… cambiar su valor lo que puede llevar a errores inesperados.

- Evita funciones similares (Avoid similar functions). Cuando en una función o método se tiene un estructura similar con muchas partes en común y que solo cambia alguna variable, efectivamente estamos ante código duplicado. Y este código duplicado es un síntoma de problemas estructurales.


El libro también da mucha importancia al principio “DRY” (Don’t repeat yourself), “No te repitas”, de cómo surge y de cómo debemos evitarlo. Incluso divide el código duplicado en distintas categorías que vamos a ver a continuación:


- Duplicación impuesta.

En este caso los programadores sienten que ellos no pueden elegir y parece que el entorno les fuerza a duplicar el código debido a que algunas plataformas, como por ejemplo los navegadores, requieren su propio lenguaje lo que hacer tener que duplicar el código.

Los propios lenguajes de programación tienen ciertas estructuras que ya duplican código, el libro nombra algunos ejemplos en C++ y Pascal.

Los comentarios en código también son un ejemplo de duplicidad pues en caso de que haya un cambio habría que cambiar el código y los comentarios, además estos siempre tienden a quedar desactualizados.


- Duplicación inadvertida.

En ocasiones la duplicación viene por errores en el diseño de la aplicación.

Aparece como ejemplo la clase “Line”. La cual tiene los atributos “end”, “start” y “length”. A un primer vistazo puede parecer correcto pero si nos paramos a pensar, podemos ver que “length” se puede obtener de los atributos “end” y “start”.


- Duplicación impaciente.

En la mayoría de las ocasiones los proyectos tienen unos tiempos de entrega apretados, lo que nos obliga a tomar atajos para hacerlo de una forma más rápida pero con menos calidad. Si necesitamos hacer algo y hay algo parecido ya desarrollado, lo que hacemos es duplicarlo y hacer unos pequeños cambios a esto, quedando así realizado el nuevo cambio. Ésto es lo que se le llama duplicación impaciente y es fácil de detectar y arreglar solamente gastando algo de tiempo en diseñar mejor el nuevo código.


- Duplicación entre desarrolladores

Es el tipo de duplicación más difícil de detectar y es la que ocurre entre desarrolladores de un mismo proyecto. Funciones enteras pueden ser repetidas de forma inadvertida y sin ser detectada durante años, lo que puede conllevar problemas de mantenimiento.

Para evitar esto, hay que incentivar una comunicación frecuente y activa entre programadores. Nos aconsejan nombrar a un miembro del equipo como el responsable de facilitar el intercambio de conocimiento.

 

Y para terminar, el libro nos aconseja que en el sector en el que estamos tenemos que investigar constantemente, nos recuerdan que aprendamos un lenguaje de programación cada año y que no nos quedemos solo con el lenguaje con el que trabajamos normalmente. Los autores nos sugieren que hay que leer al menos cuatro libros técnicos al año, que saben que cuesta, pero nos reportará unos beneficios y un conocimiento que merecerá la pena.


Si tu lectura ha llegado hasta aquí, simplemente darte las gracias por leer el artículo y espero que haya sido de ayuda e interés.