Recuerdo una vez que me encontré desarrollando una historia de usuario que requería cambiar un método que había hecho hace mucho tiempo atrás. Inmediatamente antes de arrancar noté que algo me hacía ruido, no me gustaba, no me convencía. Me di cuenta que tardé más de lo que me hubiese gustado en entender qué hacia realmente ese método. Inmediatamente pensé "mmm... ¡creo que esto amerita un refactor!".

Es probable que te haya pasado algo similar en algún momento y que, como yo, te parezca que el código parece ser muy complejo para cambiar y no sabés por dónde empezar. En este post te voy a dar cinco consejos para que puedas hacerlo.

¿Qué es "refactorizar"?

El termino refactor fue acuñado por Martin Fowler para describir una técnica por la cual se busca mejorar el diseño de un código ya existente1. Esta técnica consiste en aplicar unas pequeñas transformaciones, paso a paso, que en su suma van a producir un gran cambio.

¿Para qué refactorizamos? Para reducir la posibilidad de introducir errores cuando estamos agregando nueva funcionalidad. Esto se hace teniendo en cuenta las posibles señales o síntomas de que puede haber un problema en el código conocidos como code smells2. Si, aplicar un refactor no se basa en que el código quede "más lindo", si no en que sea mantenible, definiendo mantenible con la pregunta: ¿Cuán costoso resulta introducir cambios en este código?

Consejo 1: Aprendete los code smells más comunes

Si los googleas vas a ver cantidad de artículos que hablan sobre code smells y sus definiciones. En particular te quiero dejar dos de los que son, para mí, los más comunes de encontrate y qué podés hacer para limpiarlos. 

Code Smell: Método grande

¿Te encontráste alguna vez con un método de más de 10, 20 o 30 líneas que te costó seguir para identificar qué es lo qué está haciendo? Muchas veces esto puede resultar peligroso. Podés dividir y extraer los algoritmos internos en nuevos métodos privados, sucesivamente, uno a la vez, para generar pequeños métodos que contengan la funcionalidad más específica. Por ejemplo:

public BigDecimal obtenerMonto(List<Cuota> cuotas) {
	if (cuotas.isEmpty()) {
		throw new IllegalArgumentException("No hay cuotas a pagar");
	}
	BigDecimal porcentajeInteres = new BigDecimal("0.3");
	BigDecimal intereses = BigDecimal.valueOf(cuotas.size()).multiply(porcentajeInteres);
	BigDecimal monto = BigDecimal.ZERO;
	for (Cuota cuota : cuotas) {
		if (cuota.getMonto().equals(BigDecimal.ZERO)) {
			throw new IllegalArgumentException("El monto de la cuota es nulo");
		}
		BigDecimal montoCuota = cuota.getMonto().add(cuota.getMonto().multiply(intereses));
		monto = monto.add(montoCuota);
	}
	return monto;
}

Puede dividirse y quedarte algo como esto:

public BigDecimal obtenerMonto(List<Cuota> cuotas){	
	validarCuotasVacias(cuotas);
	BigDecimal intereses = calcularIntereses(cuotas);
	BigDecimal monto = calcularMontoTotal(cuotas);
	return aplicarIntereses(monto,intereses);
}

* Nombre poco descriptivo

Este no es un code smell como tal, pero lo menciono porque creo que va de la mano de lo anterior. Muchas veces los métodos grandes no describen en su firma qué es todo lo que hacen, y suelen ocultar parte del comportamiento. Dividir el método en pequeños métodos que lo compongan te va a facilitar renombrar los mismos, con sus parámetros y variables, para describir con más claridad de qué es lo que hacen. Por ejemplo, si reescribimos el código anterior:

public BigDecimal obtenerMontoTotalAPagar(List<Cuota> cuotasAPagar){	
	validarCantidadDeCuotas(cuotasAPagar);
	BigDecimal intereses = calcularInteresesPorCantidadDeCuotas(cuotasAPagar);
	BigDecimal montoTotalAPagar = calcularMontoTotal(cuotasAPagar);
	return aplicarInteresesAlMontoTotal(montoTotalAPagar,intereses);
}


