movercampos

Refactoring / Refactorización

1 Refactorización.

Todo ello sin realizar cambios externos (el comportamiento del software deberá ser siempre el mismo).

Para refactorizar un software habrá que realizar una serie de cambios internos, reestructurando componentes siempre teniendo en cuenta que su comportamiento debe permanecer inalterado.

Ventajas de refactorizar:

  • Te ayudará a encontrar errores de una forma más rápida y sencilla. Muchas veces una estrategia equivocada al programar implica a la larga errores y fallos internos.
  • Te ayudará a programar más rápido. Seguramente al encontrar menos errores perderás menos tiempo en corregir y arreglar código.
  • Tus programas se leerán e interpretarán de una manera más fácil.
  • Tus diseños serán más robustos.
  • Tus programas tendrán más calidad.
  • Evitarás duplicar la lógica de tus programas.

Patrones de refactorización más usuales.

Veamos formas de refactorización con ejemplos concretos:

Extract Method o reducción lógica

extractmethod

Este tipo de refactorización es una de las más sencillas y típicas. Cuando escribas código lo que tienes que crear son métodos que hagan lo que dicen. Muchas veces los métodos cortos tienen muchas ventajas, primero que realizan tareas específicas concretas (con lo cual pueden ser invocados por otros métodos de forma más sencilla) y segundo que permiten ir creando otros métodos con un nivel de complejidad mayor.

Refactoring y Netbeans.

refactoringNetbeans

Netbeans viene equipado con herramientas de refactoring que van a permitir reestructurar tu código sin cargárselo. Netbeans tiene una herramienta de inspeccionar y transformar que permite arreglar y mejorar el código de una manera transparente.

Además pulsando botón derecho tienes la opción de Refactor y ahí muchas subopciones para mejorar tu código extrayendo interfaces, superclases, encapsulando campos, etc.

Por ejemplo, si tenemos el siguiente campo:

private equipo miequipo;

Y utilizamos la opción encapsular campo, refactor nos creará los siguientes métodos:
/**
* @return the miequipo
*/
public equipo getMiequipo() {
return miequipo;
}

/**
* @param miequipo the miequipo to set
*/
public void setMiequipo(equipo miequipo) {
this.miequipo = miequipo;
}

Luego crea los setters y getters para los campos que yo haya seleccionado ahorrando tiempo y mejorando la aplicación.

Métodos Inline / código embebido

metodoinline

Si lo que se va buscando es el escribir métodos y código que muestre la intención del código muchas veces ocultar la lógica del programa factorizando el código puede ser mala idea. En el ejemplo anterior se puede ver que el código queda más simple y fácil de entender eliminando la función puesto que se reduce la complejidad del mismo y es más legible.

De hecho no debería de utilizarse este tipo de formas de programar en Java cuando hay métodos que sobrecargan métodos de clases base.

Otro ejemplo de código embebido que habría que evitar es el siguiente:

inlinemethod2

En este caso utilizamos una variable temporal solamente para realizar la comparación. Si la variable solamente se utiliza para realizar dicha comparativa una forma más elegante y que evita problemas sería el utilizar la comparación directa a la hora de realizar el return.

Truco.
Evita utilizar variables temporales para almacenar resultados intermedios de las operaciones.

inline3

Ahora queremos realizar un descuento al cliente. Si la cuenta supera una determinada cantidad pagará un 90 (descuento de un 10%) en caso contrario el descuento solamente será de un 5%.

En el ejemplo anterior vemos que utilizamos una variable temporal para calcular el precio base de una cuenta. Muchas veces en vez de utilizar una variable temporal es mejor utilizar un nuevo método con un nombre suficientemente explicativo para determinar el precio base. Este método podrá ser utilizado por otros método de la clase con lo cual la utilidad del mismo se incrementaría.

Imagina que tienes o quieres utilizar una variable sin tener que crear un método para factorizarla. En ese caso se recomienda utilizar variables final. Utilizar una variable de tipo final nos permite asignar un valor a la misma y previene de una doble instanciación porque el compilador nos va a mostrar el error. Por ejemplo el siguiente código mostrará un error:

variablefinal

Con lo cual nuestro método podría ser redefinido de la siguiente forma:

funcionconfinal

Variables autoexplicativas

