El arte del código limpio – II

“Un buen programador es aquel que siempre mira a ambos lados antes de cruzar una calle que tiene un único sentido”

Doug Linder

Vuelvo a abrir la caja de Pandora de la programación para hablar de nuevo sobre buenas prácticas en la elaboración de nuestro código. Para aquel que no lo sepa en el artículo anterior de “El arte del código limpio – I” repasamos ciertos conceptos que nos servían para aportar una mayor legibilidad a nuestro código, como puede ser con el correcto nombrado de variables y métodos, eliminación y correcto uso de comentarios, y junto con algunas buenas practicas en la creación de funciones.

Se trataron los aspectos más simples y minúsculos del código, con esto remarco que en estas secciones no hablaré sobre patrones concretos o técnicas específicas para la correcta fabricación de nuestro código, si no de buenas prácticas en general, a fin de cuentas tratamos de pulir los más ínfimos detalles que pueden parecer nimiedades pero que son los que realmente aportan calidad en nuestro código. Dicho esto, en éste artículo trataremos la calidad en el uso de Objetos y Clases.

Uso de Objetos

Antes de comenzar con las buenas prácticas vamos a dar un breve repaso de qué significa un Objeto en Programación Orientada a Objetos (OOP). Se puede definir como la implementación de un tipo de datos abstracto, es decir, conocemos los tipos primitivos que suelen manejar todos los lenguajes de programación (Enteros, caracteres, decimales… etc), partiendo de esto un objeto nace de la necesidad de representar conjuntos de datos relacionados con un mismo concepto. Por ejemplo, la agrupación de varios tipos primitivos como cadenas y enteros podría representar a un usuario con su nombre y edad como tipos primitivos.

Con esto conseguimos la creación de distintas estructuras de datos, y no solo esto, podríamos definir varias instancias de un mismo objeto basándonos en un “esqueleto” denominado Clase del que hablaremos más adelante. Otra ventaja que ofrecen los objetos es que son capaces de encapsular la información y de permitir/denegar el acceso o modificación a sus datos internos mediante métodos o funciones (Definidas previamente por un programador claro…). Pues bien, una vez que ya tenemos claro qué es un objeto, continuamos con cómo usarlos de forma correcta.

Ley de Demeter

Esta regla o norma, también conocida como “No hables con extraños”, básicamente nos viene a decir que nuestros objetos no deberían hablar (invocar) métodos o acciones de otros objetos que no estén directamente relacionados con nosotros. Esta idea parte de que cada objeto solamente debe conocerse a si mismo y además también puede conocer algunas partes de otros objetos relacionados con el, pero no todas las propiedades de éste otro ente.

O dicho de una forma más técnica, evitar invocar directamente los métodos de objetos devueltos por otra función. Veamos un ejemplo para dejarlo más claro aun:

ObjectA a = new ObjectA(); // "a" Contiene una referencia a un objeto "b"
a.GetB().OtherAction(); // rompe la Ley de Demeter
a.OtherAction(); // Uso correcto 

Como podemos ver el ejemplo es muy sencillo, incumplimos la norma cuando realizamos una llamada sobre el objeto A, el cual nos devuelve una instancia de un objeto B, e inmediatamente realizamos otra llamada sobre el objeto B. Si por el contrario en el objeto A definimos un método desde el que internamente realizamos la acción sobre B, no estaríamos rompiendo la “Ley de Demeter”. El porqué de la importancia de esta regla (Pudiendo entender que ha primera vista parezca una burda tontería) radica en que si estas acciones las vamos propagando por todo nuestro código, la cadena de responsabilidades de las que deberían beber nuestros objetos se rompe. Reinando la anarquía y haciendo que cualquier objeto pueda acceder a cualquier otro sin restricciones algunas, cuando la programación orientada a objetos busca totalmente lo contrario.

Si intentamos seguir esta ley conforme vamos desarrollando nuestro código el resultado será que habremos definido de una forma clara los contextos de actuación de cada clase, reduciendo así de sobremanera las dependencias entre objetos, facilitando la reutilización de los mismos y por último pero no por ello menos importante, nuestro código sera fácilmente mantenible y flexible al cambio.

Ocultar la Información

