Clases en código limpio: responsabilidad única y cohesión
Abres una clase para cambiar una cosa pequeña y te encuentras con que también hace otras cinco. No sabes qué vas a romper al tocarla. Casi siempre el problema no es el código de cada método, sino que la clase hace demasiadas cosas.
Piensa en un mando universal con botones para la tele, el aire acondicionado y la persiana. Si cambias cualquiera de los tres aparatos, tienes que tocar el mando. El mando de la tele, en cambio, solo cambia si cambias de tele. Una clase limpia es ese mando de un solo aparato: tiene un solo motivo para cambiar.
Este artículo resume las ideas del capítulo «Classes» de Clean Code, de Robert C. Martin, explicadas con palabras propias y con un ejemplo en Ruby. Forma parte de la guía de código limpio (clean code), con todos los principios y un ejemplo de cada uno.
Organización de una clase
Según el capítulo, que sigue la convención de Java, una clase declara sus partes en este orden:
- Constantes estáticas públicas.
- Variables estáticas privadas.
- Variables de instancia privadas.
- Funciones públicas.
Las funciones privadas de apoyo van justo después de la función pública que las usa. Así se cumple la regla descendente, que permite leer el programa como un artículo de periódico: primero lo importante y, debajo, los detalles.
Cada lenguaje tiene su propia convención. En Ruby, la guía de estilo de la comunidad propone otro orden: primero include y extend, después constantes, macros como attr_reader, métodos de clase, initialize, los métodos públicos y, al final, los privados. Lo importante no es qué orden eliges, sino que todo el equipo use el mismo.
Encapsulación
La idea es que las variables y las funciones de apoyo sean privadas: lo que no forma parte de lo que la clase ofrece no debería verse desde fuera.
No es una regla absoluta. A veces se relaja la visibilidad para poder probar algo, pero antes conviene buscar otra forma de mantenerlo privado. Abrir la clase es el último recurso.
Tamaño: se mide en responsabilidades
Las clases deben ser pequeñas. Con las funciones medimos el tamaño contando líneas; con las clases, contando responsabilidades.
No depende del número de métodos. Puede haber una clase con muy pocos métodos y demasiadas responsabilidades, y otra con muchos métodos y la responsabilidad justa.
La prueba del nombre
Si no encuentras un nombre conciso para una clase (que diga lo que hace con pocas palabras y bien elegidas), seguramente sea demasiado grande. Cuanto más ambiguo es el nombre, más probable es que la clase tenga muchas responsabilidades. Nombres como Gestor, Procesador o Utils suelen ser una señal.
Otra prueba del capítulo: deberías poder describir la clase en unas 25 palabras sin usar «si», «o», «y» ni «pero». Cada una de esas palabras suele delatar una responsabilidad de más.
Principio de responsabilidad única (SRP)
El principio de responsabilidad única (Single Responsibility Principle, SRP) dice que una clase o módulo debe tener un solo motivo para cambiar. En palabras de Robert C. Martin: «each software module should have one and only one reason to change» (cada módulo debería tener una, y solo una, razón para cambiar).
En un artículo de 2014 lo aclara con una regla práctica: junta lo que cambia por las mismas razones y separa lo que cambia por razones distintas. Y añade que esas razones son personas: quienes piden los cambios.
El SRP es la S de los principios SOLID; allí tienes los otros cuatro con ejemplos.
Crear software que funcione y crear software limpio son dos actividades diferentes. Mientras programamos, nos centramos en que el código funcione más que en que esté ordenado, y es normal. El problema es dar el trabajo por terminado cuando el programa funciona y pasar al siguiente problema, en vez de volver atrás y refactorizar (reorganizar el código sin cambiar lo que hace) para separar responsabilidades.
Todos los sistemas tienen mucha lógica y complejidad. Lo mejor que puedes hacer es organizarla en piezas pequeñas para que quien llegue sepa dónde buscar y la entienda.
Muchas clases pequeñas, no unas pocas enormes.
En resumen:
- Los sistemas deben estar formados por muchas clases pequeñas, no por unas pocas de gran tamaño.
- Cada clase pequeña:
- Encapsula una única responsabilidad.
- Tiene un solo motivo para cambiar.
- Colabora con otras para conseguir lo que el sistema necesita.
Cuando una clase acumula tantas responsabilidades que casi todo pasa por ella, tienes un God Object, uno de los antipatrones más comunes.
Cohesión
La cohesión mide cuánto tienen que ver entre sí las partes de una clase. Cuantas más variables de instancia usa un método, más unido está a su clase. Buscamos cohesión alta: que los métodos y las variables dependan unos de otros y funcionen como un todo.
Mira esta clase:
# Una clase con dos grupos de métodos que no se tocan entre sí
class Biblioteca
def initialize
@libros = []
@socios = []
end
def anadir_libro(titulo) = @libros.push(titulo)
def total_libros = @libros.size
def dar_de_alta(socio) = @socios.push(socio)
def socio?(nombre) = @socios.include?(nombre)
end
Tiene dos grupos de métodos: unos solo usan @libros y otros solo usan @socios. Ningún método necesita las dos variables. Es baja cohesión: dos clases compartiendo un mismo fichero.
Pasa a menudo al trocear funciones grandes. Para no pasar tantos parámetros, se convierten en variables de instancia que solo usan unos pocos métodos. Cuando lo veas, hay al menos una clase intentando salir de la grande. Sepárala:
# Cada clase tiene sus datos y los métodos que los usan
class Catalogo
def initialize
@libros = []
end
def anadir(titulo) = @libros.push(titulo)
def total = @libros.size
end
class RegistroDeSocios
def initialize
@socios = []
end
def dar_de_alta(nombre) = @socios.push(nombre)
def socio?(nombre) = @socios.include?(nombre)
end
Biblioteca salen dos grupos que no se tocan; al separarlos, cada clase usa sus datos en todos sus métodos.Ahora cada clase usa todos sus datos en todos sus métodos, y cada una tiene un nombre que se explica solo. Además, se prueban por separado:
require "minitest/autorun"
require_relative "despues"
class CatalogoTest < Minitest::Test
def test_cuenta_los_libros_anadidos
catalogo = Catalogo.new
catalogo.anadir("Clean Code")
catalogo.anadir("Refactoring")
assert_equal 2, catalogo.total
end
end
class RegistroDeSociosTest < Minitest::Test
def test_reconoce_a_un_socio_dado_de_alta
registro = RegistroDeSocios.new
registro.dar_de_alta("Ana")
assert registro.socio?("Ana")
refute registro.socio?("Luis")
end
end
Organizar las clases para el cambio
En casi todos los sistemas el cambio es continuo: las necesidades cambian y el código también. Cada cambio supone un riesgo de que otra parte del sistema deje de funcionar.
Con responsabilidad única, ese riesgo se reduce mucho: cambiar una clase no debería afectar a las que hacen otra cosa. Y probar la lógica resulta más sencillo, porque las clases están aisladas unas de otras.
Debemos estructurar los sistemas para ensuciarlos lo menos posible al añadir funciones o hacer cambios. En un sistema ideal, las funciones nuevas se incorporan ampliando el código, no modificando el que ya existe. Es lo que se conoce como principio abierto/cerrado: abierto a la extensión, cerrado a la modificación.
El capítulo añade una idea más: que las clases dependan de abstracciones (una interfaz, un contrato) y no de detalles concretos. Así puedes cambiar un detalle, como una base de datos o un servicio externo, sin tocar la clase que lo usa, y sustituirlo por una versión de prueba en los tests.
Una idea parecida, a escala de aplicación, es la del patrón MVC (modelo vista controlador): separa los datos, la pantalla y la coordinación porque cambian por motivos distintos.
Y cuando programas con IA
Esto es criterio propio del enfoque IA Limpia, no algo que diga el libro. Un asistente de IA escribe código donde le indicas y como le pides. Si le pides «añade esto» sin más, lo más cómodo es meterlo en la clase que ya tiene abierta, y así la clase acaba siendo un mando universal.
- Antes de aceptar un cambio, haz la prueba de las 25 palabras: ¿la clase se sigue describiendo sin «y» ni «pero»?
- Pregúntate quién pediría cambiar ese código. Si son personas distintas, pide que lo separe en clases distintas.
- Separa con los tests en verde: primero tests que fijen el comportamiento y después la división, paso a paso.
Lista para revisar una clase
- Tiene un nombre concreto, sin «Gestor», «Manager» ni «Utils».
- Se describe en unas 25 palabras sin «si», «o», «y» ni «pero».
- Tiene un solo motivo para cambiar.
- Sus métodos usan sus variables de instancia; no hay grupos aislados.
- Lo que no ofrece hacia fuera es privado.
- Cada dato se guarda en un solo sitio.
Si quieres seguir con la serie, el siguiente paso natural son las funciones en código limpio: lo que aquí se aplica a clases, allí se aplica a cada método.
Fuentes
- Robert C. Martin, Clean Code (2008), capítulo 10, «Classes». Las ideas se explican con palabras propias; no hay citas literales del libro.
- Robert C. Martin, «The Single Responsibility Principle», 8 de mayo de 2014. Cita literal comprobada en noviembre de 2026.
- Ruby Style Guide, sección «Consistent Classes», consultada en noviembre de 2026.
- El ejemplo y sus tests se han ejecutado con Ruby 4.0.9 y Minitest 6.0.0 (imagen oficial de Docker
ruby:latest).