La cohesión mide cuánta relación existe entre los elementos de una misma unidad de código. Cuando la cohesión cae, se vuelve difícil entender el propósito de la clase, aparecen bugs ocultos y el mantenimiento se torna riesgoso. Este tema propone una ruta para detectar la baja cohesión y refactorizar los componentes hasta recuperar un enfoque nítido y estable.
Un componente con responsabilidades mezcladas suele tener métodos que solo comparten el nombre de la clase. Eso incrementa el acoplamiento accidental, reduce la capacidad de prueba y rompe el Principio de Responsabilidad Única del conjunto SOLID. Detectar temprano la baja cohesión evita refactorizaciones traumáticas y asegura que el diseño evolucione acompañando al dominio.
Algunos síntomas se pueden reconocer solo leyendo el código:
La baja cohesión también se detecta por el impacto en el trabajo diario:
Las métricas apoyan la intuición sin reemplazarla. Indicadores como Lack of Cohesion of Methods o el conteo de razones para cambiar ayudan a validar sospechas. Un LCOM alto implica que los métodos no comparten atributos, mientras que un registro de cambios fragmentado revela que la clase sirve a actores distintos del dominio. Las herramientas de análisis estático permiten monitorear tendencias y configurar alertas cuando se supera un umbral.
La siguiente clase en Java representa un anti-ejemplo de cohesión. En apariencia resuelve la gestión de pedidos, pero combina logíca de negocio, persistencia y notificación.
class GestorPedido {
private final DataSource dataSource;
private final SmtpClient smtpClient;
GestorPedido(DataSource dataSource, SmtpClient smtpClient) {
this.dataSource = dataSource;
this.smtpClient = smtpClient;
}
// Regla de negocio
void aprobar(Pedido pedido) {
if (pedido.total().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("El monto debe ser positivo");
}
pedido.marcarAprobado();
guardar(pedido);
notificarCliente(pedido);
}
// Persistencia
void guardar(Pedido pedido) {
try (Connection cn = dataSource.getConnection()) {
PreparedStatement st = cn.prepareStatement(
"UPDATE pedidos SET estado = ?, actualizado = ? WHERE id = ?");
st.setString(1, pedido.estado().name());
st.setTimestamp(2, Timestamp.from(Instant.now()));
st.setLong(3, pedido.id());
st.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException("Error al persistir el pedido", e);
}
}
// Comunicación
void notificarCliente(Pedido pedido) {
Email email = Email.from(pedido.emailCliente(),
"Tu pedido " + pedido.id() + " fue aprobado",
"Gracias por confiar en nosotros");
smtpClient.enviar(email);
}
}
El problema no es la longitud, sino la mezcla de motivos de cambio. Cualquier ajuste en la base de datos o en el canal de notificación obliga a editar la misma clase, generando un grafo de dependencias defectuoso.
Una refactorización efectiva persigue la especialización. El objetivo no es reducir líneas, sino separar colaboraciones que puedan evolucionar de manera independiente. Se pueden aplicar pasos graduales como Extract Method, Move Method y Extract Class.
class ServicioAprobacion {
private final ValidadorPedido validador;
private final RepositorioPedido repositorio;
private final ServicioNotificacion notificador;
ServicioAprobacion(ValidadorPedido validador,
RepositorioPedido repositorio,
ServicioNotificacion notificador) {
this.validador = validador;
this.repositorio = repositorio;
this.notificador = notificador;
}
void aprobar(Pedido pedido) {
validador.verificar(pedido);
pedido.marcarAprobado();
repositorio.guardar(pedido);
notificador.notificarAprobacion(pedido);
}
}
Las colaboraciones específicas encapsulan los detalles de cada dependencia:
class RepositorioPedido {
private final DataSource dataSource;
RepositorioPedido(DataSource dataSource) {
this.dataSource = dataSource;
}
void guardar(Pedido pedido) { /* Implementación enfocada en persistencia */ }
}
class ServicioNotificacion {
private final SmtpClient smtpClient;
ServicioNotificacion(SmtpClient smtpClient) {
this.smtpClient = smtpClient;
}
void notificarAprobacion(Pedido pedido) { /* Enviar email */ }
}
El comando principal ahora delega en participantes cohesivos: las modificaciones en la persistencia o la notificación no arrastran al proceso de aprobación. A medida que el dominio crece se puede introducir una interfaz para abstraer la notificación y habilitar otras implementaciones como mensajes push o integraciones con Apache Kafka.
Para reorganizar un componente existente sin afectar la operación en producción conviene seguir un plan:
Antes de cerrar una iteración, repasar las siguientes preguntas ayuda a preservar el equilibrio:
La cohesión elevada convierte a la arquitectura en una colección de piezas claras y confiables. Al monitorear los indicadores y refactorizar de manera incremental, el diseño se mantiene flexible para incorporar cambios sin sacrificar la estabilidad.