La cohesión es una métrica que muestra qué tan estrechamente relacionados están los elementos dentro de un módulo o clase. Según Wikipedia, la alta cohesión es una característica de un módulo bien diseñado, donde todos los métodos y campos trabajan en una misma tarea. La cohesión afecta directamente la mantenibilidad del código y se contrapone al acoplamiento (coupling) — la interdependencia entre módulos.
Puntos clave
La cohesión es una métrica que evalúa qué tan lógicamente conectados están los métodos, campos y propiedades dentro de una clase o módulo. Un módulo altamente cohesivo realiza una tarea y contiene solo los elementos necesarios para llevarla a cabo. Un módulo con baja cohesión intenta hacer varias cosas a la vez — sus métodos están débilmente relacionados en significado.
En el contexto de la programación orientada a objetos, la cohesión está estrechamente vinculada al Principio de Responsabilidad Única (S). Si una clase tiene una responsabilidad clara, su cohesión suele ser alta. Si una clase maneja UI, lógica de negocio y redes al mismo tiempo — la cohesión es baja, y dicha clase debería dividirse en varias clases separadas con responsabilidades más específicas.
Comprender la cohesión ayuda a los desarrolladores a tomar decisiones de refactorización. Cuando ves un método en una clase que no utiliza ningún campo de la clase, es una señal de baja cohesión. Ese método está fuera de lugar en la clase, o la clase está mal diseñada. Aspirar a una alta cohesión es un esfuerzo continuo para mejorar la arquitectura en todos los niveles del código.
En la ingeniería de software se distinguen siete niveles de cohesión, ordenados del peor al mejor. Comprender esta escala permite evaluar objetivamente la calidad de un módulo y determinar la dirección para la refactorización. Cuanto mayor es el nivel, más mantenible y comprensible será el código.
Coincidental — el peor nivel, donde los elementos de un módulo se agrupan al azar sin ninguna conexión lógica. Ejemplo: una clase Utilities con métodos para formatear fechas, enviar correos y calcular descuentos. Esta clase no se puede entender sin leer todos sus métodos, y cambiar uno podría romper otros simplemente por estar juntos.
Lógica — los elementos realizan tareas lógicamente relacionadas pero fundamentalmente diferentes. Una clase con métodos parseJSON, parseXML y parseCSV está conectada lógicamente por el tema de “analización,” pero cada método hace un trabajo radicalmente distinto. El problema: al añadir un nuevo formato (YAML), la clase crece y su interfaz se vuelve inflada.
Temporal — los elementos se agrupan por tiempo de ejecución. Una clase AppInitializer que configura la base de datos, carga la configuración e inicializa análisis — todo esto ocurre al iniciar la aplicación, pero las tareas en sí no están relacionadas. Es mejor dividirlas en Initializers separados para cada área de responsabilidad.
Procedural — ocurre cuando los elementos se unen por una secuencia de ejecución. Un módulo de “procesamiento de pedidos” contiene métodos validateCart, processPayment y sendConfirmation — cada método se llama estrictamente después del anterior. Esto es mejor que la cohesión coincidental o lógica, pero aún no es ideal: cada paso podría extraerse en un módulo separado.
Comunicacional — los elementos trabajan con los mismos datos. Una clase UserService con métodos getUser, updateUser y deleteUser está unida por la entidad común User. Esto es significativamente mejor que la cohesión procedural: la clase tiene un dominio claro. La mayoría de las clases Repository en proyectos móviles tienen cohesión comunicacional.
Funcional — el nivel más alto, donde cada elemento del módulo participa en la realización de una sola tarea. Una clase PasswordValidator con un único método validate que verifica longitud, presencia de caracteres y complejidad de la contraseña es un ejemplo de cohesión funcional. Si esta clase cambia, es solo porque las reglas de validación de contraseñas han cambiado.
Alcanzar la cohesión funcional es el objetivo principal de la refactorización arquitectónica. Cada clase debe tener exactamente una razón para cambiar. En el desarrollo móvil, la cohesión funcional se logra extrayendo Use Cases separados, Vistas personalizadas, formateadores y validadores. Cada una de estas clases es un bloque de construcción completo con un área de responsabilidad clara.
Cohesión y acoplamiento son dos caras de la misma cualidad. Cuanto mayor es la cohesión dentro de un módulo, menor tiende a ser el acoplamiento entre módulos. Un sistema bien diseñado busca simultáneamente una alta cohesión interna y un acoplamiento externo débil. Este principio se reconoce como fundamental en la ingeniería de software desde la década de 1970.
La relación cohesión-acoplamiento puede entenderse como un equilibrio. Si un desarrollador sacrifica la cohesión combinando varias tareas en una clase, los módulos vecinos obtienen más dependencias — tienen que acceder a esta clase sobrecargada para diferentes propósitos, lo que aumenta el acoplamiento. Por el contrario, dividir en clases pequeñas y altamente cohesivas reduce los puntos de interacción entre módulos.
En la práctica, esto significa: cuando extraes una nueva clase con cohesión funcional, liberas a otros módulos de la necesidad de conocer los detalles de su implementación. Por ejemplo, extraer EncryptionManager en una clase separada con cohesión funcional proporciona a otros módulos una interfaz simple de encrypt/decrypt sin necesidad de entender los detalles del algoritmo de cifrado.
// Baja cohesión — la clase lo hace todo a la vez
class UserManager {
fun fetchAndSaveUser(id: String) { }
fun parseUserJson(json: String): User { }
fun displayUserName(user: User): String { }
fun validateEmail(email: String): Boolean { }
}
// Alta cohesión — cada clase resuelve una tarea
class UserRepository {
fun fetchUser(id: String): User { }
}
class UserJsonParser {
fun parse(json: String): User { }
}
class UserNameFormatter {
fun format(user: User): String { }
}
class EmailValidator {
fun isValid(email: String): Boolean { }
}
El ejemplo muestra la diferencia: UserManager tiene cohesión lógica — todos los métodos son sobre usuarios, pero cada uno hace un trabajo fundamentalmente diferente. Después de la refactorización, cada clase tiene cohesión funcional y el acoplamiento se reduce porque otros módulos dependen solo de la clase que necesitan, no de todo UserManager.
LCOM (Falta de Cohesión de Métodos) es la métrica más conocida para medir la cohesión de una clase. LCOM cuenta cuántos pares de métodos no comparten campos comunes. Un valor de 0 significa cohesión ideal (todos los métodos trabajan con los mismos campos), mientras que un valor alto indica baja cohesión. LCOM4 (una versión mejorada) considera conexiones transitivas a través de otros métodos.
En el desarrollo Android, las métricas de cohesión se pueden obtener mediante Detekt con la regla TooManyFunctions. Las clases con docenas de métodos que usan diferentes grupos de campos probablemente tienen baja cohesión. En iOS, SwiftLint tiene reglas file_length y function_body_length — indicadores indirectos: los archivos y métodos largos suelen señalar baja cohesión.
Un método de evaluación manual: hazte la pregunta “¿Esta clase cambiará por una razón o por múltiples razones?” Si puedes nombrar más de una razón independiente — la clase tiene baja cohesión. Una segunda prueba: “¿Se puede dividir esta clase en dos clases independientes?” Si es así — hazlo. Revisar la cohesión regularmente en las revisiones de código previene las clases Dios y reduce la deuda técnica.
El primer paso es aplicar el Principio de Responsabilidad Única. Cada clase debe tener una responsabilidad clara. Si una clase tiene un método que no se relaciona con su tarea principal, extráelo a una clase separada. La técnica Extract Class o Extract Delegate en los IDE automatiza este proceso. Después de la extracción, verifica si la clase original se ha vuelto más enfocada.
El segundo paso es usar el patrón Facade para simplificar la interfaz. Si una clase proporciona 20 métodos pero los clientes solo usan 3–4, la clase puede tener baja cohesión — ofrece demasiada funcionalidad diversa. Agrupa los métodos por tema, extrae clases separadas para cada grupo y convierte la clase original en una fachada o elimínala.
El tercer paso es prestar atención a los grupos de campos. Si una clase tiene campos que solo son usados por un subconjunto de métodos — es un indicador de baja cohesión. Divide la clase por grupos de campos. Por ejemplo, si una clase contiene campos userRepository, networkClient y analyticsTracker, pero el primer grupo de métodos solo usa userRepository mientras que el segundo usa networkClient — son dos clases diferentes.
El cuarto paso es evitar crear clases “utility” con métodos static arbitrarios. Cada método static que se encuentra en una clase Utils o Helpers es candidato a ser extraído en una clase especializada. FormatUtils.dateToString es mejor moverlo a DateFormatter, y ValidationUtils.isValidEmail a EmailValidator. Esto aumenta la cohesión de cada clase y hace que el código sea autodocumentado.
Preguntas frecuentes
Casi siempre. La cohesión funcional hace que el código sea claro y predecible. Sin embargo, llevarlo al extremo puede provocar una fragmentación excesiva: crear una clase separada para cada operación, haciendo que la arquitectura sea demasiado compleja. El equilibrio son unas pocas clases por funcionalidad, cada una con cohesión funcional.
La cohesión es una métrica de consistencia interna dentro de un solo módulo o clase. La modularidad es un principio arquitectónico donde una aplicación se divide en módulos físicos. La alta cohesión es un objetivo tanto al diseñar clases individuales como módulos completos.
Detekt para Android y Xcode Analyzer para iOS resaltan clases con una cantidad sospechosamente grande de métodos o campos. IntelliJ IDEA y AppCode tienen visualización de dependencias — puedes ver el grafo de conexiones y detectar clases con baja cohesión. SonarQube calcula las métricas LCOM automáticamente.
Sí. Una interfaz con métodos connect, disconnect e isConnected tiene alta cohesión — todos los métodos se relacionan con la gestión de conexiones. Una interfaz con connect, parseData y renderUI tiene baja cohesión. El Principio de Segregación de Interfaces (SOLID) exige crear interfaces estrechamente enfocadas con alta cohesión.
Hazte tres preguntas: ¿Se puede describir el propósito de la clase en una oración? ¿Todos los métodos respaldan este propósito? ¿Hay campos en la clase que no son usados por algunos métodos? Si la respuesta a alguna pregunta es no — la cohesión es baja y la clase debe dividirse.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también