Code Smell: Clase larga

Con largo nos referimos a la cantidad de cosas que hace, sus responsabilidades. La realidad es que las clases arrancan chiquititas, incluso con un sólo método, pero mucha de ellas terminan siendo como una bolsa de gatos de comportamiento. Dividir la clase en nuevas clases que contengan una responsabilidad única3 te va a facilitar introducir los cambios. Por ejemplo:

public class CuotaService() {
	public Cuota crear(Cuota cuota);
	public void eliminar(Cuota cuota);
	public BigDecimal obtenerMontoTotalAPagar(List<Cuota> cuotas);
	public DetalleCuota obtenerDetalle(Cuota cuota);
	public int calcularDiasDeAtrasoDelPago(Cuota cuota);
}

Puede dividirse y quedarte algo como esto:

public class CuotaService() {
	public Cuota crear(Cuota cuota);
	public void eliminar(Cuota cuota);
}
public class DetalleCuotaSerivce(){
	public DetalleCuota obtener(Cuota cuota);
	public int calcularDiasDeAtrasoDelPago(Cuota cuota);
}
public class MontoService(){
	public BigDecimal obtenerMontoTotalAPagarDeLasCuotas(List<Cuota> cuotas);
}

Consejo 2: Un cambio a la vez

Tener un codigo limpio o un clean code4 no siempre resulta fácil o rápido, sobre todo cuando el código hace tiempo está sucio. Lo mejor es ir paso a paso, como dijo Fowler. Si usas TDD5 de seguro tengas una batería de test que salvaguarde tu código. Usala, ¡para eso está!. Hace un refactor pequeño, corré los test y, si todo salió bien, recién ahí seguí o commiteá.

Consejo 3: Planificá tu refactor

¿Cómo hago para dar entregar valor al final del sprint y a la vez limpiar todo el código? A veces cuando el código es realmente complejo y hay fechas comprometidas, no parece fácil mantener ese equilibrio.
Te aconsejo que lo planifiques con tu equipo. De la mano al consejo de ir paso a paso, podés generar pequeños timebox fijos para realizar estos refactors. Quizá un refactor por sprint, o quizá un refactor por historia. ¡Lo que más te convenga!

Consejo 4: Pedí ayuda

Dos cabezas piensan mejor que una, y dos desarrolladores refactorizan mejor que uno. Siempre tener una mirada extra sobre el código te puede abrir el panorama. De hecho, me ha pasado más de una vez que tan sólo al relatar en voz alta lo que el método estaba haciendo, una chispa se encendía y, como una musa, me daba el camino correcto para continuar con ese refactor.

Consejo 5: Ofrecé y pedí revisiones de código

Ofrecerte y darte el tiempo para realizar una revisión de código va a agudizar tu capacidad de detectar code smells. Es más, quizá te encuentres con cosas que también puedas refactorizar en tus propios proyectos. Por otro lado, recibir una revisión te va a ayudar a ver las cosas que quizá se te pasaron por alto. En definitiva, vas a aprender y crecer como desarrollador.

 

Espero que estos consejos logren darte el pié para empezar a refactorizar o mejorar la forma en la que lo venías haciendo. Quizá como última reflexión o consejo bonus, en base al último consejo, me recomiendo que no pierdas tiempo ni energía criticandote a vos o a quienes hicieron ese código. Una vez alguien me dijo: Hacemos lo mejor que hoy podemos con las herramientas que hoy tenemos. ¡Seguramente el día de mañana también veas tu código de hoy, y se te ocurran mil maneras nuevas de mejorarlo!

 

Referencias
  1. Libro Refactoring de Martin Fowler (link).
  2. Definción de Code Smells de Kent Beck (por Martin Fowler) (link).
  3. Artículo en blog de SomosPNT sobre Principio de Responsabilidad Única (link).
  4. Blog de Clean Code de Robert C. Martin (link).
  5. Articulo de Martin Fowler sobre TDD, de Kent Beck (link).

Mandanos tus sugerencias

Ayudanos con ideas para los artículos de este blog a contacto@somospnt.com

¡Seguínos en nuestras redes sociales para enterarte de los últimos posts!