code design – ¿Qué es Refactorización?

Refactoring
Refactoring

Introducción

¿Qué es Refactoring?

  • Refactoring (Sustantivo): Un cambio hecho a la estructura interna de un software para hacerlo fácil de entender y mantener sin modificar el comportamiento observable.
  • Refactor (Verbo): Reestructurar un software aplicando uno o más estrategias de “refactorings” sin modificar el comportamiento observable.
Refactoring spaghetti code
Refactoring spaghetti code

Martin Fowler nos dice lo siguiente en su libro Refactoring – Improving the design of existing code:

Cualquier tonto puede escribir código que una computadora pueda entender, solo los buenos programadores pueden escribir código que otros humanos puedan entender.

Refactoring es un derivado del término “Factoring” y se le conoce informalmente como limpiar el código. Su objetivo es mejorar la lectura del código y eliminar código muerto (death code) para hacer más fácil el mantenimiento del mismo. En este proceso normalmente nos encontramos con defectos que debemos corregir: anidamiento excesivo, clases y funciones grandes, código duplicado y comentado, acoplamiento excesivo, uso de nombres incorrectos (clases, funciones, variables), etc. Por su lado Uncle Bob nos dice lo siguiente:

Deja un módulo en mejor estado que como lo encontraste.

Boy scout rule
Boy scout rule

Debemos tener en cuenta que muchas veces se escriben las cosas con lo primero que se nos viene a la mente y no es porque se quiera hacer de esa manera sino que por falta de tiempo para pensar en una mejor solución como nos dice Blaise Pascal:

Escribí esta carta más larga de lo usual ya que no tuve tiempo de hacerla más corta.

Tip: La primera versión de nuestro código no siempre será la mejor, pero esto tampoco debemos sentirnos mal, la refatorización debe realizarse poco a poco hasta encontrar el resultado esperado.

¿Por qué debemos Refactorizar?

  • Mejora el diseño
  • Mejora la lectura del código
  • Revela defectos
  • Ayuda a programar más rápido

¿Cuándo debemos Refactorizar?

  • Cuando desarrollamos usando TDD: Red – Gree – Refactor
  • Cuando desarrollamos usando PDD (Pain driven development): Si el código te causa dolor, entonces refactorizalo :).
  • Cuando se necesita agregar una nueva funcionalidad
  • Cuando corregimos bugs
  • Cuando hacemos la revisión de código

¿Cuándo no debemos Refactorizar?

  • Cuando la deuda técnica es muy alta y la única opción es reescribir toda la aplicación
  • Cuando no se usa un código, en este caso recomiendo borrarlo.
  • Cuando el fin del plazo de entrega está cerca, esto puede agregar nuevos bugs que no se llegan a probar por falta de tiempo.

Principios de Refactorización

  • KISS (Keep it simple stupid)
  • DRY (Dont repeat yourself)
  • Escribir código expresivo
  • Reducir el código
  • Separar responsabilidades
  • Usar un nivel de abstracción apropiado

Tips de Refactorización

  • Revisar constantemente el código
  • Hacer un checklist de defectos a refactorizar
  • Hacer refactorizaciones pequeñas, una a la vez
  • Agregar casos de prueba o pruebas unitarias
  • Revisar los resultados

Herramientas de Refactorización

  • Visual studio (Microsoft)
  • JustCode (Telerik)
  • ReSharper (JetBrains)
  • CodeRush (DevExpress)
  • Visual Assist X (Whole Tomato)

Técnicas de Refactorización

Rename Method

Una de las técnicas más sencillas pero efectivas, se puede usar sobre: clases, funciones, propiedades, variables, etc. Usar nombres claros reduce el uso de comentarios y aumente la lectura del código.

Antes
    /// <summary>
    /// Responsable de manejar los impuestos
    /// </summary>
    public class Class1
    {
        /// <summary>
        /// Nombre del proceso a ejecutar
        /// </summary>
        public string Nombre { get; set; }

        /// <summary>
        /// Calcula el impuesto a pagar
        /// </summary>
        /// <param name="t">Numero de Cedula</param>
        public double Calcula(string t) { }
    }
Después
    public class ServicioImpuesto
    {
        public string NombreProcesoEjecutar{ get; set; }
        public double CalculaImpuestoAPagar(string numeroCedula) { }
    }
Introduce Explaining Variable

Esta técnica se relaciona con la anterior, pero se aplica cuando se tiene una expresión complicada para darle un nombre fácil de entender.

