Refactoring: Safe Unwrap

0
4350

En este tutorial aprenderemos una técnica de refactorización para borrar una clase Wrapper sin errores de compilación ni errores en los tests.

Índice de contenidos

1. Introducción

Hace unos días tuvimos la oportunidad aquí en Autentia de tener con nosotros a Carlos Blé, autor del libro Diseño Ágil con TDD. Podéis visitar en el siguiente link su página web www.carlosble.com. En el taller de refactoring nos enseñó técnicas que nos permiten deshacernos de la dependencia que tenemos con el compilador para ver los errores que aparecen después de realizar un cambio. Estas técnicas son a priori difíciles de entender, por lo que recomiendo paciencia y tratar de entender lo que se pretende realizar antes de nada.

2. Entorno

Para este tutorial he usado el siguiente entorno:

  • Hardware: Portátil MacBook Pro 17′ (CPU Intel Core 2 Duo 3Ghz, 8GB DDR3, GPU NVIDIA GeForce 9400M 256 MB).
  • Sistema Operativo: Mac OS El Capitán 10.11
  • Entorno de desarrollo: IntelliJ Idea 2016.1
  • Gradle 2.13

3. Conceptos básicos

Antes de comenzar con el código del ejemplo, voy a explicar las técnicas de refactoring que vamos a usar. Todos los ejemplos que aparecen a continuación de las técnicas de refactoring están sacados de esta increíble web creada por Martin Fowler

Inline method

Realizar un inline method es poner el cuerpo de un método dentro del cuerpo de sus llamadores y removerlo de la clase en la que estaba definido. Es exactamente lo contrario que un Extract method.

Captura de pantalla 2016-04-29 a las 13.15.17

Pull up field/method

Llamado Pull members up en IntelliJ. Lo que hace este refactor es «subir» un atributo o método al padre de una clase. En otras palabras, mueve un atributo o método de una clase a la clase superior de la que hereda.

Captura de pantalla 2016-05-03 a las 14.54.09

Replace Constructor with Factory Method

El nombre es autoexplicativo, reemplaza el constructor de una clase por una factoría.

Captura de pantalla 2016-04-29 a las 13.28.02

Use interface where possible

Reemplaza todas las instancias de una clase por instancias de la superclase o interfaz donde sea posible. El siguiente ejemplo, al ser un refactor más moderno, no está añadido en la web de Martin Fowler. He sacado el ejemplo de la web de IntelliJ, el cual es menos intuitivo que los otros, aunque explica el resultado que obtenemos del refactor.

Captura de pantalla 2016-04-29 a las 13.53.48

4. Ejemplo en C#

El ejemplo en C# lo muestra en un vídeo Carlos Blé, el cual dejo a continuación.

 

5. Ejemplo en Java

Para el siguiente ejemplo usaré el material que Carlos nos proporciono, a continuación el código

Model
public class Model {
    private String Color;
    private int Amount;

    public Model(String color, int amount) {
        Color = color;
        Amount = amount;
    }

    public String getColor() {
        return Color;
    }

    public int getAmount() {
        return Amount;
    }
}
Wrapper
public class Wrapper {
    private Model Wrapped;

    public Wrapper(Model wrapped) {

        Wrapped = wrapped;
    }

    public Model getWrapped() {

        return Wrapped;
    }
}
Consumer
public class Consumer {

    public String getColor(Model model){
        Wrapper wrapper = new Wrapper(model);

        return wrapper.getWrapped().getColor();
    }
}
AnotherConsumer
public class AnotherConsumer {

    public int getAmount(Model model){
        Wrapper wrapper = new Wrapper(model);

        return wrapper.getWrapped().getAmount();
    }
}

En este ejemplo la clase a eliminar es, como su propio nombre indica, la clase Wrapper. Se quiere eliminar para quitar de las clases Consumer y AnotherConsumer las llamadas que violan la Ley de Demeter: wrapper.getWrapped().getColor() y wrapper.getWrapped().getAmount(). Esto mejorará el entendimiento del código y su mantenimiento.

Primer paso: Elaborar los test

