Post on 25-Sep-2018
Refactorización - Refactoring
1
Refactoring
Tabla de contenidos Introducción: un ejemplo de refactoringPrincipios en refactoring
Definiciones y orígenes
¿Por qué y cuando refactorizar?
Problemas con las refactorizaciones
Misceláneo:
Herramientas
Metodologías ágiles
Definición de nuevas refactorizaciones (Big – Low)
Conclusiones
Bibliografía
Fowler, Refactoring: Improving the Design of Existing Code, Addison-
Wesley, 1999
Refactorización - Refactoring
2
Introducción
DefiniciónRefactoring son una serie de pequeñas transformaciones, cada uno de las cuales cambia la estructura interna del programa sin cambiar su
comportamiento externo.
Verificación del comportamiento externo
Testing – Prueba
Análisis formal de código dirigido por herramientas
Refactoring
Se parte de código existente y con el diseño subyacente en él
Limpia el código para minimizar el coste de los cambios
Mejora el diseño del código una vez que este ha sido escrito
Refactorización - Refactoring
3
Introducción: un ejemplo de refactoring
Contexto
Calculo e impresión de los alquileres en un Videoclub
El coste del alquiler depende del tiempo de alquiler y tipo de película.
Tipos de películas: regular, children, new-release.
El coste de alquiler también depende de los puntos de frecuencia en el alquiler que dependen del tipo de película.
priceCode: int
Movie
daysRented: int
Rental
statement()
Customer1
1
Refactorización - Refactoring
4
Class Movie
public class Movie {public static final int CHILDRENS = 2;public static final int REGULAR = 0;public static final int NEW_RELEASE = 1;
private String _title;private int _priceCode;
public Movie(String title, int priceCode) {_title = title;_priceCode = priceCode;
}
public int getPriceCode() {return _priceCode;
}
public void setPriceCode(int arg) {_priceCode = arg;
}public String getTitle () {
return _title; }
}
Refactorización - Refactoring
5
Class Rental
public class Rental {private Movie _movie;private int _daysRented;
public Rental(Movie movie, int daysRented) {_movie = movie;_daysRented = daysRented;
}public int getDaysRented() {
return _daysRented;}public Movie getMovie() {
return _movie;}
}
Refactorización - Refactoring
6
Class Customer
public class Customer {private String _name;private Vector _rentals = new Vector();
public Customer (String name) {_name = name;
};public void addRental(Rental arg) {
_rentals.addElement(arg);}public String getName () {
return _name;};public String statement()…
1
Refactorización - Refactoring
7
Customer.statement()
public String statement() {double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {double thisAmount = 0;Rental each = (Rental) rentals.nextElement();//determine amounts for each lineswitch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:thisAmount += 2;if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;break;
case Movie.NEW_RELEASE:thisAmount += each.getDaysRented() * 3;break;
case Movie.CHILDRENS:thisAmount += 1.5;if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;break;
}…
1
2
Refactorización - Refactoring
8
Customer.statement()
// add frequent renter pointsfrequentRenterPoints ++;// add bonus for a two day new release rentalif ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1)frequentRenterPoints ++;//show figures for this rentalresult += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(thisAmount) + "\n";totalAmount += thisAmount;
}//add footer linesresult += "Amount owed is " + String.valueOf(totalAmount) +
"\n";result += "You earned " + String.valueOf(frequentRenterPoints) +
" frequent renter points";return result;
}
2
Refactorización - Refactoring
9
Customer.statement()
aCustomer aRental aMovie
getMovie
* [for all rentals]
getPriceCode
getDaysRented
statement
Rental Record for Dinsdale Pirhana
Monty Python and the Holy Grail 3.5
Ran 2
Star Trek 27 6
Star Wars 3.2 3
Wallace and Gromit 6
Amount owed is 20.5
You earned 6 frequent renter points
Refactorización - Refactoring
10
Comentarios
El programa funciona perfectamenteSe observan problemas de diseño
Método excesivamente largo en la clase Client.statement()
Dificultan futuros cambios
Cambios en los requerimientos
Producir una versión de HTML para statement
Solución 1: copy & paste statement -> problemas de código duplicado
La clasificación de películas pronto cambiará, se desconoce la clasificación definitiva
Junto con las reglas para calcular el precio del alquiler y los puntos de alquileres frecuentes
Cuando haya que añadir una nueva característica al programa y el código no mantiene la estructura adecuada para añadir la nueva característica, primero
se refactoriza el programa y luego se añade la nueva característica.
Refactorización - Refactoring
11
Testear
Construir un conjunto sólido de test
Crear unos clientes
Dar a los clientes unos alquileres de varias clases de películas
Generar manualmente cadena resultantes del alquiler
Comparar las cadenas resultantes con las generadas manualmente
Localiza los posibles fallos (bugs) cuando se refactorizaUsar un simple framework para construir los Test
http://www.junit.org
Comprobar la ejecución correcta de todos los test
Ejecutar el suite de todos los test como parte del proceso de construcción.
Refactorización - Refactoring
12
Extract Method
Se dispone de un fragmento de código que puede ser agrupado junto. Crear un nuevo método con ese código, cuyo nombre
explique el propósito del método.
void printOwing() {printBanner();//print detailsSystem.out.println ("name: " + _name);System.out.println ("amount " + getOutstanding());
}
void printOwing() {printBanner();printDetails(getOutstanding());
}
void printDetails (double outstanding) {System.out.println ("name: " + _name);System.out.println ("amount " + outstanding);
}
Refactorización - Refactoring
13
Extract Method
Crear un método con el nombre que describa la intención del código
Copiar en el nuevo método el código extraídoDeterminar si las variables utilizadas en el código extraído son:
Parámetros
Valores de retorno
Variables locales
CompilarSustituir el fragmento de código con llamadas al nuevo métodoCompilar y testear
Refactorización - Refactoring
14
Código candidato para extraer
public String statement() {double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {double thisAmount = 0;Rental each = (Rental) rentals.nextElement();//determine amounts for each lineswitch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:thisAmount += 2;if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;break;
case Movie.NEW_RELEASE:thisAmount += each.getDaysRented() * 3;break;
case Movie.CHILDRENS:thisAmount += 1.5;if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;break;
}…
Refactorización - Refactoring
15
Aplicación del Extract Method
private int amountFor(Rental each) {int thisAmount = 0;switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:thisAmount += 2;if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;break;
case Movie.NEW_RELEASE:thisAmount += each.getDaysRented() * 3;break;
case Movie.CHILDRENS:thisAmount += 1.5;if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;break;
}return this amount;
}
Refactorización - Refactoring
16
Statement() después de la extracción
public String statement() {double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {
double thisAmount = 0;Rental each = (Rental) rentals.nextElement();//determine amounts for each linethisAmount=amountFor(each)
…
}
Refactorización - Refactoring
17
Fallo de Testeo
private int amountFor(Rental each) {double thisAmount = 0;switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
...return this amount;
}
public String statement() {double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {double thisAmount = 0;Rental each = (Rental) rentals.nextElement();//determine amounts for each linethisAmount=amountFor(each)
…}
La refactorización cambia los programas en pequeños pasos. Si se comete un error es más fácil encontrar el fallo.
Refactorización - Refactoring
18
Cambiar el nombre de las variables
private double amountFor(Rental aRental) {double result = 0;switch (aRental.getMovie().getPriceCode()) {case Movie.REGULAR:
result += 2;if (aRental.getDaysRented() > 2)result += (aRental.getDaysRented() - 2) * 1.5;
break;case Movie.NEW_RELEASE:
result += aRental.getDaysRented() * 3;break;
case Movie.CHILDRENS:result += 1.5;if (aRental.getDaysRented() > 3)
result += (aRental.getDaysRented() - 3) * 1.5;break;
}return result;
}
Un buen código debería comunicar claramente que hace, el nombre de las variables es clave para conseguirlo.
Refactorización - Refactoring
19
Move Method
aMethod()
Class 1
Class 2
Class 1
aMethod()
Class 2
Un método esta usando más características de otra clase que de la clase en la que está definido.
Crear un nuevo método con un cuerpo similar en la clase que más lo usa. Transformar el nuevo método en una delegación o
eliminarlo completamente.
Refactorización - Refactoring
20
Move Method
Declarar el método en la clase destinoCopiar y ajustar el códigoColocar una referencia del objeto destino en el objeto fuenteTransformar el método original en un método delegado
amountOf(Rental each) {return each.charge()}
Compilar y testearEncontrar todos los usuarios de ese método
Transformarles para llamar al método sobre la clase destino
Eliminar el método original Compilar y testear
Refactorización - Refactoring
21
Aplicación Move Method Rental.getCharge()
//class Rentaldouble getCharge() {
double result = 0;switch (getMovie().getPriceCode()) {case Movie.REGULAR:
result += 2;if (getDaysRented() > 2)
result += (getDaysRented() - 2) * 1.5;break;
case Movie.NEW_RELEASE:result += getDaysRented() * 3;break;
case Movie.CHILDRENS:result += 1.5;if (getDaysRented() > 3)
result += (getDaysRented() - 3) * 1.5;break;
}return result;
} 1 statement()
Customer
getCharge()
daysRented: int
Rental
priceCode: int
Movie
Refactorización - Refactoring
22
Aplicación Move Method Customer.amountFor()
//class Customerpublic String statement() {
double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {double thisAmount = 0;Rental each = (Rental) rentals.nextElement();
//determine amounts for each linethisAmount=each.getCharge();
…}private double amountFor(Rental rental) {return rental.charge();
}
Si el método movido es privado no tiene sentido mantener el delegado por que no van a existir clientes del mismo.
Refactorización - Refactoring
23
Problemas con variables temporales//class Customerpublic String statement() {
double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n“; while (rentals.hasMoreElements()) {
double thisAmount = 0;Rental each = (Rental) rentals.nextElement();
thisAmount = each.getCharge();// add frequent renter pointfrequentRenterPoints ++;// add bonus for a two day new release rentalif ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1) frequentRenterPoints ++;
//show figures for this rentalresult += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(thisAmount) + "\n“;totalAmount += thisAmount;
}//add footer linesresult += "Amount owed is " + String.valueOf(totalAmount) + "\n";result += "You earned " + String.valueOf(frequentRenterPoints) +
" frequent renter points“;return result;
}
Refactorización - Refactoring
24
Problemas con variables temporales
Ámbito temporal y local
Aumentan complejidad - > aumentan tamaño de los métodos
Pueden provocar duplicación de código
Dificultan la trazabilidad del códigoSu eliminación tiene un coste de rendimiento
Gran parte del tiempo de ejecución esta localizado en pequeñas porciones de código
Fácil localizar esos fragmentos de código
La mejor forma para optimizar es primero escribir un programa bien factorizado y luego optimizar
Refactorización - Refactoring
25
Replace Temp with Query
Se esta utilizando una variable temporal para manejar el resultado de una expresión.
Extraer la expresión en un método. Sustituir todas las referencias de la variable por la invocación al método. El nuevo método puede ser luego
utilizado en otros métodos.
double basePrice = _quantity * _itemPrice;if (basePrice > 1000)
return basePrice * 0.95;else
return basePrice * 0.98;
if (basePrice() > 1000)return basePrice() * 0.95;
elsereturn basePrice() * 0.98;
...double basePrice() {
return _quantity * _itemPrice;}
Refactorización - Refactoring
26
Replace temp with Query
Encontrar las variables temporales en las sentencias de asignación que son asignadas una única vez.
Java -> final y compilar
Extraer el lado derecho de la asignación en un nuevo método
A priori método como privado
Asegurarse que no se modifica ningún objeto
Reemplazar todas las referencias de la variable temporal por el nuevo método
Eliminar la declaración y la sentencia de asignación de la variable temporal
Compilar y testear
Refactorización - Refactoring
27
Aplicación Replace Temp with Query Customer.statement()
//class Customerpublic String statement() {
double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n“; while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();// add frequent renter pointfrequentRenterPoints ++;// add bonus for a two day new release rentalif ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1) frequentRenterPoints ++;
//show figures for this rentalresult += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(each.getCharge();) + "\n“;totalAmount += each.getCharge();;
}//add footer linesresult += "Amount owed is " + String.valueOf(totalAmount) + "\n";result += "You earned " + String.valueOf(frequentRenterPoints) +
" frequent renter points“;return result;
}
Refactorización - Refactoring
28
Aplicación Extract y Move Method Rental.getFrequentRenterPoints()
//class Customerpublic String statement() {
double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {Rental each = (Rental) rentals.nextElement();frequentRenterPoints += each.getFrequentRenterPoints();//show figures for this rentalresult += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(each.getCharge()) + "\n";totalAmount += each.getCharge();
}//add footer linesresult += "Amount owed is " + String.valueOf(totalAmount) + "\n";result += "You earned " +String.valueOf(frequentRenterPoints) + "
frequent renter points";return result;
}
Refactorización - Refactoring
29
UML: Después de refactorizar
1statement()
Customer
getCharge()getFrequentRenterPoints()
daysRented: int
Rental
priceCode: int
Movie
aCustomer aRental aMovie
getCharge
* [for all rentals]
getPriceCode
statement
getFrequentRenterPointsgetPriceCode
Refactorización - Refactoring
30
Eliminación variables temporales totalAmount - frequentRenterPoints
//class Customerpublic String statement() {double totalAmount = 0;int frequentRenterPoints = 0;Enumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {Rental each = (Rental) rentals.nextElement();frequentRenterPoints += each.getFrequentRenterPoints();//show figures for this rentalresult += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(each.getCharge()) + "\n";totalAmount += each.getCharge();
}//add footer linesresult += "Amount owed is " + String.valueOf(totalAmount) + "\n";result += "You earned " +String.valueOf(frequentRenterPoints) +
"frequent renter points";return result;
}
Refactorización - Refactoring
31
Aplicación Replace Temp with Query Nuevos métodos: Customer.getTotalCharge(),
Customer.getTotalFrequentRenterPoints()
//class Customer…private double getTotalCharge() {double result = 0;Enumeration rentals = _rentals.elements();while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();result += each.getCharge();
}return result;
}
private int getTotalFrequentRenterPoints(){int result = 0;Enumeration rentals = _rentals.elements();while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();result += each.getFrequentRenterPoints();
}return result;
}
Refactorización - Refactoring
32
Aplicación Replace Temp with Query Customer.statement( )
//Customerpublic String statement() {//Remove declarationsEnumeration rentals = _rentals.elements();String result = "Rental Record for " + getName() + "\n";while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();//show figures for this rentalresult += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(each.getCharge()) + "\n";}//add footer linesresult += "Amount owed is " + String.valueOf(getTotalCharge()) +
"\n";result += "You earned " +
String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";
return result;}
Refactorización - Refactoring
33
UML: Después de refactorizar
1statement()getTotalCharge()getTotalFrequentRenterPoints()
Customer
getCharge()getFrequentRenterPoints()
daysRented: int
Rental
priceCode: int
Movie
aCustomer aRental aMovie
* [for all rentals] getCharge
getTotalCharge
getPriceCode
statement
* [for all rentals] getFrequentRenterPoints
getPriceCode
getTotalFrequentRenterPoints
Refactorización - Refactoring
34
Añadir funcionalidad Customer.htmlStatement()
//Customerpublic String htmlStatement() {Enumeration rentals = _rentals.elements();String result = "<H1>Rentals for <EM>" + getName() +
"</EM></H1><P>\n";while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();//show figures for each rentalresult += each.getMovie().getTitle()+ ": " +
String.valueOf(each.getCharge()) + "<BR>\n";}//add footer linesresult += "<P>You owe <EM>" +
String.valueOf(getTotalCharge())+ "</EM><P>\n";result += "On this rental you earned <EM>" +
String.valueOf(getTotalFrequentRenterPoints()) +"</EM> frequent renter points<P>";
return result;}
Refactorización - Refactoring
35
Clasificación desconocida de películas
//class Rentaldouble getCharge() {double result = 0;switch (getMovie().getPriceCode()) {
case Movie.REGULAR:result += 2;if (getDaysRented() > 2)
result += (getDaysRented() - 2) * 1.5;break;
case Movie.NEW_RELEASE:result += getDaysRented() * 3;break;
case Movie.CHILDRENS:result += 1.5;if (getDaysRented() > 3)
result += (getDaysRented() - 3) * 1.5;break;
}return result;
}
Refactorización - Refactoring
36
Aplicación Move Method Movie.getCharge()
//class Rentaldouble getCharge(){
return _movie.getCharge(_daysRented);}//class Moviedouble getCharge(int daysRented) {
double result = 0;switch (getPriceCode()) {
case Movie.REGULAR:result += 2;if (daysRented > 2)
result += (daysRented - 2) * 1.5;break;
case Movie.NEW_RELEASE:result += daysRented * 3;break;
case Movie.CHILDRENS:result += 1.5;if (daysRented > 3)
result += (daysRented - 3) * 1.5;break;
}return result;
}
Hacer lo mismo con Rental.frequentRenterPoints()
Refactorización - Refactoring
37
Clasificación por herencia
Permite reemplazar las sentencias switch por polimorfismoProblema
Un película puede cambiar su clasificación durante su vida
Un objeto no puede cambiar de clase durante su vida
Solución aplicar el patrón de diseño Estado (State)
getCharge
Movie
getCharge
Regular Movie
getCharge
Childrens Movie
getCharge
New Release Movie
getCharge
Price
getCharge
Regular Price
getCharge
Childrens Price
getCharge
New Release Price
getCharge
Movie
1
return price.getCharge
Refactorización - Refactoring
38
Replace Type Code with State/Strategy
ENGINEER : intSALESMAN : intty pe : int
Employee Employee Type
Engineer Salesman
Employee1
Se dispone para clasificar de un código-tipo que afecta al comportamiento de una clase, pero no puede usarse
clasificación por herencia.Replace the type code with a state object.
Refactorización - Refactoring
39
Replace Type Code with State/Strategy
Crear una nueva clase para el código - tipoAñadir subclases del objeto estado, una por cada código - tipoCrear un método abstracto en la superclase que retorne el
código-tipo. Definir en las subclases dicho método, retornando el codigo – tipo correcto.
CompilarCrear un campo en la clase original para referenciar al objeto
estado.Cambiar los métodos de la clase original para la obtención del
código – tipo por una delegación sobre el objeto estado.Cambiar el codigo – tipo poniendo métodos para asignar una
instancia de una subclaseCompilar y testear.
Refactorización - Refactoring
40
Transformar código – precio en jerarquía de precios
abstract class Price {abstract int getPriceCode();
}
class ChildrensPrice extends Price {int getPriceCode() {
return Movie.CHILDRENS;}
}
class NewReleasePrice extends Price {int getPriceCode() {
return Movie.NEW_RELEASE;}
}
class RegularPrice extends Price {int getPriceCode() {
return Movie.REGULAR;}
}
Refactorización - Refactoring
41
Cambiar metodos de acceso en Movie
//Class Moviepublic int getPriceCode() {
return _price.getPriceCode();}public void setPriceCode(int arg) {
switch (arg) {case REGULAR:
_price = new RegularPrice();break;
case CHILDRENS:_price = new ChildrensPrice();break;
case NEW_RELEASE:_price = new NewReleasePrice();break;
default:throw new IllegalArgumentException("Incor rect Price Code");
}}private Price _price;
//Class Moviepublic int getPriceCode() {
return _priceCode;}public setPriceCode (int arg){
_priceCode = arg;}private int _priceCode;
Refactorización - Refactoring
42
Replace Conditional With Polymorphism
getSpeed
Bird
getSpeed
European
getSpeed
African
getSpeed
Norweigian Blue
Se tiene una sentencia condicional que selecciona diferentes comportamientos dependiendo del tipo de objeto.
Mover cada rama condicional a un método redefinido en una subclase. Transformar el método original a abstract.
double getSpeed() {switch (_type) {case EUROPEAN:
return getBaseSpeed();case AFRICAN:
return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;case NORWEIGIAN_BLUE:
return (_isNailed) ? 0 : getBaseSpeed(_voltage);}throw new RuntimeException ("Should be unreachable");
}
Refactorización - Refactoring
43
Replace Conditional with Polymorphism
Mover la sentencia condicional a la superclase de la jerarquía de herencia
Copiar el cuerpo de cada sentencia condicional como el cuerpo del método redefinido en una subclase
Compilar y testearRepetir el mismo proceso para cada rama condicionalSustituir las sentencias condicionales por un método abstracto
Refactorización - Refactoring
44
Aplicación Move Method Movie.getCharge() a Price. getCharge()
//class Movie…double getCharge(int daysRented) {
return _price.getCharge(daysRented);}//class Price…double getCharge(int daysRented) {
double result = 0;switch (getPriceCode()) {case Movie.REGULAR:
result += 2;if (daysRented > 2)result += (daysRented - 2) * 1.5;
break;case Movie.NEW_RELEASE:
result += daysRented * 3;break;
case Movie.CHILDRENS:result += 1.5;if (daysRented > 3)
result += (daysRented - 3) * 1.5;break;
}return result;
}
Refactorización - Refactoring
45
Replace Conditional with Polymorphism Price.getCharge( )
//Class RegularPricedouble getCharge(int daysRented){double result = 2;if (daysRented > 2)
result += (daysRented - 2) * 1.5;return result;
}//Class ChildrensPrice
double getCharge(int daysRented){double result = 1.5;if (daysRented > 3)
result += (daysRented - 3) * 1.5;return result;
}//Class NewReleasePrice
double getCharge(int daysRented){return daysRented * 3;
}
//Class Priceabstract double getCharge(int daysRented);
Hacer lo mismo con Rental.getFrequentRenterPoints()
Refactorización - Refactoring
46
Resumen…
En el ejemplo se mejoro el diseño de un programa para añadir funcionalidad
Más fácil añadir nuevos servicios a los clientes
Más fácil añadir nuevos tipos de películas
Dismuye depuración/debugging durante el proceso de refactorización
Las transformaciones controladas reducen la probabilidad de fallos
Las pequeñas transformaciones hacen que los fallos sean más fáciles de encontrar
En el ejemplo se ilustran varias refactorizaciones
Extract Method
Move Method
Replace Temp with Query
Replace Type Code with State/Strategy
Replace Switch with Polymorphism
Mirar el catálogo de Fowler en: http://www.refactoring.com/
Refactorización - Refactoring
47
Tabla de contenidos
Introducción: un ejemplo de refactoring
Principios en refactoringDefiniciones y orígenes ¿Por qué y cuando refactorizar? Problemas con las refactorizaciones
Misceláneo:
Herramientas
Metodologías ágiles
Definición de nuevas refactorizaciones (Big – Low)
Conclusiones
Bibliografía
Fowler, Refactoring: Improving the Design of Existing Code, Addison-
Wesley, 1999
Refactorización - Refactoring
48
Definiciones y orígenes de Refactoring
Uso relajado
Reorganizar un programa o algo
Como nombre
Cambio en la estructura interna de un programa para facilitar su comprensión y abaratar sus modificaciones, sin modificar el comportamiento externo
Como verbo
Actividad de reestructurar el software aplicando una serie d e refactoring/transformaciones sin cambiar el comportamiento externo del software.
Origenes
Ward Cunningham y Kent Beck (Smalltalk)
Ralph Johnson y Bill Opdyke (Definición formal d)
ftp://st.cs.uiuc.edu/pub/papers/refactoring/opdyke-thesis.ps.Z
John Brant y Don Roberts (Refactoring Browser)
Refactorización - Refactoring
49
¿Cuando refactorizar?
Añadir nueva funcionalidad
Refactorizar el código existente hasta que se entienda
Refactorizar el diseño para facilitar la incorporación de la nueva funcionalidad
Para encontrar fallos
Refactorizar para entender el código
Para revisiones de código
Efecto inmediato de una revisión de código
Regla de tres para hacer una cosa
(1ª vez) Hazla (2ª vez) Duplica (3ª vez) Refactoriza
El tiempo necesario para refactorizar hay que incluirlo como una actividad normal en el desarrollo del software.
Refactorización - Refactoring
50
Metáfora de los dos sombreros
Añadir funcionalidad
No modifica el código existenteAñade nueva funcionalidad al
sistemaAñade nuevos testObtener los resultados de test
Refactorizar
Modifica el código existenteNo añade nuevas característicasNo añade test (pero podría modificar
alguno)
Cambio de interfaz
Reestructura el código existente para eliminar redundancia
Realizar intercambios de sombreros, pero llevar puesto sólo uno cada vez
Refactorización - Refactoring
51
¿Por qué refactorizar?
Para mejorar el diseño del software
Combate la perdida de la estructura de código (diseño) en cambios acumulativos
Facilita futuros cambios en el programa
Hacer el software más fácil de entender
Escribir para que lo entienda las personas, no el compilador
Entender código desarrollado por otros
Ayuda a encontrar errores
Refactorizar mientras se depura para clarificar el código
www.junit.org
www.xprogramming.com/software
Refactorizar ayuda a programar más rápido
Refactorización - Refactoring
52
Problemas con la refactorización
Convencer a directivos
Directivos motivados por la calidad -> mejora la calidad del proceso
Otros -> Aumento de velocidad de desarrollo
Migraciones de bases de datos
Aislar la estructura persistente de la base de datos de los objetos
Añadir flexibilidad -> aísla los cambios de los distintos módulos a costa de añadir complejidad a la estructura
Interfaces publicadas -> más allá del modificador public
Si una refactorización cambia la signatura de un método, no existe problema si se tiene acceso a todas las llamadas a ese método
Publicar sólo cuando sea necesario
Un cambio obliga a mantener la antigua interface -> problema de códigos legado
Publicar dentro del equipo de desarrollo
Trabajar sin casos de prueba definidos
Para refactorizar el código debe funcionar correctamente sino reescribirlo
Refactorización - Refactoring
53
Tabla de contenidos
Introducción: un ejemplo de refactoringPrincipios en refactoring
Definiciones y orígenes
¿Por qué y cuando refactorizar?
Problemas con las refactorizaciones
Misceláneo:HerramientasMetodologías ágiles
Definición de nuevas refactorizaciones (Big – Low)Conclusiones
Bibliografía
Fowler, Refactoring: Improving the Design of Existing Code, Addison-
Wesley, 1999
Refactorización - Refactoring
54
Herramientas de Refactoring
Basadas en transformaciones demostrables
Repositorio para consultar información
Entidades del programa: clases, métodos, instrucciones,
Encontrar todos los métodos que leen o escriben una var. de instancia
…
Construir parses de programas
Probar matemáticamente que la refactorización no cambia la semántica
Incorporar las refactorizaciones en entornos integrados de desarrolloAumentan la velocidad en el proceso de refactorización
Operaciones para realizar Extract method con una herramienta
Seleccionar código a extraer
Escribir el código del método
No son ciencia ficción - > falta madurar
Refactorización - Refactoring
55
Herramientas de Refactoring
Refactorización - Refactoring
56
La importancia de los Test
Incluso con la herramienta, las pruebas son importantes
No se pueden probar todas las refactorizaciones
Es tan importante codificar los test cómo escribir el código
Para preservar el comportamiento en los cambios
Los test deben comprobarse automáticamente (self-checking)
Incluir los resultados esperados dentro del propio test
retornar “OK” si los resultados obtenido = resultados esperado
Ejecutar un conjunto de Test (suite) con un único comandoTestear con cada compilaciónDirecciones de interés
Refactorización - Refactoring
57
Decisiones de diseño
Diseño planeado
Considera necesidades actuales y posibles necesidades futuras
Se diseña para minimizar los cambios con futuras necesidades
Añade un parche en el código si aparece alguna necesidad imprevista
Diseño evolutivo
Considerar necesidades actuales y posibles necesidades futuras
Evaluar el coste de la actual flexibilidad versus coste de refactorizar más tarde
Refactorizar cuando el cambio aparece
XP
Metodología desarrollada por Kent Beck
Diseñada para soportar los cambios
Prácticas claves
Desarrollo iterativo
Código Self Testing
Refactoring
Programación por parejas
Influencia de las refactorizaciones para dar soporte el diseño evolutivo
©
Refactorización - Refactoring
58
Técnicas de equipo
Animar a la cultura de la refactorización
Nadie hace bien algo la primera vez
Nadie puede escribir código limpio sin revisiones
Refactorizar es un paso al progreso
Proporcionar un buen conjunto de Test
Los test son esenciales para refactorizar
Construir el software y ejecutar los test de forma diaria
Programación por parejas
Dos programadores trabajando juntos pueden ser más rápidos que trabajando separadamente
Refactorizar con escritor de la clase y con un usuario de la misma
Refactorización - Refactoring
59
Crear refactorizaciones propias
Considerar un cambio en el programaDebería cambiar el comportamiento externo del programaDesglosar el cambio en pequeños pasos
Localizar los puntos donde compilar y testear
Realizar los cambio -> anotar que es lo que se hace
Utilizar plantillas para definir los cambios
Si ocurre algún problema, considerar cómo eliminarlo en el futuro
Realizar los cambios de nuevo, comprobar y redefinir las anotaciones
Después de dos o tres veces de ejecutar la refactorización se dispondrá de una nueva refactorización
Refactorización - Refactoring
60
Conclusiones
El principal beneficio de los objetos es su facilidad para ser cambiados. (Encapsulamiento)
Las refactorizaciones permiten mejorar el diseño una vez que este ha sido escrito.
El tema de Refactoring esta inmaduro:
Falta de documentación escrita
Falta y mejora de herramientas