Antes
if (solicitud.Tipo == 2 && solicitud.Cantidad > 10 && solicitud.Precio < 100)
{
    solicitud.Descuento = 0.5M;
}
Después
var aplicaDescuento = 
    solicitud.Tipo == 2 && solicitud.Cantidad > 10 && solicitud.Precio < 100;
if (aplicaDescuento)
{
    solicitud.Descuento = 0.5M;
}
Inline Temp

Las variables temporales son un tipo de code smell que hacen que los métodos se vuelvan más largos, siempre y cuando se usen una sola vez.

Antes
public void EjecutarProcesoAdmision(Solicitud solicitud)
{
    var estudiante = solicitud.Estudiante;
    var programa = solicitud.Programa;
    var vacante = solicitud.Vacante;
    var ciclo = solicitud.Ciclo;

    var resultado = ValidarAdmision(estudiante, programa, vacante, ciclo);
}
Después
public void EjecutarProcesoAdmision(Solicitud solicitud)
{    var resultado = ValidarAdmision(solicitud.Astudiante, 
           solicitud.Programa, solicitud.Vacante, solicitud.Ciclo);
}

No siempre es malo usar variables temporales, en algunos casos pueden servir para volver el código más fácil de entender, por ejemplo al llamar un método o una propiedad que su nombre no sea lo suficientemente claro.

Antes
    ValidarAdmision(solicitud.Ent,
           solicitud.Programa, solicitud.Vacante, solicitud.Ciclo);
Después
    var estudiante = solicitud.Ent;

    ValidarAdmision(estudiante,
           solicitud.Programa, solicitud.Vacante, solicitud.Ciclo);
Split Temp Variable

Esta técnica se aplica cuando una variable tiene muchas responsabilidades o almacena distintos resultados.

Antes
//Almacena el total
var temp = solicitud.Monto * solicitud.Cantidad;
//Calcula el impuesto
temp = (temp  * 0.15);
Después
var total= solicitud.Monto * solicitud.Cantidad;
var imuesto = (total  * 0.15);
Replace Temp With Query

Esta técnica es parecida a la técnica Inline temp pero con la diferencia que invoca a un método que contiene la evaluación de la expresión compleja.

Antes
var total= solicitud.Monto * solicitud.Cantidad;
var impuesto = (total  * 0.15);
Después
var total= CalcularTotal(solicitud);
var impuesto = (total  * 0.15);

public double CalcularTotal(Solicitud solicitud)
{
      return solicitud.Monto * solicitud.Cantidad;
}
Extract Method

Esta es una de las técnicas más usadas e importantes de la refactorización. Consiste en dividir métodos largos moviendo pedazos de códigos en nuevos métodos con nombres descriptivos.

Antes
public void CaducarPoliza(Poliza poliza)
{ 
	var servicioPoliza = new ServicioWebPolizaExterno();
    RequestOpera105874 requestCaducarPoliza;
	//Código...
	servicioPoliza.Opera105874(requestCaducarPoliza);
	 
	var servicioNotificacion = new ServicioNotificacion();
    Mensaje mensajeResponsable;
	//Código...
	servicioNotificacion.EnviarNotificacion(mensajeResponsable);

	var servicioTraza = new ServicioTraza();
    Traza trazaPolizaCaducada;
	//Código...
	servicioTraza.Registrar(trazaPolizaCaducada);
}
Después
public void CaducarPoliza(Poliza poliza)
{
	CaducarPolizaServicioExterno(poliza);
	EnviarNotificacionResponsable(poliza);
	RegistrarLogPoliza(poliza);
}

private void CaducarPolizaServicioExterno(Poliza poliza)
{
	var servicioPoliza = new ServicioWebPolizaExterno();
    RequestOpera105874 requestCaducarPoliza;
	//Código...
	servicioPoliza.Opera105874(requestCaducarPoliza);
}

private void EnviarNotificacionResponsable(Poliza poliza)
{
	var servicioNotificacion = new ServicioNotificacion();
    Mensaje mensajeResponsable;
	//Código...
	servicioNotificacion.EnviarNotificacion(mensajeResponsable);
}

private void RegistrarLogPoliza(Poliza poliza)
{
	var servicioTraza = new ServicioTraza();
    Traza trazaPolizaCaducada;
	//Código...
	servicioTraza.Registrar(trazaPolizaCaducada);
}
Inline Method

Esta técnica es opuesta a la técnica Extract method ya que une todos los métodos en un solo, se debe aplicar siempre y cuando una función solo se use en un solo lugar y tener el código en métodos separados no agregue ningún beneficio.