Como habréis notado, el ejemplo no contiene ninguna clase de test, asi que necesitamos crearlos. A continuación dejo el código de los que yo he realizado, aunque os animo a crearlos por vosotros mismos.

import org.junit.Test;
import src.safeunwrap.Consumer;
import src.safeunwrap.Model;

import static org.junit.Assert.assertEquals;

public class ConsumerTest {

    private Consumer consumer;
    private Model model;

    @Test
    public void shouldReturnColorCallingConsumerGetColor(){

        consumer =  new Consumer();
        String color = "Green";
        int amount = 2;
        model = new Model(color, amount);
        assertEquals(color,consumer.getColor(model));

    }

}
import org.junit.Test;
import src.safeunwrap.AnotherConsumer;
import src.safeunwrap.Model;

import static org.junit.Assert.assertEquals;

public class AnotherConsumerTest {

    private AnotherConsumer anotherConsumer;
    private String color;
    private int amount;
    private Model model;

    @Test
    public void shouldReturnAmountCallingAnotherConsumerGetAmount(){

        anotherConsumer = new AnotherConsumer();
        color = "Red";
        amount = 5;
        model = new Model(color, amount);
        assertEquals(amount,anotherConsumer.getAmount(model));

    }
}

 

Segundo paso: Extender de Model

La clase Wrapper debe heredar de la clase Model. Esto nos dará un error en el constructor debido a que la clase Model no contiene un constructor por defecto. Si queremos que los test no den error solo tenemos que añadir el super-constructor. Una vez hecho, si pasamos los test no nos dará ningún problema.

Captura de pantalla 2016-05-03 a las 17.44.06

 

Tercer paso: Getter return this

En este momento necesitamos entender que el getter de la clase Wrapper devuelve un Model. Por tanto, si Wrapper extiende de Model, podemos hacer que el getter devuelva this y, por polimorfismo, el resultado del return sea un Model.

Captura de pantalla 2016-05-04 a las 9.15.50

 

Cuarto paso: Inline Getter

Como el getter devuelve this y por polimorfismo se trata como un return de una instancia de Model, podemos hacer un inline para que en las clases en las que se usa el getter este se sustituya por una instancia de Wrapper. El getter desaparecerá mediante este refactor y podremos observar el cambio en las clases Consumer y AnotherConsumer.

Wrapper

Captura de pantalla 2016-05-04 a las 9.21.33

 

Consumer y AnotherConsumer

El inline ha eliminado el getWrapped() dejando la instancia de Wrapper con una sola llamada, con lo que ya cumplimos la Ley de Demeter:

Captura de pantalla 2016-05-04 a las 9.30.42

Captura de pantalla 2016-05-04 a las 9.27.37

 

Quinto paso: Replace constructor with factory

Ya hemos cumplido con la mitad del trabajo, hemos eliminado esa horrible cadena de métodos mejorando mucho el entendimiento del código. Ahora vamos a eliminar la clase Wrapper. Para ello lo primero que necesitamos es sustituir el constructor por una factoría. Este es probablemente el refactor más sencillo de entender de los que hay en este tutorial, por lo que no es necesario explicar nada más.

Wrapper

Captura de pantalla 2016-05-04 a las 9.41.07

 

Consumer y AnotherConsumer

Captura de pantalla 2016-05-04 a las 9.43.19

 

Captura de pantalla 2016-05-04 a las 9.43.32

 

Sexto paso: Use interface

Ahora, debemos usar el refactor Use interface where possible, el cual puede ser el más difícil de entender. Vayamos paso a paso. Primero, seleccionamos la factoría y elegimos el refactor. Nos saldrá una ventanita en la que nos pedirá seleccionar qué clase es la que debe utilizar. Elegimos Model:

Captura de pantalla 2016-05-04 a las 9.49.27

 

En la siguiente ventana se nos muestran las instancias de Wrapper que van a ser sustituidas por instancias de Model. Si seleccionamos el checkbox nos cambiará el nombre de la variable (wrapper -> model). Esto nos dará un error ya que el parámetro de entrada de getAmount(Model model) y de getColor(Model model) se llama igual. Por tanto si queremos que renombre las variables, debemos escribirle en lugar de model otro nombre. Yo he usado modelUnwrap

