Meta: pasar de “código que funciona hoy” a “código que sigue funcionando y evolucionando en 6 meses”. Aquí el foco está en estructura, límites y refactor disciplinado.
Una clase grande suele ocultar más de una responsabilidad. Divide por razón de cambio para reducir impacto en mantenimiento.
Prefiere depender de abstracciones y contratos claros, no de detalles concretos de infraestructura.
Exponer menos reduce mal uso y hace más fácil refactorizar internals sin romper consumidores.
MAL VS BIEN — CLASE CON RESPONSABILIDADES MEZCLADAS
// Mal: dominio + persistencia + notificación en una sola clase
public class InvoiceService {
public void closeInvoice(Invoice invoice) {
invoice.markAsClosed();
jdbcTemplate.update("update invoices set status='CLOSED' where id=?", invoice.id());
smtpClient.send(invoice.customerEmail(), "Factura cerrada");
}
}
// Bien: caso de uso orquesta contratos, cada detalle afuera
public final class CloseInvoiceUseCase {
private final InvoiceRepository invoices;
private final NotificationPort notifications;
public void execute(InvoiceId id) {
Invoice invoice = invoices.findById(id).orElseThrow();
invoice.close();
invoices.save(invoice);
notifications.invoiceClosed(invoice);
}
}
Lanza errores temprano con contexto relevante para diagnóstico. Evita “Exception” genérica sin información.
Traduce errores técnicos a errores de dominio en fronteras de módulo para no propagar detalles internos.
Error técnico filtrado
Exponer `SQLException` o mensajes del proveedor al dominio/cliente rompe encapsulamiento.
Error de dominio traducido
Mapear fallos de infraestructura a errores de negocio permite decisiones claras y estables.
Refactoriza en cambios acotados con feedback de tests para evitar regresiones silenciosas.
La duplicación no es solo código igual: también reglas replicadas en múltiples capas.
Cada nueva abstracción debe pagar renta. Si no simplifica lectura o cambio, probablemente sobra.
Primero protege con tests el comportamiento actual (aunque esté feo).
Extrae una responsabilidad o simplifica una función por iteración.
Si algo rompe, revierte rápido; el objetivo es progreso seguro, no heroísmo.
Al final, documenta decisiones de diseño y patrones de uso para el equipo.
Checklist final antes de merge:
- ¿Cada clase tiene una razón de cambio clara?
- ¿Los errores se traducen en los límites del módulo?
- ¿Las abstracciones nuevas reducen complejidad real?
- ¿El diff mejora legibilidad sin cambiar comportamiento accidentalmente?