Este principio básico de la programación orientada a objetos también se ve beneficiado por la “Ley de Demeter” vista en el apartado anterior. Y si indagamos un poco más en este principio veremos que se trata del simple hecho de que un objeto como tal, debe de ocultar su estructura interna y tan solo mostrar aquellas propiedades o funciones que sean de real importancia hacia el exterior y que su conocimiento no pueda desbaratar la lógica interna de dicho objeto.

Este encapsulamiento de la información se puede proveer en los lenguajes a través de las clausulas de visibilidad ofrecidas tanto para propiedades como para métodos y clases, estas se componen normalmente en 5 niveles:

  1. Publico -> cualquiera que tenga acceso al objeto puede acceder.
  2. Protegido -> Solo tienen acceso aquellas clases que son del mismo tipo del objeto que las implementa (Herencia).
  3. Interna -> Accesible tan solo desde otros objetos ubicados en el mismo ensamblado en el que se encuentre el origen.
  4. Protegido interno -> Mezcla de ambas visibilidades, permite acceder a objetos en el mismo ensamblado o aquellos de los que herede y se pueden encontrar en ensamblados distintos.
  5. Privado -> Solo el propio objeto internamente tiene acceso.

Ahora mismo te puedes estar preguntando que porqué tanta historia para simplemente ocultar la información, pues bien, todo parte de intentar evitar que ciertos agentes externos a tu objeto sean capaces de dejar en un estado inconsistente o inválido la información que éste alberga (Y créeme, es muy fácil que esto suceda…). Podríamos decir que el único objetivo y por el que debe luchar un objeto hasta ser eliminado (Disposed) es asegurarse que la información que éste ofrece es válida para un contexto delimitado. A continuación veamos un ejemplo sencillo de cómo funciona el encapsulamiento:

public class User {
  private string _dni;

  public User(string dni){
    SetDNI(dni);
  }

  public string GetDNI(){ 
    return _dni;
  }

  public void SetDNI(string dni){
    // Ejemplo básico de validación de DNI
    if(string.IsNullOrEmpty(dni) || 
       dni.Lenght != 9 || 
       !IsLetter(dni[dni.Length - 1])){
          throw new InvalidArgumentException("El DNI indicado no es correcto");
    }

    _dni = dni;
  }

  internal bool IsLetter(char c)
  {
    return (c>='A' && c<='Z') || (c>='a' && c<='z');
  }
}

Con la implementación anterior de la clase User estamos consiguiendo que el DNI establecido en objetos de éste tipo siempre sea un documento identificativo válido, de ésta forma estamos asegurandonos de mantener la integridad de la información propia de éste objeto. Al establecer como privada la propiedad y haciendo solamente posible su acceso a través de los métodos de SetDNI() y GetDNI(), conseguimos validar de forma sencilla que el valor proporcionado es el correcto. Existen otras formas más elegantes para conseguir ésto, pero lo que quiero destacar aquí es que si hubiese creado la propiedad DNI como pública en sus métodos de acceso (public string DNI {get; set;}), cualquier clase que tuviera acceso a dicho objeto podría modificar éste valor poniendo cualquier cadena de texto, rompiendo así uno de los objetivos para los cuales la clase User ha sido implementada.

Data Transfer Object (DTO)

Seguimos con una buena práctica que también esta considerada un patrón de diseño de software, y no es nada más que el uso de objetos intermedios (DTO) entre nuestro dominio interno y las capas externas de nuestra aplicación. Su única finalidad es la de transmitir información y para nada deben de contener lógica de negocio, en definitiva se trata de objetos planos con datos preparados para ser enviados, ya sea a través de la propia red o entre las distintas capas de nuestro código.

Además de un simple tratamiento de la información con el uso de DTO´s conseguimos también un desacoplamiento entre los distintos niveles de nuestra aplicación antes mencionados, con lo que estaríamos cumpliendo así el principio Abierto/Cerrado (Open/Closed) del conjunto de principios SOLID del que ya hemos hablado en anteriores artículos.

