Evita primitive obsession

En la capa de lógica de nuestra aplicación se encuentra el código más personal del programador, ese código que no necesita basarse en primitivos para cumplir tipos de datos en transferencias porque es otra capa quien lo hará por ésta.

Anteriormente he definido “el código más personal del trabajador”, y me refiero a que la estructura y legibilidad del código en ésta capa depende de como se implemente. Si no se nos exigen primitivos, podemos crear nuestros propios tipos para que cuando se lea el código mejoremos la expresividad y legibilidad, en una capa como la que menciono en la que se encuentra el núcleo de la aplicación y a su vez el código que más desarrollo y razonamiento necesita.

Con tus propios tipos se te abre un mundo de posibilidades para simplificar tu código. En el siguiente ejemplo tenemos una clase que utiliza un String como un texto, y verifica que cuando sea null retorne un String vacío.

public String wordWrap(String text, int columnWidth){
      if (text==null){
          return "";
      }
}

Esta clase no debería estar haciendo éstas comprobaciones, su misión es única, solo debería implementar la función para la que fue pensada, por lo que refactorizando lo anterior podemos crear nuestro propio tipo delegando la construcción a un método de factoría que llamará al constructor y que cuando reciba un null lo transforme a un String vacío. El porqué usar un método de factoría en lugar del constructor como tal se debe a que un constructor solo debería realizar lo que su nombre indica, construir un objeto, por lo que cualquier otro tipo de implementación dentro de éste solo conseguiría empeorar la legibilidad en nuestro código.

/*Texto.java*/
public class Texto{
    private String texto;

    private Texto(String texto) 
    {this.texto=texto;}

    public static Texto crearTexto(String 
    texto){
    if (texto==null){texto="";}
    return new Texto(texto);
    }
}

/*application.java*/
public String wordWrap(Texto texto, int columnWidth){
      

}

Listo, la clase en cuestión ya solo tiene que preocuparse de implementar su funcionalidad, ya que hemos llevado el String inicial a nuestro propio tipo “Texto”, el cuál tiene un método para realizar la anterior comprobación, y así la podemos extraer de “application,java”.

Hemos creado un Value Object, lo que quiere decir que su estado no mutará a lo largo del tiempo no como si lo hace una entidad. Como ya hemos visto, este tipo de objetos nos permiten volver nuestro código más legible y bonito, logrando así que no solo nosotros lo entendamos perfectamente, si no que también lo haga una persona que no desarrolló dicho código.

Cuando creamos nuestro propios tipos, debemos tener claro que cada clase debe tener una responsabilidad. Una clase que utiliza métodos de otra, debería estar accediendo a ésta para obtener datos, no para suplantar la identidad y desarrollar un comportamiento que no debería ser suyo. Estamos hablando de un code smell al que se le denomina Feature Envy. Para verlo más claro, en el siguiente ejemplo:

public class Phone {
    private final String unformattedNumber;
    public Phone(String unformattedNumber) {
        this.unformattedNumber = unformattedNumber;
    }
    public String getAreaCode() {
        return unformattedNumber.substring(0,3);
    }
    public String getPrefix() {
        return unformattedNumber.substring(3,6);
    }
    public String getNumber() {
        return unformattedNumber.substring(6,10);
    }
}

public class Customer...
    private Phone mobilePhone;
    public String getMobilePhoneNumber() {
        return "(" + 
            mobilePhone.getAreaCode() + ") " +
            mobilePhone.getPrefix() + "-" +
            mobilePhone.getNumber();
    }

tenemos una clase “Phone” que tiene como propiedad un número de teléfono (String), y mediante métodos se puede acceder a dicha propiedad y extraer el “areacode”, “prefix” y “number”. Por otro lado, la clase “Customer”, que accede a clase anterior para conseguir un número de teléfono (String) accediendo a los métodos de ésta para implementar su propia función de formatear un número de teléfono a un estado más legible.

Es un código funcional, pero ¿no debería ser la primera clase (Phone) la que desarrolle dicha funcionalidad?, al fin y al cabo es la clase que sabe operar con números de teléfono, por lo que la segunda clase (Customer) no debería saber hacer eso ya que su comportamiento es otro. Si arreglamos un poco lo anterior nos queda algo así:

public class Phone {
    private final String unformattedNumber;
    public Phone(String unformattedNumber) {
        this.unformattedNumber = unformattedNumber;
    }
    
    public String getMobilePhoneNumber() {
        return "(" + 
            getAreaCode() + ") " +
            getPrefix() + "-" +
            getNumber();
    } 

    public String getAreaCode() {
        return unformattedNumber.substring(0,3);
    }
    public String getPrefix() {
        return unformattedNumber.substring(3,6);
    }
    public String getNumber() {
        return unformattedNumber.substring(6,10);
    }
}

public class Customer...
    private Phone mobilePhone;
    private String emailAddress;
    
    public String dataOfCustomer(){
        return mobilePhone.getMobilePhoneNumber() + emailAddress;
    }

Se implementó lo anterior en la primera clase (Phone), por lo que ahora la segunda (Customer) accede a éste método para obtener lo que desea y hemos conseguido que cada clase cumpla con su misión sin que ninguna se entrometa en el funcionamiento de otro, y no intente implementar cosas que no debe conocer

Creado con Hugo
Tema Stack diseñado por Jimmy