Hace unos días estaba viendo un documental sobre la historia de Python —si no lo has visto, ya estás tardando— y apareció una frase que me hizo reflexionar:
Complex is better than complicated
Es parte del Zen of Python, escrito por Tim Peters, y refleja muy bien la filosofía del lenguaje: aceptar la complejidad interna cuando es necesaria, pero sin complicar el lenguaje para el usuario. Esa frase, en realidad, me acompaña desde hace años en mi trabajo como ingeniero: la diferencia entre lo complejo y lo complicado es sutil, pero importante de comprender y saber aplicar. Lo cambia todo.

Complejidad esencial vs. accidental
La complejidad es inevitable. Vivimos en un tiempo donde cualquier software está montado sobre capas y capas de abstracción: hardware, hipervisores, contenedores, redes virtuales, múltiples centros de datos por región, múltiples regiones… Cada vez que desplegamos una aplicación en la nube, nos apoyamos en una pila enorme de sistemas que hacen posible algo que, desde fuera, parece sencillo. Pero debajo de esa simplicidad aparente, hay complejidad esencial.
Fred Brooks, en su paper No Silver Bullet, distingue entre complejidad esencial (la que proviene del propio problema) y complejidad accidental (la que introducimos nosotros). En ingeniería del software nos encontramos con ambas constantemente. Lo esencial no se puede evitar, pero lo accidental sí. Y es nuestra responsabilidad reducirla.
En el campo de Platform Engineering, que es el mío, un ejemplo claro es Kubernetes. Este software es, sin lugar a dudas, muy complejo: orquesta contenedores, gestiona redes, balancea carga, repara fallos, escala automáticamente… y lo hace a nivel planetario si queremos. Esa complejidad está justificada cuando hablamos de escalar múltiples servicios en entornos multi-región o multi-tenant. Pero también he visto organizaciones que lo adoptan para proyectos pequeños, donde se convierte en una carga desproporcionada y difícil de gestionar. Están añadiendo complejidad accidental a la solución del problema.
En cambio, cuando el problema lo exige, Kubernetes se convierte en la navaja multiusos que necesitas. He trabajado en plataformas que debían gestionar decenas de microservicios desplegados en varios países, con alta disponibilidad y requisitos de seguridad, y SLAs, estrictos. Antes, cada equipo gestionaba sus propios servidores con herramientas diferentes, generando un caos a nivel operativo para el equipo de infraestructura. Migrar todo a Kubernetes —con la debida formación del personal— permitió unificar despliegues, estandarizar pipelines, aplicar políticas de seguridad de forma centralizada, escalar dinámicamente, y sobre todo, empoderar a los equipos de desarrollo a operar sus servicios en producción. La complejidad estaba ahí, pero se tradujo en simplicidad operativa y en mayor velocidad de entrega.
Lo complicado
Un sistema complicado no es necesariamente complejo, pero sí es difícil de entender, de usar o de operar. Para el usuario, significa interfaces confusas y una experiencia frustrante. Para el ingeniero, significa una curva de aprendizaje elevada y procesos que requieren demasiado esfuerzo para tareas que deberían ser simples.
Las consecuencias son importantes: fricción en el día a día, mayor coste de mantenimiento, dependencia excesiva de especialistas, errores frecuentes y una velocidad de entrega mucho menor.
Según la Ley de conservación de la complejidad, toda solución tiene un nivel mínimo de complejidad que alguien debe asumir. En Platform Engineering, esto se traduce en una pregunta clave: ¿quién carga con esa complejidad? ¿El equipo de plataforma, que puede abstraerla mediante automatización y buenas herramientas, o los equipos de desarrollo, que tendrían que lidiar con ella directamente? La decisión correcta marca la diferencia entre un ecosistema sano y otro plagado de fricciones. Por supuesto, es responsabilidad del equipo de Platform Engineering abstraer la complejidad donde sea posible, allanando el camino de los equipos de desarrollo, cuyo objetivo tiene que ser el de entregar valor a los clientes. Pero eso no implica que ellos no tengan que asumir parte de esa responsabilidad.
La clave está en aceptar la complejidad esencial cuando es inevitable, pero no resignarnos a la accidental ni a lo complicado. Un buen diseño consiste en abrazar la complejidad de fondo mientras ofrecemos simplicidad en la superficie. Como ingenieros, es tentador añadir pasos, configuraciones o capas que no aportan valor, porque es más fácil que enfrentarse a la tarea de simplificar. Pero cuando mantenemos la complejidad bajo control y eliminamos la complicación, logramos soluciones efectivas y elegantes.
¡Muchas gracias por leerme!