Antes
public double Calcula(Solicitud solicitud)
{
    //Codigo
    AsignarAlumno(solicitud, idEstudiante);
    var nroPedido = ObtenerNumeroPedido(solicitud);
    //Codigo
}

private void AsignarAlumno(int idAlumno)
{
    solicitud.IdAlumno = idAlumno;
}

private string ObtenerNumeroPedido(Solicitud solicitud)
{
    return solicitud.NumeroPedido;
}
Después
public double Calcula(Solicitud solicitud)
{
    //Codigo
    solicitud.IdAlumno = idAlumno;
    var nroPedido = solicitud.NumeroPedido;
    //Codigo
}
Move Method

Esta técnica mueve un método a otra clase ya que usa más características de la otra clase que de ella misma. Al aplicar estas estrategias reducimos el acoplamiento.

Antes
    class Poliza
    {
        public void CaducarPoliza(Poliza poliza)
        {
            //Codigo
            EnviarNotificacionResponsable(poliza);
            //Codigo
        }

        private void EnviarNotificacionResponsable(Poliza poliza)
        {
            Mensaje mensajeResponsable;
            //Código...
        }
    }
Después
    class Poliza
    {

        private readonly IServicioNotificacion servicioNotificacion;

        public Poliza(IServicioNotificacion servicioNotificacion)
        {
            servicioNotificacion = servicioNotificacion;
        }

        public void CaducarPoliza(Poliza poliza)
        {
            //Codigo
            EnviarNotificacionResponsable(poliza);
            //Codigo
        }

        private void EnviarNotificacionResponsable(Poliza poliza)
        {
            Mensaje mensajeResponsable;
            //Código...
            servicioNotificacion.EnviarNotificacion(mensajeResponsable);
        }
    }

    class ServicioNotificacion : IServicioNotificacion
    {
        public void EnviarNotificacion(Mensaje mensajeResponsable){}
    }
Extract Class

Esta técnica se aplica cuando se tiene una clase que hace el trabajo (tiene muchas responsabilidades) de dos o más.

Antes
    public class ServicioNotificacion
    {
        public void EnviarNotificacionSuperisor(string mensaje) { }
        public void EnviarNotificacionAdministrador(string mensaje) { }
        public string Encriptar(string valor) { }
        public string Desencriptar(string valor)
    }
Después
public class ServicioNotificacion
{
    public void EnviarNotificacionSuperisor(string mensaje) { }
    public void EnviarNotificacionAdministrador(string mensaje) { }
}


public class ServicioCifrado
{
    public string Encriptar(string valor) { }
    public string Desencriptar(string valor)
}
Inline Class

Esta técnica es opuesta a la técnica Extract class ya que une dos clases en una sola, esto aplica siempre y cuando la otra clase no es usada en más lugares.

Antes
public class ServicioNotificacion
{
    public void EnviarNotificacionSuperisor(string mensaje) { }
    public void EnviarNotificacionAdministrador(string mensaje) { }
}


public class ServicioCifrado
{
    public string Encriptar(string valor) { }
    public string Desencriptar(string valor)
}
Después
    public class ServicioNotificacion
    {
        public void EnviarNotificacionSuperisor(string mensaje) { }
        public void EnviarNotificacionAdministrador(string mensaje) { }
        private string Encriptar(string valor) { }
        private string Desencriptar(string valor)
    }
Encapsulate Field

Se aplica cuando un campo es expuesto a otras clases, en lugar de exponerlo públicamente se debe encapsular dentro de una propiedad.

Antes
class Persona
{
    public string nombre;
}
Después
class Persona
{
    public string Nombre { get; set; }
}
Encapsulate Collection

Se aplica cuando una propiedad retorna una colección, en su lugar se debe crear un método que permita agregar nuevos elementos a la colección.

Antes
    public class Estudiante
    {
        public string Nombre{ get; set; }
        public List<Clase> Clases;
    }

    public class ServicioMatricula
    {
        public void MatricularEstudiante(Estudiante estudiante, Clase clase)
        {
            var listaClases = estudiante.Clases;
            //Codigo
            listaClases.Add(clase);
        }
    }
