Skip to content

Calidad del Software I: Métricas Asociadas a la Cohesión

Publicado:
1 min de lectura diseño de software, cohesión

Tabla de contenido

Ver contenido

Introducción

¿La calidad depende del observador? En el software, la calidad es un concepto amplio y multifacético: no todos la perciben de la misma forma. Lo que para algunos usuarios representa una mejora, para otros puede ser una pérdida.

En términos generales, los usuarios esperan un producto que consideren justo en relación a su inversión. Para los ingenieros, en cambio, la calidad implica que el software sea robusto, flexible y fácil de mantener.

Por tanto, la calidad no se evalúa únicamente por su funcionalidad exterior, sino también por la forma en que está construido. Medir la calidad estructural permite tomar decisiones fundamentadas sobre el diseño.

¿Por qué cuantificar la calidad del diseño de software?

Si bien los ingenieros tienen criterio para identificar problemas de diseño, ese juicio puede ser subjetivo en muchos casos. Las métricas, por otro lado, permiten detectar problemas tangibles y justificar decisiones arquitectónicas con datos.

Métricas de Cohesión

LCOM (Lack of Cohesion of Methods)

Existen varias versiones de esta métrica. Nos centraremos en LCOM4, la más moderna y útil para evaluar la cohesión de clases.

LCOM4 evalúa qué tan cohesiva es una clase, basándose en cuántos grupos de responsabilidades (métodos) acceden a datos comunes de una instancia.

Cálculo de LCOM4

Ejemplo en Ruby

Revisemos este caso en Ruby (considere que está hecho así por fines didácticos):

La clase Route modela una ruta de transporte en un sistema simulado. Su objetivo es representar una secuencia de estaciones por las que pasa un vehículo (como un autobús), y gestionar tanto el avance en la ruta como la generación de pasajeros.

Y tiene como responsabilidades:

class Route
  def initialize(name, stops, distances, num_stops, generator)
    @name = name
    @stops = stops
    @distances_between = distances
    @num_stops = num_stops
    @generator = generator
    @destination_stop_index = 0
    @destination_stop = nil
  end

  def update
    generate_new_passengers
    @stops.each(&:update)
  end

  def is_at_end?
    @destination_stop_index >= @num_stops
  end

  def next_stop
    @destination_stop_index += 1
    if @destination_stop_index < @num_stops
      @destination_stop = @stops[@destination_stop_index]
    else
      @destination_stop = @stops.last
    end
  end

  def get_destination_stop
    @destination_stop
  end

  def get_total_route_distance
    @distances_between.sum
  end

  def get_next_stop_distance
    @distances_between[@destination_stop_index - 1]
  end

  def generate_new_passengers
    @generator.generate_passengers
  end
end

Al analizar la clase, podemos notar que tiene dos grupos de responsabilidades (métodos):

Si vamos a la definición, podemos notar que el uso de los atributos resaltan bastante bien esta división de responsabilidades.

El resultado de LCOM4 es igual a 2, lo que nos indica que la clase tiene 2 responsabilidades, lo que sugiere que podría refactorizarse.

Propuesta de división en clases

A continuación, refactorizaremos la lógica de la clase Route en dos componentes:

Es clave reconocer que la lógica de update implica modificaciones dinámicas en el estado del atributo @stops (estaciones). Mientras que RouteTracker se enfoca en el seguimiento estructural de la ruta —como el avance y el cálculo de distancias—, es PassengerManager quien asume la responsabilidad de manipular el estado de los pasajeros y en relación a las estaciones.

class RouteTracker
  def initialize(stops, distances, num_stops)
    @stops = stops
    @distances_between = distances
    @num_stops = num_stops
    @destination_stop_index = 0
    @destination_stop = nil
  end

  def is_at_end?
    @destination_stop_index >= @num_stops
  end

  def next_stop
    @destination_stop_index += 1
    @destination_stop = if @destination_stop_index < @num_stops
                          @stops[@destination_stop_index]
                        else
                          @stops.last
                        end
  end

  def get_destination_stop
    @destination_stop
  end

  def get_total_route_distance
    @distances_between.sum
  end

  def get_next_stop_distance
    @distances_between[@destination_stop_index - 1]
  end
end
class PassengerManager
  def initialize(generator, stops)
    @generator = generator
    @stops = stops
  end

  def generate_new_passengers
    @generator.generate_passengers
  end

  def update
    generate_new_passengers
    @stops.each(&:update)
  end
end
class Route
  def initialize(name, stops, distances, num_stops, generator)
    @name = name
    @tracker = RouteTracker.new(stops, distances, num_stops)
    @passenger_manager = PassengerManager.new(generator, stops)
  end

  def update
    @passenger_manager.update
  end

  def is_at_end?
    @tracker.is_at_end?
  end

  def next_stop
    @tracker.next_stop
  end

  def get_destination_stop
    @tracker.get_destination_stop
  end

  def get_total_route_distance
    @tracker.get_total_route_distance
  end

  def get_next_stop_distance
    @tracker.get_next_stop_distance
  end

  def generate_new_passengers
    @passenger_manager.generate_new_passengers
  end
end

Como puedes observar, ahora la clase Route actúa como una API que delega responsabilidades a RouteTracker y PassengerManager, mejorando la testabilidad y la separación de responsabilidades.

Conclusión

El cálculo de métricas asociadas a la cohesión es una herramienta más para tomar decisiones de diseño basadas en datos. No debemos depender exclusivamente de una sola métrica, sino utilizarla como guía complementaria a nuestro criterio.

LCOM4 nos ayuda a identificar una posible violación del principio de responsabilidad única (SRP). En este ejemplo, propusimos una refactorización que separa las responsabilidades de manera más clara. Como toda métrica, su valor está en cómo se interpreta y aplica.


¿Buscas soluciones tecnológicas personalizadas?

Contáctame para consultorías IT y desarrollo a medida

Anterior post
Arquitectura de Software III: Principios de Paquetes
Siguiente post
Complejidad del Software I: Mecanismos Fundamentales