Siguiendo con el ejemplo visto en el apartado anterior donde disponíamos de una clase User, con lógica de dominio específica para la validación de un DNI por parte del usuario. Ahora queremos que ésta información sea enviada a éste cuando realice una petición a nuestra API, ha primera vista sería muy sencillo enviar directamente la clase User puesto que ahí es donde reside tal información, el problema radica en que si hacemos eso estaríamos exponiendo el método SetDNI() con toda la lógica sensible de nuestro sistema. También podrías serializar la misma clase en un objeto JSON (Por ejemplo…) y que dicho proceso ignore todo lo demás excepto la propiedad con el DNI, todo funcionaría a la perfección en principio, los problemas te los encontrarías en el caso de que dicho proyecto siga evolucionando y la entidad User siga creciendo y creciendo con más lógica y propiedades, y sin darte cuenta llegarás a un punto en el que tienes a todas las partes o servicios que requerían de dicha información acoplada totalmente a cómo tu entidad User esté formada, llegando a ser bastante tedioso en ciertas situaciones seguir añadiendo mas modificaciones y que nada de lo anterior se vea afectado o no controlemos que partes estamos exponiendo y cuales no.

Si sencillamente creamos una clase como la siguiente:

public class UserDTO {
  public string Dni {get; set;}
}

