En el mundo del desarrollo de software, los principios SOLID son recibidos como las tablas de la ley. Nos enseñan que, si los seguimos, nuestro código será robusto, escalable y digno de una mención en los anales de la ingeniería. Sin embargo, hay una verdad incómoda que solemos ignorar bajo el entusiasmo de un nuevo proyecto: la abstracción no es gratis.
Como desarrolladores (y especialmente cuando asumimos roles de liderazgo técnico), a veces olvidamos que nuestro objetivo final no es construir una catedral arquitectónica, sino entregar valor.
El “Costo por Capa”
Cada interfaz, cada clase abstracta y cada inyección de dependencia que añadimos a un sistema actúa como un impuesto cognitivo.
- Legibilidad: Seguir el flujo de ejecución se vuelve un ejercicio de arqueología de archivos.
- Mantenimiento: Un cambio simple en la lógica requiere tocar tres interfaces y cinco implementaciones.
- Tiempo: El time-to-market se dilata mientras discutimos si una clase cumple estrictamente con el Principio de Responsabilidad Única (SRP).
La paradoja de la arquitectura: Muchas veces, por intentar hacer un código “fácil de cambiar en el futuro”, terminamos haciendo que sea “imposible de entender en el presente”.
Los principios bajo la lupa del pragmatismo
Analicemos cómo la aplicación dogmática de SOLID puede jugarnos en contra:
1. Responsabilidad Única (SRP) y la fragmentación infinita
El SRP dice que una clase debe tener una sola razón para cambiar. Llevado al extremo, esto genera un ecosistema de micro-clases de cinco líneas.
- El problema: La lógica de negocio se fragmenta tanto que es imposible entender el “big picture”. El código se vuelve una sopa de objetos donde la cohesión brilla por su ausencia.
2. El Principio Abierto/Cerrado (OCP) y la adivinación
El OCP nos pide que el código esté abierto a la extensión pero cerrado a la modificación. Esto suele derivar en el uso excesivo del Patrón Strategy o de herencias complejas para escenarios que quizás nunca cambien.
- El problema: Estamos resolviendo problemas que no existen. Si no tienes la certeza de que una lógica va a variar en tres formas distintas, un simple
ifo unswitches, a menudo, la solución más elegante y mantenible.
3. Inversión de Dependencias (DIP): ¿Interfaces para todo?
Es común ver proyectos en .NET o PHP donde absolutamente cada clase tiene su respectiva interfaz (IUserService, IOrderRepository).
- El problema: Si solo tienes una implementación y no planeas cambiarla (por ejemplo, nunca vas a dejar de usar SQL Server por una base de datos de grafos de la noche a la mañana), la interfaz es ruido. Solo añade una capa más de navegación en el IDE.
YAGNI: Tu mejor escudo contra el Over-engineering
La regla de oro debería ser YAGNI (You Ain’t Gonna Need It). Antes de crear una jerarquía de clases abstractas para gestionar diferentes tipos de pagos, pregúntate: ¿Hoy, en este sprint, necesito cobrar con algo que no sea tarjeta de crédito?
Si la respuesta es no, programa para hoy. El buen código no es el que prevé todos los futuros posibles, sino el que es lo suficientemente simple como para ser refactorizado mañana cuando el futuro realmente llegue.
La Regla de Tres
Una buena heurística es la Rule of Three: No intentes abstraer un patrón hasta que no lo hayas visto repetido al menos tres veces en el código. La primera vez, haz que funcione. La segunda vez, siente un poco de vergüenza por duplicar. La tercera vez, refactoriza y abstrae.
El rol del Líder Técnico: Saber cuándo decir “No”
Como líderes, nuestra responsabilidad es balancear la calidad técnica con la velocidad de entrega. Un equipo senior no es aquel que aplica los patrones más complejos de los libros de diseño, sino el que sabe cuándo la simplicidad es la mayor sofisticación.
Escribir código complejo es fácil. Escribir código simple, que respete lo justo de SOLID para no romperse, pero que sea legible para un junior que entra mañana, es la verdadera maestría.
Código: Antes y Después
Para que este concepto no se quede solo en palabras, vamos a verlo con un ejemplo clásico en PHP: enviar un correo de bienvenida a un usuario nuevo.
Imagina que estamos en la semana 1 de un proyecto (un MVP). Aquí es donde solemos pecar de “arquitectos de élite”.
1. El Enfoque “Over-engineered” (Sobrediagramado)
Siguiendo SOLID al pie de la letra, alguien podría construir esto para enviar un simple mail:
PHP
// 1. La interfaz (DIP)
interface MessageProviderInterface {
public function send(string $to, string $subject, string $body): bool;
}
// 2. La implementación concreta
class SendGridProvider implements MessageProviderInterface {
public function send($to, $subject, $body): bool {
// Lógica compleja de la API de SendGrid
return true;
}
}
// 3. Un DTO para el mensaje (SRP)
class WelcomeMessage {
public function __construct(private User $user) {}
public function getBody(): string { return "Hola {$this->user->name}"; }
}
// 4. El servicio que orquestra todo
class UserNotificationService {
public function __construct(private MessageProviderInterface $provider) {}
public function notifyWelcome(User $user) {
$msg = new WelcomeMessage($user);
$this->provider->send($user->email, 'Bienvenido', $msg->getBody());
}
}
El costo: Para enviar un mail, creamos 4 archivos, una interfaz y un sistema de inyección de dependencias. Si el cliente mañana dice “mejor enviemos el mail desde el controlador directamente porque tenemos prisa”, tenemos que deshacer una catedral.
2. El Enfoque Pragmático (YAGNI)
Si solo necesitas enviar un mail y no tienes planes reales de cambiar de proveedor en los próximos 6 meses, esto es mucho más honesto:
PHP
class Mailer {
public function sendWelcome(User $user) {
// Usamos una librería simple o la función mail()
mail($user->email, 'Bienvenido', "Hola {$user->name}");
}
}
// En tu controlador o servicio de registro:
$this->mailer->sendWelcome($user);
Análisis de la “Trampa”
¿Por qué el primer ejemplo es peligroso en etapas tempranas?
- Indirección innecesaria: Si quiero saber qué pasa cuando se envía un mail, en el ejemplo 1 tengo que saltar por 3 archivos. En el ejemplo 2, voy directo al grano.
- Mantenimiento de mocks: Para testear el ejemplo 1, necesito mockear la interfaz, configurar el contenedor de servicios, etc. Para un MVP, a veces un test de integración simple sobre una clase concreta es 10 veces más rápido.
- Falsa flexibilidad: Decimos “uso una interfaz por si cambio SendGrid por Mailchimp”. Realidad: Casi nunca cambias el proveedor principal de infraestructura a mitad de un proyecto, y si lo haces, refactorizar una clase simple (
Mailer) te llevará 20 minutos, lo mismo que te tomó escribir toda la arquitectura innecesaria del ejemplo 1.
Conclusión
No me malinterpretes: SOLID es fundamental. Pero debe ser una brújula, no una jaula. No permitas que la pureza arquitectónica te impida ver el bosque. Al final del día, el mejor código es el que se puede borrar, cambiar y entender sin necesidad de un mapa de tres dimensiones.
Menos arquitectura de manual y más sentido común.
