Como su nombre lo indica, Long Method (Método Largo en inglés) surge cuando un método tiene demasiadas líneas de código. Esto lo hace difícil de entender, testear y mantener. A menudo, los métodos largos aparecen de manera gradual. Comienzan como funciones simples, pero con el tiempo se les van agregando responsabilidades adicionales. Esta práctica, aunque parezca inofensiva a corto plazo, puede generar problemas serios a largo plazo.
Veamos un ejemplo para ilustrar este punto:
public void processInvoice(Customer customer, List<Item> items, double discountRate) {
double total = 0;
for (Item item : items) {
total += item.getPrice() * item.getQuantity();
}
if (discountRate > 0) {
total -= total * discountRate;
}
if (customer.getBalance() >= total) {
customer.setBalance(customer.getBalance() - total);
logger.info("Pago exitoso. Saldo restante: {}", customer.getBalance());
} else {
logger.warn("Pago fallido. Saldo insuficiente.");
return;
}
logger.info("Resumen del pedido para el cliente: {}", customer.getName());
for (Item item : items) {
logger.info("{} x {}", item.getName(), item.getQuantity());
}
}
El método processInvoice()
tiene múltiples responsabilidades: calcular el total, aplicar un descuento, verificar el saldo del cliente, procesar el pago e imprimir un resumen del pedido. Todo esto se encuentra dentro de un único bloque de código. Este enfoque puede parecer sencillo al principio, pero a medida que un proyecto crece, este tipo de métodos largos se vuelve cada vez más difícil de mantener y de probar.
¿Cómo podemos abordar este problema? Una opción inicial es extraer cada paso a un método privado dentro de nuestra clase. Aunque esta solución ayuda a reducir la cantidad de código en el método, puede llevar a violar el principio de responsabilidad única. Por lo tanto, una alternativa más efectiva sería extraer cada funcionalidad en una clase independiente.
public void processInvoice(Customer customer, List<Item> items, double discountRate) {
double total = priceCalculator.calculate(items, discountRate);
if (!customer.pay(total)) {
logger.warn("Pago fallido. Saldo insuficiente.");
return;
}
logger.info("Pago exitoso. Saldo restante: {}", customer.getBalance());
orderSummaryPrinter.print(customer, items);
}
De este modo, delegamos el cálculo del total a una nueva clase llamada PriceCalculator
. La verificación y reducción del saldo del cliente ahora se gestionan a través de un nuevo método pay()
dentro de la clase Customer
. Finalmente, la impresión del resumen de la orden se ha trasladado a una nueva clase denominada OrderSummaryPrinter
.
Esta estrategia no solo mejora la claridad del método processInvoice()
, sino que también simplifica los tests. Ahora podemos crear unit tests específicos para priceCalculator.calculate()
, customer.pay()
y orderSummaryPrinter.print()
de manera independiente. Al refactorizar el código de esta forma, logramos métodos más concisos, que son más fáciles de entender, probar y mantener.