Después
    public class Estudiante
    {
        private List<Clase> _clases = new List<Clas3>();
        public string Nombre{ get; set; }

        public IReadOnlyCollection<Clas3> Clases
        {
            get { return _clases.AsReadOnly(); }
        }

        public void InscribirseEnClase(Clase clase)
        {
            _clases.Add(clase);
        }
    }

    public class ServicioMatricula
    {
        public void MatricularEstudiante(Estudiante estudiante, Clase clase)
        {
            estudiante.IncribirseEnClase(clase);
        }
    }
Extract Interface

Aplica cuando dos clases tienen interfaces similares.

Antes
    class Poliza
    {
        public void CaducarPoliza(Poliza poliza)
        {
             //Codigo
             if(Configuracion.EsSmsActiva)
             {
                 EnviarNotificacionResponsableSms(poliza);
             }
             else {
                 EnviarNotificacionResponsableSmtp(poliza);
             }
             //Codigo
        }

        private void EnviarNotificacionResponsableSmtp(Poliza poliza)
        {
            ServicioNotificacionSmtp servicioNotificacion;
            servicioNotificacion.EnviarNotificacion(mensaje);
        }

        private void EnviarNotificacionResponsableSms(Poliza poliza)
        {
            ServicioNotificacionSms servicioNotificacion;
            servicioNotificacion.EnviarNotificacion(mensaje);
        }
    }

    class ServicioNotificacionSmtp
    {
        public void EnviarNotificacion(Mensaje mensajeResponsable){}
    }

    class ServicioNotificacionSms 
    {
        public void EnviarNotificacion(Mensaje mensajeResponsable){}
    }
Después
    class Poliza
    {

        private readonly IServicioNotificacion servicioNotificacion;

        public Poliza(IServicioNotificacion servicioNotificacion)
        {
            servicioNotificacion = servicioNotificacion;
        }

        public void CaducarPoliza(Poliza poliza)
        {
            EnviarNotificacionResponsable(poliza);
        }

        private void EnviarNotificacionResponsable(Poliza poliza)
        {
            servicioNotificacion.EnviarNotificacion(mensaje);
        }
    }

    class ServicioNotificacionSmtp : IServicioNotificacion
    {
        public void EnviarNotificacion(Mensaje mensajeResponsable){}
    }

    class ServicioNotificacionSms : IServicioNotificacion
    {
        public void EnviarNotificacion(Mensaje mensajeResponsable){}
    }
Extract SubClase

Aplica cuando una clase tiene algunos métodos o propiedades solo se usan en ciertos casos.

Antes
    class Poliza
    {
        public void RegistrarPoliza(Poliza poliza)
        {
            //Codigo
        }

        public void CaducarPoliza(Poliza poliza)
        {
            if (Tipo == TipoPoliza.Caducable)
            {
                //Codigo
            }
        }
    }
Después
    class Poliza
    {
        public void RegistrarPoliza(Poliza poliza)
        {
            //Codigo
        }
    }

    class PolizaCaducable : Poliza
    {
        public void CaducarPoliza(Poliza poliza)
        {
            //Codigo
        }
    }
Extract SuperClase

Esta técnica aplica cuando dos clases tienen características similares.

Antes
public class ServicioNotificacionAdministrador
{
    private void Enviar(Notificacion notificacion, int tipo)
    {
        var smtp = CrearClienteSmtp();
        AsignarCredenciales(smtp);
        var formatoCorreo = ObtenerFormatoCorreo(notificacion);
        var mensajeFinal = PrepararMensaje(formatoCorreo); 
        var mail = new MailMessage();
        //Codigo
        mail.Body = //Codigo
        smtp.Send(mail);
    }
}
 public class ServicioNotificacionSupervisor
{
    private void Enviar(Notificacion notificacion, int tipo)
    {
        var smtp = CrearClienteSmtp();
        AsignarCredenciales(smtp);
        var formatoCorreo = ObtenerFormatoCorreo(notificacion);
        var mensajeFinal = PrepararMensaje(formatoCorreo); 
        var mail = new MailMessage();
        //Codigo
        mail.Body = //Codigo
        smtp.Send(mail);
    }
}
Después
public abstract class ServicioNotificacion<T>
{
    protected abstract string ObtenerFormatoCorreo(T parametro);
 
    private void Enviar(T notificacion)
    {
        var smtp = CrearClienteSmtp();
        AsignarCredenciales(smtp);
        var formatoCorreo = ObtenerFormatoCorreo(notificacion);
        var mensajeFinal = PrepararMensaje(formatoCorreo);
        var mail = new MailMessage();
        //Codigo
        mail.Body = mensajeFinal; 
        smtp.Send(mail);
    }
}
 