Muchas veces los programadores nos obcecamos en escribir complejas líneas de código cuando tenemos recursos para hacer el código más legible. Por ejemplo, es fácil encontrar sentencias de código como la siguiente:

variablesexplicativas

Cuando utilizando variables autoexplicativas correctamente definidas (obsérvese que se han definido como tipo final) el código resultaría mucho más legible y sencillo.

variablesexplicativas2

Mal uso de variables temporales

Imaginemos que tenemos el siguiente código:

double tmp = pi * radio * radio;
System.out.println (tmp);
tmp = 2 * pi * radio;
System.out.println (tmp);

En realidad nuestro código debería haber tenido este aspecto:

final double area = pi * radio * radio;
System.out.println (area);
final double perimetro = 2 * pi * radio;
System.out.println (perimetro);

¿Por qué hacemos esto?
porque una variable temporal generalmente se utilizan para ser instanciadas o establecidas solamente una vez y en caso que tenga que cambiarse su valor varias veces durante la ejecución del método hay que plantearse el crear una segunda variable temporal. En el caso anterior se ve claramente que habría que utilizar dos variables temporales en vez de una.
Además, para evitar la doble instanciación se declaran las variables como final.

Cuidado con los parámetros.

Imagina que tienes el siguiente código en tu programa (las horas extras computan 1.5 la hora trabajada):

int salario(int horas, int horasextra, int salariobase){
horas = horas + horasextra * 1.5;
……
}

En realidad nuestro código debería haber tenido este aspecto:

int salario(int horas, int horasextra, int salariobase){
final int horastrabajadas = horas + horasextra * 1.5;
……
}

¿Por qué hacemos esto?
Porque un parámetro no debería ser modificado salvo que haya una razón para ello. Generalmente de los parámetros nos suele interesar su valor y no se desea que cambie el mismo una vez se haya invocado el método.
En el caso anterior el valor del parámetro horas no tiene sentido que sea modificado.
Salvo razón obvia, mejor no cambiar el valor de los parámetros durante la ejecución de un método.

Cambiando algoritmos.

Siempre hay una manera más fácil de hacer una cosa. Cuando creas que hay una mejor manera de realizar algo sustituye. La factorización trata de simplificar cosas más complejas en otras más sencillas y fáciles de entender.

Siempre que puedas utilizar o crear una librería hazlo.

En el caso que tengas que sustituir un algoritmo trata de hacerlo de tal manera que funcione igual o mejor que antes. Las pruebas de regresión te pueden ir bien para comparar resultados de algoritmo antiguo y nuevo.

Imaginemos que tenemos el siguiente código:

mejoraalgoritmo1

El resultado de una refactorización que mejore la legibilidad y eficiencia del código anterior sería la siguiente:

mejoraalgoritmo2

Moviendo métodos entre clases.

Muchas veces tenemos clases complejas y otras clases que realizan pocas tareas. Cuando tengamos en un esquema de diseño unas clases sobreexplotadas con un montón de métodos y estados internos y otras más ligeras tenemos que replantearnos repartir la carga de trabajo. Hay algo que no funciona. Repartir funciones es una de la mejor manera.

Imagina que tenemos un esquema de clases como el que ves a continuación (ojo se ha simplificado al máximo):

movermetodos

En ocasiones es necesario repartir carga de trabajo y mudar métodos entre clases para que la carga sea más homogenea en todo el esquema.

Veamos un ejemplo de cómo mudar un método de una clase a otra. Tenemos la clase tipoEmpleado:
tipoempleado1

Y también la clase empleado:

empleado1

De las clases anteriores puedes deducir que la clase empleado crecerá hasta hacerse muy voluminosa mientras que la clase tipoempleado será siendo muy simple. La mejor forma de hacer tus programas más legibles y fáciles de mantener es factorizar y racionalizar funciones. El resultado de esta racionalización sería el siguiente (clase tipoEmpleado):

tipoempleado2

Ahora la clase empleado:

empleado2

Ahora la clase empleado podrá crecer y el esquema de clases será más proporcional.

¿Quieres mejorar tu código sin alterar la funcionalidad del mismo?
En en el siguiente link tienes muchos modelos de refactorización que puedes utilizar según necesidad.

Moviendo variables miembro entre clases.

El mover métodos y campos es la base de la refactorización. Imagina que en el ejemplo anterior, el precio de la hora base no depende del tipo de empleado y va a ser utilizado por muchos métodos de la clase empleado. En vez de tenerlo en la clase tipoEmpleado seguramente sea mejor embeberla en la clase empleado.