Y simplemente mapeamos (Recomiendo el uso de Automapper si utilizas C#, del que tengo pendiente un artículo proximamente…) la propiedad de la clase interna _dni a la homónima en un objeto de la clase UserDTO nuestro código quedaría totalmente desacoplado y más fácil de mantener, y no todo termina aquí, porqué además de las ventajas anteriores estaríamos cumpliendo sin saberlo a priori, con la creación de clases POCO (Plain Old CLR Objects). Estas serían clases de nuestro dominio en las cuales prescindimos de cualquier uso de Frameworks o Dll´s externas que acoplen nuestro dominio a cualquier tecnología que pudiéramos usar incluida aquella relacionada con algún motor de persistencia. Al final, lo que buscamos es que nuestras clases relacionadas con el núcleo de nuestro sistema se encuentren lo más limpias posibles.

Uso de Clases

En secciones anteriores hemos hablado de buenas prácticas y patrones relacionados con el uso y tratamiento de objetos, y por ende al hablar de objetos también hemos tratado en parte el uso de clases. Puesto que el nacimiento de objetos depende plenamente de la definición de clases. Podríamos definir una clase como el “esqueleto” o el esquema de un objeto, ya que éso es lo que estaríamos definiendo en última instancia, una forma de definir conjuntos de datos y comportamiento que se encuentran interrelacionados.

Y además, dada la definición anterior también podríamos decir que un objeto es la materialización concreta de una clase, por ejemplo, podríamos definir una clase denominada Vehicle, que como su propio nombre indica almacenaremos datos relativos a coches, esta clase se compondrá de propiedades como el modelo del coche, su número de matricula, color …etc. Dicho esto un objeto sobre la clase Vehicle sería una instancia concreta de un coche, en la que podríamos tener por ejemplo un modelo de coche como Seat, con matrícula 3685DLK y color azul. y así de la misma forma podríamos tener distintas versiones de coches con diversos valores en sus propiedades. Una vez que hemos entendido claramente qué es una clase y en qué se diferencia de un objeto, pasemos a conocer algunas buenas prácticas en el uso de clases.

Principio de Responsabilidad Única

Volvemos a hablar de uno de los conocidos principios SOLID de desarrollo de software, se trata nada menos que el Single Responsibility Principle o principio de Responsabilidad Única. Para aquellos que no tengan buena memoria, si recordamos en el artículo anterior sobre “El arte del código limpio I” ya dimos ciertas pinceladas acerca de éste principio en el uso de funciones, y nos venía a decir que las funciones definidas deberían de tratar de hacer una sola cosa y ésta hacerla a la perfección. Si ahora extrapolamos esa filosofía en el uso de clases, trataremos de definir clases en las que su objetivo este plenamente claro, y no exista duda alguna de la existencia de dicha clase.

Con ésto definiremos clases como el que trata de construir un puzzle, vamos juntando y creando piezas pequeñas que colaboran entre sí, donde cada una tiene su responsabilida, su única razón de existir y cambiar. Y no solo eso, cómo en secciones anteriores hemos destacado una clase debe ocultar su información interna, y colaborar con sus otras clases hermanas, generando así una alta cohesión entre estas en nuestro código.

Cuando definimos la responsabilidad de una clase o clases que colaboran entre sí, podríamos imaginarnos por ejemplo, a un equipo de rodaje, donde existen varías personas dedicadas cada una a un factor específico en la creación de una película, como pueden ser actores, maquillaje, iluminación, guionistas … etc. Cada individuo de los antes mencionados están especializados en distintas áreas y su tarea en el ámbito del cine se encuentra claramente definida, es más, todos colaboran entre sí, sin involucrarse en la tarea del otro, tan solo se “hablan” para solicitarse tareas o modificaciones pero nunca se inmiscuyen en el cómo deberían hacerlas. Lo mismo sucede con nuestras clases en el código, cada una de nuestras clases deben de colaborar entre sí, invocando los métodos o funciones que cada una ofrecen pero nunca sobrepasando la información sensible que cada una conoce y debe manejar a la perfección.

No existe una ciencia exacta a niveles de tamaño de una clase para cerciorarnos cuando una clase está haciendo más tareas de las que debería hacer. Creo que en éstas situaciones es el propio contexto el que habla por sí mismo y te va dando pistas como número de propiedades que maneja, número de parámetros que necesita por función, tamaño del constructor….etc. En éste sentido creo que es la experiencia y el propio instinto como programador lo que te hará mejorar éste aspecto.

Alta Cohesión

La cohesión a nivel de clases se refiere a cómo estas colaboran entre sí y pertenecen al mismo contexto. Es por esto que las clases relacionadas deberían de estar “cerca” unas de otras, con ésto mantenemos una alta cohesión en nuestro código, y cuando hablamos de cercanía nos referimos claramente a las dependencias que pudieran haber entre dichas clases, no solo por posición en el código de estas. La principal ventaja de lograr esto es que nuestro código sera mantenible a lo largo del tiempo y también este sera muy fácil de testear, debido a que se subdividirá en módulos que colaboran entre sí.

Es por esto que un número alto de dependencias en nuestras clases, siempre y cuando estas dependencias se encuentren estructuralmente bien relacionadas, deriva en que disponemos de un modulo altamente cohesionado, donde sus límites y acciones se encuentran claramente definidos y éste podría actuar como una única unidad de código.

Bajo Acoplamiento

Por contrapartida nos encontramos con la técnica del bajo acoplamiento, que nos viene ha decir que nuestros módulos o grupos de clases deben de ser todo lo independientes que puedan de otras clases o módulos, ya que con ello estaríamos evitando un impacto mayor a la hora de modificar el código de una de las partes y que la otra no se vea afectada. Un alto acoplamiento acarrearía el conocido “efecto mariposa”, donde un pequeño cambio en una clase, puede ocasionar la destrucción en otras partes que ha simple vista no parecerían afectar, con lo que nos llevaría a navegar por TODO nuestro código adaptando los cambios pertinentes para que todo funcione como antes, o parecido al menos…

Para evitar esto lo que tenemos que hacer es que ambas partes no se conozcan demasiado, usando para ello algunas herramientas propias de los programadores como puede ser el uso de Interfaces, junto con algún contenedor de inyección de dependencias(DI); o también nos podriamos servir de otros patrones como el patrón mediador… etc. Al final existen varias formas para desacoplar dos contextos de código. Tan solo queda la astucia del programador en cuestión para darle solución a éste problema.

En definitiva si seguimos los tres pasos vistos en el uso de clases, Principio de Responsabilidad Única, Alta Cohesión y Bajo Acoplamiento, lo que estamos formando son islas del conocimiento en nuestro código, lo que en DDD (Domain Driven Design) se conoce como Bounded Contexts, grupos de entidades con lógica de negocio interna muy clara y bien definida que no se sobresale de sus límites.

Conclusión

Con ésta publicación doy por finalizado este grupo de artículos dedicado al uso de buenas prácticas a nivel de código, llegando hasta al más minúsculo detalle por simple que parezca, que al final es lo que nos llevará a la excelencia o el éxito en nuestra tarea como programadores. Por concluir aclararía que nunca se puede ser purista en estas cosas, y tampoco hay que obsesionarse si un compañero crees que no ha nombrado bien sus variables o añadido demasiados comentarios.

Las buenas prácticas esta bien conocerlas y saber manejarse entre ellas, son otra herramienta más para nosotros los desarrolladores, pero cualquiera que lleve algún tiempo programando sabrá que los problemas llevados a un entorno real, muchas veces tendremos que sacrificar ciertas normas base para que la aplicación o el sistema funcione, pero no por ello significa que lo estemos haciendo mal, significa que nuestra aplicación tiene aspectos a mejorar, y eso lo único que debe hacer es entusiasmarnos.

Gracias a todos. Espero que os haya gustado este repaso de buenas prácticas, y feliz año nuevo!!