public class ServicioNotificacionAdministrador : 
           ServicioNotificacion<NotificacionAdministrador>
{
    protected override string ObtenerFormatoCorreo(NotificacionAdministrador parametro)
    {
        //Codigo
    }
}
 
public class ServicioNotificacionSupervisor : 
           ServicioNotificacion<NotificacionSupervisor>
{
    protected override string ObtenerFormatoCorreo(NotificacionSupervisor parametro)
    {
        //Codigo
    }
}
Hide Delegate

Esa técnica se aplica cuando un cliente llama a la propiedad de otra propiedad dentro de la clase.

Antes
    public class Empleado
    {
        public string Nombre { get; set; }
        public Departamento Departamento { get; set; }
    }

    public class Cliente
    {
        public void Procesar(Empleado empleado)
        {
            var administrador = empleado.Departamento.Administrador;
        }
    }
Después
    public class Empleado
    {
        public string Nombre { get; set; }
        public Departamento Departamento { get; set; }

        public Empleado Administrador
        {
            get { return this.Departamento.Administrador; }
        }
    }

    public class Cliente
    {
        public void Procesar(Empleado empleado)
        {
            var administrador = empleado.Administrador;
        }
    }
Replace Magic Numbers

Esta técnica se aplica cuando se tiene un valor en duro y no se sabe que significa.

Antes
var aplicaDescuento =
    solicitud.Tipo == 2 && solicitud.Cantidad > 10 && solicitud.Precio < 100;
if (aplicaDescuento)
{
    solicitud.Descuento = 0.5M;
}
Después
const int CantidadMaximaContraEntrega = 10;
const double PrecioMaximoContraEntrega = 10;
const double DescuentoContraEntrega = .5M;

var aplicaDescuento =
    solicitud.Tipo == Tipo.SolicitudContraEntrega &&
    solicitud.Cantidad > CantidadMaximaContraEntrega &&
    solicitud.Precio < PrecioMaximoContraEntrega;
if (aplicaDescuento)
{
    solicitud.Descuento = DescuentoContraEntrega ;
}

Conclusiones:  Conocido informalmente como limpiar el código su objetivo es mejorar la lectura del mismo para hacer más fácil su mantenimiento. Debemos tener en cuenta que la primera versión de nuestro código no siempre será la mejor,  la refatorización debe realizarse poco a poco hasta encontrar el resultado esperado. Se debe refactorizar cuando: necesitamos agregar nuevas funcionalidades, corregimos bugs o revisamos el código; pero también necesitamos saber que si estamos próximos a entregar el producto la refactorización puede agregar bugs sino se prueba adecuadamente. Para refactorizar se necesita: revisar el código frecuentemente, hacer un cheklist con los defectos encontrados, hacer pequeñas refactorizaciones, ejecutar las pruebas unitarias y verificar los resultados.

Referencias:
Metal Tip:

Este artículo lo escribí escuchando la canción Hearts on Fire de la banda Hammerfall de Suecia, les comparto el enlace. Happy coding and Stay Heavy lml

Anuncios

2 comentarios en “code design – ¿Qué es Refactorización?

  1. Que bonito!, excelente! me encanto la frase de Martin Fowler.
    – Cualquier tonto puede escribir código que una computadora pueda entender, solo los buenos programadores pueden escribir código que otros humanos puedan entender.

    Me ha tocado ver código donde a primeras uno lo entiende y hasta se enamora o se emociona
    de tan solo ver el código y otras veces aun que otras veces donde POO se vuelve el mismito infierno
    y eso lo he visto mas en unos proyectos de ejemplos de un Framework para el proceso de imagenes que
    utilizamos mucho y en el trabajo que ni parecen ejemplos.!

    NO SOLO ES SABER PROGRAMAR CODIGO SINO TAMBIEN SABERLO EXPRESAR BIEN!
    y aveces ni como comentas los comentarios salen sobrando!

    Le gusta a 2 personas

    1. Me ha gustado mucho la entrada: la refactorización es un tema que se descuida mucho, casi siempre por falta de tiempo pero no hacerlo a la larga resulta contraproducente.
      Por cierto, siempre me ha llamado la atención (y al leer el tipo de refactorización, Encapsulate Field, lo he recordado) la preferencia de los expertos por las propiedades frente a los campos. A no ser que se tenga intención de realizar alguna validación en la asignación o convertirla en una propiedad de “solo lectura” nunca he visto ninguna ventaja de una opción frente a otra…

      Le gusta a 1 persona

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s