Haz lo más simple que pueda funcionar
(seangoedecke.com)- En diseño de software, elegir siempre “lo más simple que pueda funcionar” es un consejo eficaz
- Un gran diseño de sistemas no parece grandilocuente; en realidad resuelve problemas usando la menor cantidad posible de componentes
- Al buscar soluciones simples, el principio YAGNI (You Aren't Gonna Need It) puede adoptarse como filosofía central de diseño para expandir de forma gradual solo cuando aparezcan nuevos requisitos
- Aunque existe debate sobre la definición de “simplicidad”, lo más cercano a ella es un sistema con pocos componentes, acoplamiento interno débil y estabilidad
- La obsesión con la escalabilidad excesiva vuelve menos flexible la base de código, por lo que un diseño simple y fiel a los requisitos reales resulta más ventajoso a largo plazo
La búsqueda de lo más simple que pueda funcionar
Al diseñar sistemas de software, es importante hacer “lo más simple que pueda funcionar”. Este enfoque puede aplicarse a casi cualquier situación: corregir bugs, dar mantenimiento a sistemas existentes o diseñar una arquitectura nueva. Muchos ingenieros sueñan con un sistema “ideal”: bien organizado, casi infinitamente escalable y limpiamente distribuido. Pero en la práctica, suele ser más efectivo comprender a fondo el sistema actual y luego elegir la solución más fácil.
La simplicidad está subestimada
- El diseño de sistemas requiere saber usar diversas herramientas como servidores de aplicaciones, proxies, bases de datos, cachés y colas
- Los ingenieros junior suelen querer usar muchas tecnologías para construir estructuras complejas, pero la verdadera pericia está más bien en quitar lo innecesario
- Un gran diseño de software se ve sobrio y hasta da la impresión de que el problema podía resolverse más fácil de lo esperado
- Por ejemplo, Unicorn y la API REST de Rails aprovechan funciones básicas de Unix para lograr una estructura mínima que ofrece garantías clave (aislamiento, escalado, recuperación, etc.), y por eso son excelentes desde el punto de vista del diseño
La forma de pensar para implementar de manera simple
- Por ejemplo, al agregar rate limiting a una aplicación en Golang, se puede usar almacenamiento externo como Redis, pero si no es estrictamente necesario, primero puede intentarse un método más simple como contadores en memoria
- Si el método simple es suficiente, puede posponerse la incorporación de infraestructura pesada
- Eso permite un despliegue de desarrollo gradual del sistema, ampliándolo solo cuando aparezcan nuevos requisitos reales
- Es un enfoque que pone el principio YAGNI como máxima prioridad del diseño
Las trampas de la simplicidad y sus límites reales
1. El fenómeno Big Ball of Mud
- Si se intenta resolver de inmediato cada requisito, existe el riesgo de terminar con una base de código tipo “big ball of mud” compleja y difícil de mantener
- Sin embargo, un arreglo improvisado (
hack) no es simplicidad; al contrario, genera más complejidad para entender y mantener - Para encontrar una solución realmente simple, hay que comparar muchos enfoques y hacer ingeniería sistemática de verdad
2. La definición de simplicidad
- No es fácil lograr consenso sobre qué significa exactamente “simplicidad”
- En general, un sistema simple tiene pocas partes móviles (elementos en funcionamiento), bajo acoplamiento entre componentes e interfaces claras
- Ejemplo real: los procesos de Unix y Unicorn no comparten memoria, lo que eleva su simplicidad; en comparación con Puma o Redis, su conectividad interna es menor
- Cuando la elección es ambigua, puede considerarse que lo más simple es lo que requiere menos mantenimiento
3. Crítica a la obsesión por la escalabilidad
- Un “método simple” puede no ser adecuado para tráfico a gran escala
- Pero diseñar de antemano una solución compleja pensando en una futura expansión drástica casi siempre es esfuerzo desperdiciado
- La mayoría del código en realidad solo necesita prepararse para un aumento de tráfico de unas 2 a 5 veces; más allá de eso, lo razonable es responder cuando el problema realmente ocurra
- Un diseño excesivamente centrado en la escalabilidad daña la flexibilidad del código; dividir la estructura más de lo necesario puede dificultar la implementación de ciertas funciones e incluso exigir manejo complejo de transacciones
Conclusión
- Con el tiempo, uno se vuelve más pesimista respecto a la capacidad de predecir los requisitos futuros de un sistema
- En la práctica, entender con precisión el estado actual del sistema ya es bastante difícil, y ese es el principal obstáculo para un buen diseño
- En el desarrollo de software hay, a grandes rasgos, dos formas de trabajar
- Diseñar anticipando requisitos futuros
- Repetir lo más simple que pueda funcionar, siendo fiel a los requisitos actuales
- La segunda suele ser más efectiva en la práctica, y una forma de pensar centrada en YAGNI y la simplicidad produce mejores diseños a largo plazo
Apéndice: discusión en Hacker News y origen del término
- Frente a la opinión de que la simplicidad arquitectónica pierde sentido a gran escala, el autor sostiene justamente lo contrario: que mientras mayor es la escala, más importante se vuelve una estructura simple (para enfrentar la complejidad de las interacciones entre funciones)
- La expresión “Do the simplest thing that could possibly work” fue acuñada por Ward Cunningham y Kent Beck
1 comentarios
Opinión de Hacker News
Creo que este enfoque puede funcionar bien en dominios simples. Pero incluso después de trabajar mucho tiempo en grandes empresas tecnológicas, siempre me sorprende la complejidad que se requiere. Hasta los problemas de negocio más simples toman más de un año en resolverse, o se rompen con frecuencia por una gran cantidad de casos límite y problemas de escala. Quienes proclaman la simplicidad probablemente no han tenido experiencia a gran escala. Incluso si revisas una base de código de 10 años, hay tantas cosas que considerar que las reescrituras también suelen fracasar. Como en la analogía de Chesterton's Fence, hace falta la sabiduría de no eliminar algo a la ligera si no entiendes por qué existe
Creo que este es un malentendido clásico que surge porque los ingenieros de software no se comunican bien entre sí. Como dice el título del artículo, se trata de hacer "lo más simple posible". En problemas complejos, la complejidad es inevitable, pero el punto es tener cuidado de no cometer el error de volverlos innecesariamente más complejos. No significa evitar por completo la complejidad, sino cuidarse del hábito de exagerarla
Como señalas, la complejidad puede no deberse al dominio en sí. Puede ser un problema causado por un mal diseño de software. Si está lleno de dependencias y side effects, es porque no se ha gestionado bien la separación de responsabilidades ni el acoplamiento. Refactorizar se vuelve casi imposible y, si la cultura organizacional no mejora, durante el refactor solo se terminan pegando problemas nuevos encima. Aun así, con separación de responsabilidades y composiciones simples también se pueden resolver problemas complejos. Es difícil, pero mientras más firmemente tengan esta perspectiva los desarrolladores senior, mayores son las probabilidades de éxito
Mencionan que el autor es staff engineer en GitHub, así que creo que esta persona sí tiene experiencia suficiente con sistemas a gran escala
Los sistemas legacy existen en los bordes. Incluso en sistemas reales, igual que en un espacio multidimensional donde, a medida que aumentan las dimensiones, los puntos se concentran en la superficie límite, la mayoría de los usuarios reales trabajan cerca de los límites del sistema. El sistema viejo existente es, al final, el que acomoda de forma óptima todos esos bordes
En mi trabajo anterior, gran parte de la complejidad venía de intentos de refactorización o mejora que fracasaron, se abandonaron a medio camino o quedaron incompletos. A menudo me preguntaba si habríamos heredado un sistema más simple si eso se hubiera evitado desde el inicio. No digo que no haya que refactorizar ni mejorar, sino que creo que hay que planearlo de forma que cubra con certeza el 100% de los casos de uso, asegurar presupuesto e hitos, y luego garantizar una mejora gradual
Ojalá hubieran explicado el origen de la expresión "lo más simple posible". Esta frase era un principio que el inventor del wiki, Ward Cunningham, y Kent Beck usaban mucho cuando trabajaban juntos a finales de los 80. Mientras programaban se lo recordaban mutuamente una y otra vez, y luego se volvió un tema importante en sus charlas y escritos. Igual que en el ejemplo de la puerta cerrada, el artículo también menciona que, aunque parezca simple, esa "simplicidad" cambia según la situación. Es decir, encontrar la solución simple no siempre es simple. También existía la conciencia de que este enfoque podía dejar deuda técnica, pero la prioridad era primero hacer que el código funcionara. Personalmente, creo que en este texto también habría sido bueno hablar un poco más del lado de la deuda técnica
Más adelante Kent Beck formalizó Extreme Programming. Esta metodología es un conjunto de prácticas que ayuda a que un sistema simple evolucione de manera natural cada vez que cambian los requisitos
Había oído esa expresión de un colega y sabía que la usaba casi como lema de vida, pero no sabía que Ward Cunningham era el original. Como la expresión se usa tanto, supongo que el máximo honor es que ya nadie sepa quién fue el autor original
Me pareció interesante que mencionaran Wiki. En un trabajo anterior manejábamos proyectos con Lotus Notes, y era útil porque básicamente te resaltaba cuándo había cambiado un documento. En el proyecto siguiente solo usamos Wiki, pero como no se podía identificar de un vistazo qué documentos habían cambiado, en la práctica dejó de ser útil. Era simple, sí, pero demasiado simple y por eso menos práctico
A lo largo de mi carrera, una de las discusiones más frecuentes ha sido la definición de "funciona". La frase "que funcione no significa que no esté roto" les resulta intuitiva a quienes alguna vez han arreglado algo con sus manos. Los mecánicos arreglan como pueden con una herramienta rota y al final deciden reemplazarla, mientras que los desarrolladores parecen no sentir tanto la necesidad de "hay que arreglar esto"
La etapa más difícil de mi carrera fue en una empresa donde había un equipo que adoraba los prototipos. Ese equipo hacía pruebas de concepto rapidísimo, se las mostraba a los ejecutivos en demos y luego las desplegaba de inmediato. Entonces los ejecutivos se confundían y pensaban: "si esto se terminó tan rápido, se puede lanzar enseguida". Después, cuando el equipo responsable abría el código, veía que faltaban por completo cosas indispensables como seguridad end-to-end, validación y manejo de errores. Al final había que rehacer todo desde cero, y los ejecutivos malinterpretaban que el equipo de despliegue estaba complicando algo innecesariamente
Vi por ahí una cita muy buena: "No basta con que un programa funcione. Tiene que funcionar por las razones correctas". En esencia, es el mismo mensaje
Este tipo de conversación es importante a nivel profesional. Se puede decir que un sistema "funciona" en el sentido de que produce la salida deseada, pero aun así puede considerarse un fracaso en términos de confiabilidad o costo
La definición de "funciona" en mi trabajo depende de dónde quiera invertir recursos (mi tiempo) mi empleador. Si hay margen para dedicar tiempo a mejorar calidad, doy lo mejor de mí. Si en cambio solo quieren alcanzar una meta o marcar una checklist, trabajo conforme a eso. Creo que obtienes resultados según lo que mides, así que puedo aconsejar, pero la decisión no me corresponde
La ironía de este tipo de consejo es que, en la práctica, quien sabe aplicarlo bien ya suele ser un profesional con mucha experiencia. Por ejemplo, cómo saber qué es "lo más simple", o cómo juzgar si realmente va a servir. Hace poco, en un importador XLSX que hice yo mismo, cometí un error con el manejo de namespaces XML, porque asumí que Excel siempre usa solo el namespace por defecto. Luego llegó un archivo con namespaces distintos y se rompió de inmediato. Lo simple habría sido quitar solo los prefijos y procesarlo así, pero por mi yo del futuro invertí 4 horas y rehice todo el parser para que fuera namespace-friendly. Hacer "lo más simple" en realidad no es tan fácil, y mientras más experiencia tienes, mejor puedes juzgarlo. Pero cuando ya tienes ese nivel de experiencia, también piensas que tal vez ya no necesitas este tipo de consejo
Creo que el propio artículo menciona por qué este consejo es difícil de aplicar. La clave es que, para encontrar la solución más simple, hay que considerar varios enfoques y al final se necesita pensamiento de ingeniería
En la empresa tenemos un principio: "no agreguemos código en la dirección equivocada". Aunque sea una implementación parcial, solo codificamos en una dirección que luego sea fácil de desarrollar. Es KISS, pero jamás permitimos atajos
Yo juzgo la simplicidad con base en "qué es lo más fácil de transferir". Un servicio único que no dependa de abstracciones complejas ni de infraestructura termina siendo más fácil de transferir. Quien lo lea puede entenderlo de inmediato, y también facilita el onboarding y el debugging. Claro, cuando hace falta abstracción se agrega, y cuando la infraestructura realmente es necesaria, también se añade. Lo importante es pensar siempre si, al entregar el código, esa estructura es la más fácil de explicar. Antes yo buscaba abstraerlo todo al máximo, separarlo por servicios, hacerlo basado en configuración y dejar casi nada de código, pero en la práctica la transición y la transferencia de conocimiento fracasaban por completo. Ahora solo complico la estructura cuando de verdad hace falta, y por defecto mantengo una simplicidad intuitiva. Al final eso ayuda tanto al onboarding de gente nueva como a corregir bugs y hacer transiciones reales
El vibecoding con IA es parecido. Mientras más experiencia y know-how tienes, más fácil es elegir tareas adecuadas y gestionar agentes
Creo que mucha gente pasa por alto que "lo más simple" no significa un atajo ni un parche rápido. De hecho, cuanto más simple es el método, más reflexión y comprensión del sistema requiere, y eso mismo se menciona al inicio del texto. Parece que mucha gente solo leyó el título y aprovechó para desahogar sus frustraciones
En general, cuando escucho principios o afirmaciones fuertes de este tipo, se me encienden las alertas. Cuando alguien habla como si hubiera una respuesta universal para el desarrollo de software, muchas veces en realidad no sabe tanto. La conclusión de los desarrolladores con experiencia real es que el software es difícil y requiere cuidado. No existe una respuesta mágica. Hace falta tener mente abierta y consideración
La simplicidad (es decir, el reverso de la complejidad) es casi siempre el criterio número uno al comparar alternativas de diseño de software. La razón es que al final los seres humanos tienen que planear, acordar, implementar y mantener todo eso. Pero juzgar la simplicidad es extremadamente difícil, y no se puede confiar mucho en que el ingeniero promedio de la industria haga bien ese juicio. Además, la propia idea de "simplicidad" se ha extendido como una especie de palabra de moda vacía, y muchas veces se usa solo para responderle a un argumento serio diciendo "esto es más simple". El líder del equipo debería saber detectar bien este tipo de cosas, pero cada vez resulta más difícil porque la capacidad de los líderes de equipo ha bajado a niveles poco confiables
También hubo una frase del artículo que me impresionó. El verdadero maestro sabe "hacer menos", no "agregar más"; como en esas escenas donde el principiante se mueve mucho y hace cosas vistosas, pero el maestro se mueve poco y da un solo golpe decisivo
Al leer el texto, al principio me irritó un poco, pero creo que señalaron bien la esencia de la prueba y error: el "atajo" solo añade otra capa de complejidad. El problema aparece cuando este tipo de principios se sobreaplican como si fueran órdenes de la gerencia, tratando problemas complejos de manera demasiado simplista, lo que luego dificulta transferir conocimiento y deja una limpieza todavía más compleja para después. A la inversa, una razón por la que los sistemas se vuelven innecesariamente complejos es que, después de vivir atados a parches e improvisaciones constantes, se intenta arreglar todo de un solo golpe y esta vez se exagera hacia el otro lado. Al final, los desarrolladores tienen que involucrarse activamente en las discusiones para guiar decisiones sensatas desde el inicio
No creo que esto sea una alarma roja como tal. La complejidad innecesaria aumenta todos los días, así que sí hace falta defender la simplicidad. Diseñadores, product managers, incluso clientes y arquitectos tienden por instinto a añadir complejidad
Entiendo tu punto, pero también señalaría que al final todo termina reduciéndose a "todo son trade-offs" o "no existe el almuerzo gratis". Ninguna generalidad puede eliminarse por completo porque es la destilación de experiencias de campo. El problema es cuando un concepto útil se degrada y se convierte en puro ruido o casi una religión, y al final solo quedan discusiones sobre el nombre de una carpeta
Hay un principio que interioricé construyendo varios sistemas de 0 a 1 en startups (Seed~Series C): "la simplicidad es robustez". Tanto en el diseño inicial como al mejorar sistemas existentes, es muy fácil sobrearquitectar. Las necesidades del cliente siguen evolucionando, y aunque intentes predecir requerimientos futuros, igual vas a fallar. Creo que la simplicidad no solo reduce errores, sino que también se vuelve la base para poder cambiar la estructura con facilidad. Puedes prepararte para posibles X, Y y Z, pero la dirección correcta es construir "lo más simple" para dejar espacio y capacidad de expansión futura. La complejidad inevitablemente aumenta las restricciones, y mientras más se acumula con el tiempo, más débil se vuelve el stack
Creo que solo después de haber intentado cierto nivel de diseñar un "sistema ideal" puedes llegar realmente a "lo más simple". También hace falta mucho oficio para construir sistemas fáciles de leer y de cambiar. Las mejoras verdaderamente "simples" también nacen de la experiencia y de una comprensión profunda
Va en la misma línea que Gall's law. Según esta idea, un sistema complejo que funciona bien siempre empezó como un sistema simple al que se le fue agregando complejidad gradualmente. Los sistemas diseñados como complejos desde el inicio no suelen salir bien. Por eso primero hay que construir el sistema más simple posible e ir añadiendo complejidad poco a poco según los requisitos
Estoy de acuerdo con la idea general del texto, pero creo que con la llegada de la infraestructura en la nube cambió la definición de "simplicidad". Antes era "algo simple pero no escalable" vs. "algo complejo pero escalable", pero ahora ya no es así. Una solución de rate limiting en memoria puede ser simple en un solo servidor, pero en cuanto tienes dos servidores se convierte de inmediato en un problema de estado distribuido. En cambio, servicios administrados como DynamoDB o ElastiCache se vuelven una única fuente de verdad tanto con un nodo como con 1000, y reducen muchísimo la complejidad. Desde la perspectiva de que "los sistemas simples son robustos", usar managed services puede ser más simple en un sentido más fundamental. Porque hacen desaparecer problemas repetitivos como pérdida de datos o manejo de estado distribuido. Ahora parece que la definición de "lo más simple" cambió hacia aprovechar bien los managed services sin operación propia. Hoy, más que evitar dependencias externas, apalancarse en sistemas validados y aislados para minimizar complejidad termina ahorrando tiempo y costo
Me identifico con la frase: "hazlo tan simple como sea posible, pero no más simple que eso". Siempre trato de seguir ese principio, pero me pregunto constantemente si por no conocer bien la tecnología más reciente estoy dejando pasar complejidad innecesaria, o si en realidad todos están cayendo en complejidad innecesaria. También es difícil aprender si algo de verdad vale la pena sin usarlo en un proyecto real; pero al mismo tiempo he visto que meter herramientas nuevas a la fuerza en producción solo por aprendizaje personal también termina perjudicando al equipo
Estoy viviendo una pesadilla parecida. Siempre hay alguien arriba muy interesado en el software más nuevo, así que tengo que evaluar herramientas que quizá ni aporten valor real en producción. Da igual el impacto en resultados: me siguen pidiendo hacer esa exploración cada semana, y se vuelve una carga
En la práctica, creo que lo correcto es primero hacer que funcione de la forma más simple posible (aunque sea manualmente si hace falta), y solo añadir toda la funcionalidad cuando de verdad llegue el momento de necesitarla. Aunque al inicio hayas perdido tiempo automatizando o armando herramientas de más, lo importante es la experiencia de haber entendido después por qué hacía falta
Esta sensación me resulta muy familiar. Yo también siempre conecto con textos así e intento practicar la simplicidad, pero siempre dudo si eso realmente es sensatez y pragmatismo, o simplemente falta de experiencia o de habilidad
Por esto mismo intento tener mucho cuidado de no caer en el "desarrollo orientado al currículum"