El resultado sería tener en la clase empleado otra variable más y eliminar dicha variable de la clase tipoEmpleado.

movercampos

Autoencapsular campos (getters y setters).

Una buena práctica cuando una variable va a ser utilizada de forma asidua es autoencapsularla en la clase creando sus getters y setters.

En nuestra clase tipoEmpleado el resultado sería el siguiente:

autoencapsular

Extraer clases.

No crees que la variable miembro horabase en la clase tipoEmpleado debería ser de tipo final.
Razona tu respuesta.

¿Tienes en una clase código que hace más que lo que debería?

Si tienes en una clase mucho código es que no has diseñado correctamente tu esquema de clases. Por ejemplo veamos la siguiente clase:

clasegorda

Puedes adivinar que todavía habría que añadir más funcionalidad y el volumen de la misma ya es grande. Si continuamos añadiendo métodos, getters, setters, … nuestra clase crecerá hasta el infinito.

La solución es extraer parte de la funcionalidad en otra clase. De esa forma la clase será más fácil de mantener, ampliar y más modular.

Refactorización y pruebas.

Todos los desarrolladores en su trabajo diario crean nuevo código a la vez que van haciendo refactorización de dicho código y van creando casos de prueba con dicho código. El objetivo es claro: mejorar eficiencia, consistencia, claridad y mantenimiento futuro.

Si un proyecto va a sufrir una gran refactorización, lo mejor es que crees casos de prueba con JUnit u otra herramienta para asegurar que dicha refactorización no altera el comportamiento del código anterior.

Ten en cuenta que la factorización es simplificación: Es más fácil realizar (x+1)*(x-1) que x2-1.

Clases delegadas.

clasedelegada

Muchas veces tenemos esquemas de clases en los que no nos interesa hacer accesibles a cualquier clase una clase concreta. Imaginemos que tenemos el esquema de clases anterior. ¿Por qué una clase tiene que hacer uso de la clase equipo para conocer su capitán por ejemplo?

Muchas veces conocemos un jugador concreto y lo que queremos es conocer información sobre su equipo, capitán, lugar de entrenamiento, etc. Bastaría con ocultar esta información y hacerla accesible solamente a través de la clase jugador:

clasedelegada2

Veamos cómo se implementaría esta estructura de clases:

Primeramente veamos como quedaría la clase equipo:
equipo

Una vez que tenemos la clase equipo vamos a utilizar la clase jugador como una clase man in the middle para ocultar a las clases la clase equipo y que el acceso sea siempre bajo la clase jugador:

jugador

Imaginemos que queremos conocer el capitan de un equipo si sabemos el nombre de un jugador. El acceso sería el siguiente:

Capi = Benzema.getEquipo().getCapitan();

Ejercicio Drones.
Queremos realizar un proyecto en el cual se va a tomar una serie de fotos todas ellas tendrán una coordenada GPS.
La coordenada de una dirección comprende una longitud y una latitud. Si quieres trabajar con coordenadas GPS y experimentar puedes hacerlo en el siguiente link.
Dicha coordenada pertenece a un país determinado. En principio se van a mapear solamente países de Europa pero en un futuro el sistema estará abierto a todo el mundo.
Por lo tanto la foto tendrá asociada una coordenada, un país, un dron determinado y una descripción.
Los drones tendrán que darse de alta en el sistema con lo cual tendrán que tener un nombre o nick y especificaciones sobre su modelo, autonomía, año de fabricación, propietario, etc.
También los usuarios tendrán que estar registrados en el sistema.
Se quiere saber las fotos que realiza cada usuario en el sistema y también se querrá conocer las fotos por lugar (se indexarán en un google maps u herramienta parecida).
Se pide:
Diseñar e implementar cada una de las clases que creas convenientes utilizando criterios de refactorización. Cuando utilices algún criterio de refactorización tienes que dejarlo plasmado en el código mediante comentarios.

Foreign Methods o métodos foraneos

Muchas veces tienes una clase que hace todo lo que tu le pides y funciona a las mil maravilas pero necesitas algo más. Si modificas la clase temes alterar la misma solamente para añadirle un método que seguramente no utilices de forma habitual con lo cual te planteas otra alternativa.

