Aplicar DRY, KISS y YAGNI deja de ser teoría cuando enfrentamos código real. En estos casos prácticos en Java mostramos cómo identificar problemas concretos, refactorizar y evaluar el impacto de aplicar cada principio.
Cada caso detalla el contexto, la refactorización paso a paso y el resultado final para que puedas replicarlo en tus proyectos.
Problema: distintos módulos calculaban impuestos y totales de manera duplicada con pequeñas variaciones.
// Código original: lógica duplicada en dos servicios
class OnlineBillingService {
BigDecimal calculateTotal(Order order) {
BigDecimal subtotal = order.subtotal();
BigDecimal iva = subtotal.multiply(new BigDecimal("0.21"));
return subtotal.add(iva);
}
}
class WholesaleBillingService {
BigDecimal calculateTotal(Order order) {
BigDecimal subtotal = order.subtotal();
BigDecimal iva = subtotal.multiply(new BigDecimal("0.21"));
BigDecimal bonificacion = subtotal.multiply(new BigDecimal("0.05"));
return subtotal.add(iva).subtract(bonificacion);
}
}
Refactorización: se creó un componente común para el cálculo de IVA y se simplificaron los métodos.
// Código refactorizado: DRY para calcular IVA y métodos simples
final class TaxCalculator {
BigDecimal iva(BigDecimal subtotal) {
return subtotal.multiply(new BigDecimal("0.21"));
}
}
final class BillingService {
private final TaxCalculator taxCalculator;
BillingService(TaxCalculator taxCalculator) {
this.taxCalculator = taxCalculator;
}
BigDecimal totalOnline(Order order) {
BigDecimal iva = taxCalculator.iva(order.subtotal());
return order.subtotal().add(iva);
}
BigDecimal totalWholesale(Order order) {
BigDecimal iva = taxCalculator.iva(order.subtotal());
BigDecimal bonificacion = order.subtotal().multiply(new BigDecimal("0.05"));
return order.subtotal().add(iva).subtract(bonificacion);
}
}
Resultado: se eliminó duplicación (DRY) y cada método quedó enfocado en su tarea (KISS).
Problema: un controlador MVC gestionaba capas de validación redundantes, feature flags que nunca se usaron y transformaciones innecesarias.
// Código original: controlador con capas y flags especulativos
class OrderController {
private final OrderService service;
private final FeatureToggle toggles;
OrderController(OrderService service, FeatureToggle toggles) {
this.service = service;
this.toggles = toggles;
}
ResponseEntity<?> create(OrderRequest request) {
if (toggles.isEnabled("ASYNC_CREATE")) {
// Camino alternativo no habilitado
}
ValidationResult result = validate(request);
if (!result.valid()) {
return ResponseEntity.badRequest().body(result.errors());
}
Order order = service.create(transform(request));
return ResponseEntity.ok(mapResponse(order));
}
private Order transform(OrderRequest request) {
// Conversión redundante
return new Order(request.id(), request.items());
}
private OrderResponse mapResponse(Order order) {
// Wrapper innecesario
return new OrderResponse(order.id(), order.total());
}
}
Refactorización: se eliminó código especulativo (YAGNI) y se simplificó el flujo.
// Código refactorizado: controlador simple y sin supuestos futuros
final class OrderController {
private final OrderService service;
OrderController(OrderService service) {
this.service = service;
}
ResponseEntity<Order> create(OrderRequest request) {
ValidationResult result = request.validate();
if (!result.valid()) {
return ResponseEntity.badRequest().build();
}
Order order = service.create(request.toOrder());
return ResponseEntity.ok(order);
}
}
Resultado: la ruta principal quedó clara (KISS) y se eliminó funcionalidad no validada (YAGNI).
Problema: un pipeline de procesamiento de reportes tenía duplicaciones, configuraciones anticipadas y pasos que mezclaban responsabilidades.
// Código original: pasos duplicados y configuraciones innecesarias
class ReportPipeline {
private final CsvReader csvReader;
private final XmlReader xmlReader;
private final ReportSender sender;
private final AuditService auditService;
ReportPipeline(CsvReader csvReader,
XmlReader xmlReader,
ReportSender sender,
AuditService auditService) {
this.csvReader = csvReader;
this.xmlReader = xmlReader;
this.sender = sender;
this.auditService = auditService;
}
void processCsv(Path file, boolean sendAudit) {
List<Record> records = csvReader.read(file);
List<Record> normalized = normalize(records);
if (sendAudit) {
auditService.record(normalized);
}
sender.send(normalized, "CSV");
}
void processXml(Path file, boolean sendAudit) {
List<Record> records = xmlReader.read(file);
List<Record> normalized = normalize(records);
if (sendAudit) {
auditService.record(normalized);
}
sender.send(normalized, "XML");
}
private List<Record> normalize(List<Record> records) {
return records.stream()
.map(record -> record.trimmed())
.toList();
}
}
Refactorización: se aplicaron los tres principios: DRY para reutilizar el flujo, KISS para hacerlo legible y YAGNI para posponer configuraciones no requeridas.
// Código refactorizado: pipeline reutilizable y simple
final class ReportPipeline {
private final Map<Format, Reader> readers;
private final ReportSender sender;
private final AuditService auditService;
ReportPipeline(Map<Format, Reader> readers,
ReportSender sender,
AuditService auditService) {
this.readers = Map.copyOf(readers);
this.sender = sender;
this.auditService = auditService;
}
void process(Path file, Format format, boolean audit) {
Reader reader = readers.get(format);
List<Record> normalized = reader.read(file).stream()
.map(Record::trimmed)
.toList();
if (audit) {
auditService.record(normalized);
}
sender.send(normalized, format.name());
}
}
Resultado: el pipeline quedó reutilizable (DRY), fácil de seguir (KISS) y sin configuraciones futuras innecesarias (YAGNI).
Practicar con ejemplos concretos demuestra que DRY, KISS y YAGNI no se excluyen: juntos sostienen un código evolutivo, fácil de entender y enfocado en el valor real.