Captura de pantalla 2016-05-04 a las 9.58.44

 

El resultado es el siguiente:

Wrapper

Captura de pantalla 2016-05-04 a las 10.15.51

 

Ahora la factoría devuelve Model el lugar de Wrapper. Como ya he nombrado a lo largo del tutorial, la instancia de Wrapper por polimorfismo es tratada también como Model, ya que hereda de dicha clase.

 

Consumer y AnotherConsumer

Captura de pantalla 2016-05-04 a las 10.16.16

 

Captura de pantalla 2016-05-04 a las 10.16.32

 

Séptimo paso: Inline factory

Ahora debemos parar un segundo a entender qué es lo que hace el factory en este instante:

public static Model createWrapper(Model wrapped) {
     return new Wrapper(wrapped);
}

Recoge por parámetro un Model y devuelve el mismo Model pero «Wrappeado». Esto podemos eliminarlo en dos pasos. Primero, sustituimos new Wrapper(wrapped) por wrapped. Esto deja la factoría sin sentido, ya que recoge un Model y devuelve el mismo Model.

Captura de pantalla 2016-05-04 a las 10.37.56

 

Segundo, realizamos un inline del factory, con el que sustituremos todas las llamadas de createWrapper por la instancia del objeto pasado por parámetro, wrapped. El resultado es el siguiente:

Wrapper

Captura de pantalla 2016-05-04 a las 10.47.01

 

Consumer y AnotherConsumer

Captura de pantalla 2016-05-04 a las 10.48.01

 

Captura de pantalla 2016-05-04 a las 10.48.51

 

Como podemos comprobar, las clases Consumer y AnotherConsumer ya no usan la clase Wrapper y, de hecho, el propio IntelliJ nos avisa de ello. Por último solo quedaría hacer un safe delete de la clase Wrapper y eliminar la variable modelUnwrap sustituyendo esta por el model que entra por parámetro (como es evidente). En este momento, si has entendido bien los refactor te habrás dado cuenta de que puedes hacer un inline de modelUnwrap, lo que nos hará todo automático

Wrapper

Captura de pantalla 2016-05-04 a las 10.53.27

 

Consumer y AnotherConsumer

Captura de pantalla 2016-05-04 a las 11.16.28

 

Captura de pantalla 2016-05-04 a las 11.16.42

 

Demo en vídeo

Para acabar, me gustaría poner este vídeo donde hago todos los pasos antes nombrados. Antes de verlo, por favor, asegúrate de que has leído todo lo anterior. El objetivo de este tutorial no es replicar los pasos, si no entender lo que se pretende hacer.

https://youtu.be/4QauQXT4XxU[/embed]

La canción de fondo es Foria – Break Away y está sacada de NoCopyrightSounds. Podéis encontrar más sobre Foria en el siguiente link: Foria

6. Conclusiones

Es probable que alguien piense que el resultado sería el mismo que borrando la clase desde el principio y arreglar lo que falle, es cierto, el resultado es el mismo. El problema está cuando tienes que arreglar mil clases distintas y tener que ir una por una sin poder asegurarnos de que todo funciona hasta que no terminemos. Y ahora alguien puede pensar, al igual que hice yo, «entonces esto solo es para proyectos grandes». En realidad me he dado cuenta de que es más útil en proyectos enormes, pero es recomendable hacerlo siempre.

Aquí entra en juego el ser un buen programador, es decir, tener una disciplina al igual que en muchas otras profesiones las tienen. Yo a un obrero que para arreglar una pequeña grieta en una pared, tira la casa entera y vuelve a construirla, no le veo mucho futuro como profesional. Igual pasa con un informático que rompe todo para levantarlo de nuevo.

El mundo del refactoring es muy amplio y, además, se está expandiendo. Por eso, como profesionales debemos aprender a refactorizar y a hacer bien las cosas. Una vez lo hayamos hecho, descubriremos todas las ventajas que nos aportan estas herramientas.

7. Agradecimientos

Quería agradecer a Carlos Blé su increíble taller de refactoring y por permitirme hacer este tutorial. También a Esther Lozano Hontecillas por ayudarme a entender los conceptos más importantes.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad