Object Calisthenics – Envolver todos los primitivos y Strings en clases.
Bueno, pues esta entrada os la debía, y vamos a por ella. Intentaré que sea indoloro.
Vamos a empezar por definir que es un tipo de dato primitivo, y así seguro que todos entramos en contexto. Un tipo de dato primitivo, o también llamado tipo de dato elemental es todo aquel que nos proporciona el lenguaje de forma natural y con los que podemos construir estructuras de datos, un claro ejemplo en C#, sería un tipo string, int, char, booleano, float.. etc. Con este tipo de datos, podemos construir Clases, Estructuras, Dtos, Records o lo que se nos tercie.
Bien, una vez que tenemos todos claro que son los tipos de datos primitivos y por qué lo son, pasemos al tema principal, y es que la tercera regla de Object Calisthenics nos dice que hemos de envolver todos los datos de tipo primitivo y vamos a ver por qué es aconsejable, y si, digo aconsejable, ya que no siempre es posible, o beneficioso realizar esta práctica.
A diferencia de cuando os decía que nunca debéis usar el Else, esta vez, en la medida de lo posible trataremos de envolver o encapsular los tipos primitivos.
Volvamos al ejemplo de la clase robot que veíamos en la entrada de object-calisthenics-no-usar-la-palabra-clave-else, en dicha clase recordad que teníamos un método Avanzar(), el cual recibía por parámetro un tipo primitivo int pasos, y este lo validábamos para que no fuese menor que cero, y si lo era devolvíamos una excepción, ya que no podía retroceder, hasta ahí todo bien.
Pues ahora imaginemos que en nuestro negocio el Robot solo puede avanzar un máximo de 100 pasos, y solo puede hacerlo si los pasos pasados son pares, o sea solo podría avanzar 2,4,6,8,10…100 pasos. Aquí ya estaríamos trabajando con tres validaciones diferentes:
- Que pasos no sea menor que cero.
- Que pasos sea par.
- Que pasos no sea mayor que 100.
Pues implementemos las validaciones en el método Avanzar():
public class Robot
{
private int Position = 0;
public int Avanzar(int pasos)
{
if (pasos < 0 || pasos % 2 != 0 || pasos > 100)
{
throw new InvalidPositionException("Posición no válida");
}
return Position += pasos;
}
}
Lenguaje del código: PHP (php)
Sencillo, ¿ no ?, hemos concatenado en el if dos validaciones más para que pasos no sea impar, y no sea mayor que 100, como siempre decimos, esto es funcional, no hay ninguno tipo de problema y los pasos del Robot quedarán validados tal y como queríamos.
Pasa el tiempo el Robot sigue con sus andanzas por el mundo y un montón de aventuras que le suceden (que no os voy a contar), y un día nos proponen una nueva funcionalidad, !el robot ahora puede retroceder!, y las validaciones han de ser las mismas, vaya que bien… pasemos a implementarlo, no hay problema, creamos un método Retroceder() y ya está listo:
public class Robot
{
private int Position = 0;
public int Avanzar(int pasos)
{
if (pasos < 0 || pasos % 2 != 0 || pasos > 100)
{
throw new InvalidPositionException("Posición no válida");
}
return Position += pasos;
}
public int Retroceder(int pasos)
{
if (pasos < 0 || pasos % 2 != 0 || pasos > 100)
{
throw new InvalidPositionException("Posición no válida");
}
return Position -= pasos;
}
}
Lenguaje del código: PHP (php)
¡Uy! Estas validaciones que hemos introducido en Retroceder() me suenan mucho a las de Avanzar().
¡Pero si son las mismas!
¿Podría refactorizar?
Bueno pues mejor nos deberíamos esperar a que se cumpla la regla del tres, o mejor.. ¿por qué voy a esperar?
Vamos a encapsular ese tipo primitivo que está ahí, que no me gusta nada y veamos como queda y las ventajas que nos ofrece:
public class Robot
{
private int Position = 0;
public int Avanzar(Pasos pasos)
{
return Position += pasos.Avances;
}
public int Retroceder(Pasos pasos)
{
return Position -= pasos.Avances;
}
}
public class Pasos
{
public int Avances { get; }
private Pasos(int avances)
{
Avances = avances;
}
public static Pasos Create(int avances)
{
if (AreInValidAvances(avances))
{
throw new InvalidPositionException("Posición no válida");
}
return new Pasos(avances);
}
private static bool AreInValidAvances(int avances)
{
return avances < 0 || avances % 2 != 0 || avances > 100;
}
}
Lenguaje del código: PHP (php)
Pues ya hemos encapsulado, como vemos en el ejemplo de arriba, hemos sustituido el tipo primitivo por un objeto Pasos, el cual en su método Create() implementa sus validaciones llamando a un método AreInvalidAvances() que valida si los avances pasados son o no permitidos por nuestra lógica de negocio, si no lo son lanzarán una excepción que podremos capturar.
No solo hemos refactorizado el código y ya no tenemos que repetirlo dos veces en dos métodos diferentes, si no que le hemos pasado la responsabilidad de validar los avances a quien realmente tiene que tenerla, es responsabilidad de la clase Pasos validar los avances que vaya a realizar el Robot.
Semánticamente esto es mas correcto, de este modo evitamos crear dos Code Smells muy famosos, uno es Shotgun Surgery (del cual también hablaré), y el otro Primitive Obsession, y conseguimos no tener las mismas validaciones repartidas por todo nuestro código a lo largo de toda la aplicación.
Este tipo de prácticas nos obligan a implementar buenos hábitos en la Programación Orientada a Objetos, creando un código de mayor calidad, mas legible y mas limpio.
¿Qué os ha parecido? !Os leo en comentarios!