La alternativa es un foreign method. Imagina que tienes este código:

Date diaSiguiente = new Date (fechafin.getAnio(),
fechafin.getMes(), fechafin.getDia() + 1);

La solución a tu problema seguramente sería la siguiente:

Date diaSiguiente = siguienteDia(fechafin);
private static Date siguienteDia(Date var) {
return new Date (var.getAnio(),var.getMes(), var.getDia() +
1);
}

Reemplazar valores con objetos.

Muchas veces al comienzo del diseño se toman decisiones que puede ser que eviten un crecimiento o desarrollo futuro de la aplicación.

extraerclase

En ocasiones tenemos datos que son tan simples que no tienen suficiente entidad para crear una clase. Pero cuando desarrollamos más la aplicación vemos que ese número de teléfono por ejemplo que creíamos que era tan simple necesitará formatearse de una manera determinada, extraer el prefijo, determinar si es móvil o fijo, etc.

Cuando veas que esto puede ocurrirte, modifica la clase y extrae el campo creando una nueva clase.

Imagina que tienes la clase jugador que es parecido a lo siguiente:
class jugador {
private String telefono;
.....

Al final tendrás una clase teléfono parecida a las siguiente:

telefono

¿Todavía sin utilizar constantes?

Muchas veces cuando el programador cree que solamente la va a utilizar una vez, utiliza un valor y se olvida de declarar una constante. Si quieres ser un programador limpio mejor utiliza constantes siempre que se pueda.

Cambia los métodos como este:

double descuento(double unidades, double preciounitario) {
return unidades * 0.95 * preciounitario;
}

Por esta solución más acertada:

double descuento(double unidades, double preciounitario) {
return unidades * DESCUENTO_DIRECTO * preciounitario;
}
static final double DESCUENTO_DIRECTO = 0.95;

Encapsular arrays

Los arrays son utilizados por muchos programadores, sobretodo por aquellos que no les gusta utilizar recursos más eficientes como las colecciones. Las colecciones generalmente ofrecen más versatilidad pero en caso de utilizar arrays, el encapsularlos puede darnos mucho más juego.

Veamos un ejemplo:

encapsulararray1

Una vez establecidos los getters y setters, en ocasiones necesitamos crear getters y setters específicos que utilicen copias de objetos para preservar los originales. Utilizaremos métodos parecidos a estos:

encapsulararray2

Con los bonitos que son los List.
Realiza una clase como la anterior pero utilizando internamente objetos de tipo List.
Añádele las funcionalidades que creas oportunas para hacerla más rica y útil.

Reemplazar tipos de objeto con subclases

registrosporclases

Muchas veces utilizamos variables internas para clasificar objetos. Este tipo de programación lo que implica es que dentro del código existan muchas sentencias condicionales ya sea de tipo if o de otro tipo. En nuestro caso, dependiendo del tipo de tenista el código tendrá que hacer una cosa u otra.

Este tipo de programación no tiene sentido en una POO, luego para refactorizar esta situación lo mejor es cambiar toda la estructura condicional con polimorfismo. Crearemos clases derivadas y mediante la herencia y el polimorfismo haremos un código más limpio y fácil de mantener.

Veamos el código fuente antes y después de la refactorización.

Código antes de la refactorización:

tenistaantesrefactorización

Veamos el código después de la refactorización. Lo primero es que hemos transformado el constructor en un factory method para crear objetos de subtipo tenista:

tenistatrasrefactor

Para encapsular los distintos subtipos de tenista hemos creado clases derivadas que heredan de la superclase tenista:

amateur

En la imagen anterior vemos la clase amateur pero habrá más clases derivadas como la clase profesional y semiprofesional.

Tenemos la siguiente clase persona:
abstract class persona {
abstract boolean esHombre();
abstract char getGenero();
}

Se pide crear las subclases hombre y mujer que implementen los métodos de la clase abstracta persona y también se cree necesario crear un factory method para crear los objetos de dicha subclase.
Implementa todo este código con la herramienta NetBeans.

Resumen
Nombre del artículo
Refactoring/refactorización
Autor
Descripción
La refactorización son los cambios que se le pueden realizar a un software para que sea más fácil de entender por los programadores y sea más fácil y menos costoso de modificar. En este artículo se ven los patrones de refactorización utilizados más usuales.

2 thoughts on “Refactoring / Refactorización

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>