- La complejidad es el elemento más peligroso en el desarrollo
- La verdadera eficiencia surge de un enfoque pragmático que evita la complejidad, como una "solución 80/20"
- Es importante mantener una postura equilibrada y flexible respecto a las pruebas y el refactoring
- Se enfatiza el uso de herramientas y la adopción del hábito de escribir código fácil de leer y mantener
- Se recomienda desconfiar de la abstracción excesiva y de las modas, y perseguir la simplicidad
Introducción
- Este texto es una recopilación de ideas de un desarrollador con cerebro de Grug, que resume lo aprendido con la experiencia tras muchos años desarrollando software
- El desarrollador con cerebro de Grug no se considera muy inteligente, pero ha aprendido mucho programando durante mucho tiempo
- Comparte sus descubrimientos de forma simple y graciosa, con la esperanza de que otros aprendan de los errores
- La complejidad es, sin duda, el mayor enemigo en la vida de desarrollo
- La complejidad se infiltra sigilosamente en la base de código y hace que incluso el código que al principio era fácil de entender termine siendo casi imposible de modificar
Cómo lidiar con el demonio de la complejidad
- La complejidad se filtra en silencio como un espíritu invisible, y muchas veces los project managers y los desarrolladores que no son Grug no la perciben bien
- La mejor forma de impedir la complejidad es decir "no"
- "No voy a crear esta funcionalidad"
- "No voy a introducir esta abstracción"
- Claro, para la carrera profesional puede convenir más gritar "sí", pero el desarrollador con cerebro de Grug valora elegir con honestidad consigo mismo
- Dependiendo de la situación, también hace falta transigir (“ok”), y en esos casos se prefiere resolver el problema de forma simple con una solución 80/20 (aplicando el principio de Pareto)
- No contarle todo al project manager y en realidad sacarlo adelante con un enfoque 80/20 también puede ser una estrategia inteligente
Estructura del código y abstracción
- La unidad adecuada del código (cut point) se revela de manera natural con el tiempo, por eso conviene evitar la abstracción temprana
- Un buen cut point idealmente tiene una interfaz estrecha con el resto del sistema
- Los intentos de abstracción prematura suelen fracasar, y los desarrolladores con experiencia tratan de estructurar el código poco a poco cuando su forma ya está algo asentada
- Los desarrolladores con menos experiencia o con “big brain” tienden a intentar demasiada abstracción al inicio del proyecto, dejando una carga de mantenimiento
Estrategia de pruebas
- Es importante tener equilibrio, no obsesión, con los tests
- Se prefiere escribir tests después de hacer prototipos y cuando el código ya está relativamente asentado
- Los unit tests se usan al principio, pero en la práctica las pruebas de nivel intermedio (integración) son las que más impacto tienen
- Los tests end-to-end también hacen falta, pero si hay demasiados se vuelven imposibles de mantener, así que conviene dejar solo unas pocas rutas realmente necesarias
- Cuando llega un bug report, primero se agrega una prueba de reproducción y luego se corrige el bug
Proceso, agile y refactoring
- Agile no le parece mal al desarrollador Grug; no es lo peor, pero depositar demasiadas expectativas en los “chamanes agile” es peligroso
- El prototipado, las herramientas y buenos colegas son factores de éxito mucho más importantes
- El refactoring también es un buen hábito, pero los refactorings grandes y forzados son riesgosos
- Introducir abstracciones complejas a la fuerza puede terminar causando el fracaso del proyecto
Mantenimiento, perfeccionismo y humildad
- Es riesgoso desarmar un sistema existente sin motivo, y no es una buena práctica eliminar a ciegas una “estructura cuyo propósito no entiendes”
- El idealismo de soñar con código perfecto en la práctica suele causar problemas
- Con más experiencia, uno aprende en carne propia que hay que “respetar el código que funciona”
Herramientas y productividad
- Las buenas herramientas de desarrollo (autocompletado del IDE, debugger, etc.) elevan mucho la productividad, y es importante conocerlas a fondo
- Se enfatiza que el valor real de los sistemas de tipos está en el “autocompletado” y en prevenir errores, y que la abstracción excesiva y los genéricos pueden ser más bien peligrosos
Estilo de código y repetición
- Se recomienda un estilo como dividir las condiciones en varias líneas, para que el código sea más fácil de leer y depurar
- Se respeta el principio DRY (Don’t Repeat Yourself), pero se enfatiza que el equilibrio importa más que eliminar código repetido a la fuerza
- Muchas veces una repetición simple es mejor que una implementación DRY complicada
Principios de diseño de software
- Se prefiere la localidad del comportamiento por encima del principio de SoC (Separación de responsabilidades), sosteniendo que “si el código que realiza cierta acción está en ese objeto, mantenerlo es más fácil”
- Se advierte que callbacks/closures, sistemas de tipos, genéricos y abstracciones deben usarse con moderación y de forma apropiada
- El abuso de closures puede crear “callback hell” en JavaScript
Logging y operación
- El logging es muy importante: conviene dejar registros en cada ramificación principal y, en entornos cloud, permitir el rastreo con cosas como request IDs
- Si se pueden usar niveles de log dinámicos y logs por usuario, eso ayuda mucho a rastrear problemas en producción
Concurrencia y optimización
- En concurrencia, solo se confía en modelos lo más simples posible (requests web sin estado, worker queues separadas, etc.)
- Se recomienda optimizar solo después de obtener datos reales de profiling de rendimiento
- Hay que prestar atención a costos ocultos como network I/O; fijarse solo en la complejidad de CPU es peligroso
Diseño de API
- Una buena API debe ser fácil de usar, y los diseños o abstracciones demasiado complejos dañan la experiencia del desarrollador
- Se recomienda una estructura con una “API simple para casos de uso comunes” y una “API en capas que también permita implementar casos complejos”
Desarrollo de parsers
- Los parsers de descenso recursivo están subvalorados en la academia, pero en código de producción real suelen ser el método más adecuado y más fácil de entender
- En la mayoría de las experiencias desarrollando parsers, los parsers generados por herramientas terminan siendo tan complejos que más bien empeoran la resolución de problemas
- Como libro recomendado, destaca "Crafting Interpreters" como el mejor y lleno de consejos prácticos
Frontend y modas
- El frontend moderno (React, SPA, GraphQL, etc.) más bien invoca todavía más al demonio de la complejidad, y muchas veces es innecesario
- El propio Grug prefiere reducir complejidad con herramientas simples como htmx e hyperscript
- En frontend se prueban cosas nuevas todo el tiempo, pero conviene recordar que muchas son solo repeticiones de ideas ya conocidas
Factores psicológicos e impostor syndrome
- A muchos desarrolladores les pasa que sienten “no sé lo que estoy haciendo”, y hace falta liberarse del fenómeno FOLD (Fear Of Looking Dumb)
- Si un desarrollador senior dice públicamente “esto también me cuesta, es demasiado complejo”, los desarrolladores junior pueden quitarse parte de la presión
- El impostor syndrome es una sensación común, y se anima a seguir aprendiendo y creciendo
Conclusión
- En programación, siempre hay que desconfiar de la complejidad, y mantener la simplicidad es la clave para un desarrollo exitoso
- La experiencia, el uso efectivo de herramientas, la humildad y el respeto por el código que realmente funciona conducen a largo plazo a un desarrollo más eficiente y valioso
- “La complejidad es muy, muy mala”: hay que recordar siempre esa frase
1 comentarios
Opiniones de Hacker News
print. Incluso cuando intento mostrarles mi flujo de trabajo a mis colegas, no reaccionan. Estoy de acuerdo en que el mejor punto de partida para entender un sistema es justamente el depurador. Poner una pausa en una línea interesante durante una prueba y ver la pila es muchísimo más fácil que seguir el código solo en la cabeza. Aprender a usar un depurador de verdad te da una especie de superpoder pequeño pero real. Si puedes, de verdad recomiendo probarlo al menos una vezprintes la única opción posible. Incluso si hasta el sistema de logs tiene problemas, o si el programa simplemente se cae antes de imprimir logs, ni siquieraprintsirveprinty código de autocheck en el camino. Agregar unprintes mucho más rápido que entrar paso a paso con un depurador. Además, el código conprintse queda en el programa, mientras que la sesión de depuración desaparece." Yo también estoy de acuerdo con esto. Durante la mayor parte del desarrollo, el cicloprint-hipótesis-ejecución da una resolución de problemas mucho más rápida. No es que "ejecute" el código en la cabeza, sino que ya tengo un modelo mental de cómo fluye, así que cuando unprintmuestra algo incorrecto, por lo general capto muy rápido qué está pasando en realidad. Enlace relacionado: The unreasonable effectiveness of print debuggingprintfsiempre fue tan común en el mundo Linux es que el entorno no permitía confiar en depuradores con GUI. Las GUI de Linux suelen ser inestables y no se puede confiar demasiado en ellas. En mi caso, empecé a usar depuradores en serio cuando (1) en Windows la GUI funcionaba bien pero la CLI a veces se rompía, y (2) después de varias veces en que código de depuración conprintterminó colándose por error en una versión y causando problemas. Luego tuve varias aventuras con depuradores de CLI, y sentí que el proceso de usar Junit + depurador (en IDEs como Eclipse), probando código experimental directamente y dejándolo como prueba, era tan cómodo como un Python REPL. Claro, sí hace falta una inversión inicial para dejar bien configurado el depurador según el entorno