2 puntos por GN⁺ 2023-10-31 | 1 comentarios | Compartir por WhatsApp
  • La mayor lección que se repite tras años de experiencia en desarrollo es que la complejidad es el “enemigo eterno” de una base de código, y que crea situaciones en las que un cambio pequeño rompe algo en un lugar inesperado
  • La herramienta más potente para reducir la complejidad es decir “no” a funcionalidades y abstracciones, pero cuando hace falta un compromiso realista, una solución 80/20 entrega la mayor parte del valor con poco código
  • El diseño y las pruebas ponen más peso en encontrar buenos puntos de frontera una vez que emerge la forma del sistema, en vez de abstraer demasiado pronto; se valoran más las pruebas de integración, que se mantienen útiles por más tiempo que las pruebas unitarias
  • Las herramientas, los sistemas de tipos, el logging, los depuradores y el diseño de APIs deben reducir la carga cognitiva del desarrollador; el gran valor práctico de los sistemas de tipos está en el autocompletado del IDE
  • Técnicas con buenos nombres como DRY, separación de responsabilidades, microservicios, SPA, genéricos o refactorizaciones excesivas también se vuelven vías hacia la complejidad si dificultan entender y mantener el código

La complejidad es el principal riesgo de una base de código

  • La complejidad hace que un sistema que al principio era entendible de pronto se vuelva difícil de manejar, y que un cambio en un lugar rompa otra parte que parecía no estar relacionada
  • La complejidad puede entrar a través de desarrolladores o project managers con buenas intenciones, y a veces la complejidad que uno mismo introdujo termina siendo la causa del problema
  • No existe un “garrote” universal para resolver la complejidad; la mejor respuesta es no dejarla entrar desde el principio

“no” y “ok”: rechazar y comprometerse de forma realista

  • La mejor arma para frenar la complejidad es “no
    • “No vamos a hacer esa funcionalidad”
    • “No vamos a crear esa abstracción”
  • Sin embargo, aunque “no” sea un buen consejo de ingeniería, puede jugar en contra de más recompensas o ascensos
  • Cuando en la práctica hay que llegar a un compromiso, se responde “ok” y luego se busca una solución 80/20
    • Implementar el 80% del valor deseado con el 20% del código
    • Puede no tener todos los adornos y verse un poco fea, pero entrega la mayor parte del valor manteniendo baja la complejidad
  • Como los project managers a veces olvidan detalles de requisitos, se cambian de área o dejan la empresa, en ocasiones el enfoque 80/20 termina siendo una mejor elección

Diseñar y descomponer código demasiado pronto es riesgoso

  • Al inicio de un proyecto, la forma del sistema todavía es poco clara, como “agua”, así que se evita la descomposición demasiado temprana
  • Con el tiempo aparecen buenos puntos de frontera
    • Tienen una interfaz estrecha con el resto del sistema
    • Ocultan la complejidad interna con pocas funciones o abstracciones
  • Si se crean abstracciones demasiado pronto, es fácil terminar con abstracciones equivocadas, por lo que se le da más peso a esperar
  • Para desarrolladores que quieren crear grandes abstracciones al inicio, puede ayudar pedirles entregables descartables como UML, o exigir una demo funcionando mañana
  • Un prototipo inicial muestra más rápido código que realmente funciona y restricciones realistas

Las pruebas no van “todas juntas después”, sino “a medida que toma forma”

  • Las pruebas previenen muchos problemas, pero se rechaza imponer test first antes siquiera de entender el dominio
  • Se prefiere escribir la mayoría de las pruebas cuando, después del prototipo, el código empieza a solidificarse
  • Saltarse pruebas diciendo “en mi máquina funciona” es una actitud muy mala
  • Las preferencias por tipo de prueba son claras
    • pruebas unitarias: son útiles al inicio del proyecto, pero se rompen fácilmente con cambios de implementación y pueden dificultar la refactorización
    • end to end: muestran el comportamiento del sistema completo, pero cuando fallan es difícil entender la causa, y si se rompen seguido es fácil que se las ignore
    • pruebas de integración: están lo bastante arriba para ver la corrección del sistema y lo bastante abajo para encontrar la causa con un depurador, así que se acercan al mejor punto
  • Cuando la API de un punto de frontera se estabiliza, se invierte con fuerza en pruebas de integración alrededor de ella
  • Las pruebas end-to-end se mantienen como un conjunto pequeño y bien gestionado, enfocado en las funciones de UI más comunes y algunos edge cases importantes
  • El mocking se usa solo cuando es realmente necesario y, de ser posible, solo a nivel de grandes puntos de frontera
  • Cuando se encuentra un bug, excepcionalmente se prefiere reproducirlo primero con una prueba de regresión y luego corregirlo

Agile, refactorización y Chesterton's Fence

  • Agile en sí no es lo peor y puede servir como una forma de organizar equipos de desarrollo, pero hay que desconfiar del “chamán agile” que lo usa como herramienta para exculpar fracasos
  • Los prototipos, buenas herramientas y contratar buenos desarrolladores son más importantes para el éxito del software, y ningún proceso es una bala de plata que resuelva todos los problemas
  • La refactorización es especialmente útil cuando el código ya se solidificó hacia el final del proyecto, pero cuanto más grande sea la refactorización, mayor es la probabilidad de fracaso
    • Avanzar dividiéndola en partes pequeñas
    • Mantener el sistema funcionando tanto como sea posible
    • Pasar al siguiente paso después de terminar cada etapa
  • La abstracción excesiva puede llevar al fracaso de la refactorización y del sistema
    • J2EE es un caso que perjudicó a muchos proyectos
    • OSGi intentó encerrar la complejidad, pero la hizo más fuerte, y requirió varios man-years de retrabajo
  • Chesterton's Fence se relaciona con la actitud de no eliminar código feo antes de entender por qué existe
  • El código que está funcionando debe respetarse aunque no sea perfecto, y cuanto más grande sea el sistema, más tiempo se necesita primero para entenderlo

Microservicios y herramientas

  • Los microservicios agregan llamadas de red al difícil problema de dividir correctamente un sistema
  • Las herramientas aumentan mucho la productividad y la comprensión de los desarrolladores
    • En un entorno nuevo, conviene dedicar tiempo a aprender las herramientas
    • Aprender herramientas durante 2 semanas puede duplicar la velocidad de desarrollo
    • Si no hay documentación, hay que preguntar a otros desarrolladores e investigar a fondo
  • El autocompletado de código del IDE evita tener que recordar todas las APIs, y en el desarrollo Java es casi indispensable
  • Un buen depurador es muy importante
    • Breakpoints condicionales
    • Evaluación de expresiones
    • Exploración de la pila
  • Los nuevos desarrolladores deben aprender en profundidad el depurador disponible; eso puede enseñarles más sobre computadoras que una clase universitaria

Sistemas de tipos, expresiones y DRY

  • El mayor valor de un sistema de tipos está en el soporte de herramientas que muestra una lista de acciones posibles al escribir un punto
  • La corrección de tipos también es buena, pero el autocompletado y la navegación por el código tienen mayor valor práctico
  • Hay que tener cuidado con desarrolladores que usan el sistema de tipos de forma demasiado abstracta
    • Los genéricos pueden ser especialmente peligrosos
    • En la mayoría de los casos se prefiere limitarlos a algo como clases contenedoras
  • En vez de compactar expresiones condicionales cortas en una sola línea, es mejor dividirlas en variables intermedias con nombres significativos para facilitar la depuración y la comprensión
  • DRY es un buen consejo, pero necesita equilibrio
    • El código duplicado simple y claro a veces es mejor que callbacks, closures o modelos de objetos complejos
    • Si eliminar duplicación aumenta la complejidad, puede terminar siendo una pérdida

Separación de responsabilidades, closures y logging

  • Separation of Concern es una idea poderosa, pero en la práctica puede obligar a ir y venir entre varios archivos para entender el comportamiento, haciendo perder tiempo
  • Como alternativa se prefiere la locality of behavior
    • Si el código se coloca cerca de “lo que actúa”, al ver ese elemento se entiende de inmediato qué hace
  • Los closures son adecuados para abstraer operaciones sobre colecciones, pero como la sal, alcanza con una cantidad pequeña
  • El “callback hell” de JavaScript es un ejemplo de complejidad por usar closures en exceso
  • El logging es muy importante, especialmente en entornos de despliegue en la nube
    • Registrar logs en cada rama lógica importante
    • Incluir request ID en las solicitudes que pasan por varias máquinas para poder agrupar los logs
    • Si es posible, ajustar dinámicamente los niveles de log
    • Si es posible, ajustar el nivel de log por usuario para depurar problemas de usuarios específicos
  • Las bibliotecas de logging pueden ser complejas, pero invertir en construir bien la infraestructura de logging da grandes recompensas más adelante

Concurrencia, optimización y API

  • La concurrencia es un área que merece respeto, y se prefiere un modelo lo más simple posible
    • Handlers de solicitudes web sin estado
    • Colas de trabajos remotos que no dependen entre sí
    • APIs simples
  • En la web, la concurrencia optimista puede encajar bien
  • Incluso las estructuras de datos concurrentes como ConcurrentHashMap de Java deben usarse con cuidado
  • La optimización debe empezar solo después de que un perfil de rendimiento real confirme un problema concreto
    • No mirar solo la CPU
    • El acceso a red equivale a muchos ciclos de CPU, así que conviene reducirlo cuando sea posible
    • Ver bucles anidados y lanzarse de inmediato a eliminar O(n^2) puede aumentar la complejidad
  • Una buena API debe evitar que el desarrollador tenga que pensar demasiado
    • Si quien escribe la API queda atrapado en la perspectiva de la implementación o del dominio, se vuelve difícil de usar
    • Para los casos simples, conviene ofrecer una API simple, y para los casos complejos, otra API más compleja por separado: una buena estratificación
    • En orientación a objetos, el comportamiento debe estar lo más posible sobre el objeto correspondiente
  • El ejemplo de stream/collector de Java se critica como una API que obliga a dar rodeos innecesarios para tareas comunes

Parsing, Visitor Pattern y frontend

  • Los parsers de recursive descent se consideran divertidos y elegantes
  • Los parser generators dificultan entender y depurar el código generado, y ocultan la naturaleza recursiva de la gramática
  • La mayoría de los parsers reales de producción son recursive descent, y se recomienda Crafting Interpreters de Bob Nystrom
  • El Visitor pattern se resume brevemente como “bad”
  • Separar frontend y backend, y conectar bibliotecas SPA con APIs JSON de GraphQL por HTTP, se parece a crear dos nidos de complejidad
  • Se mira con cautela la tendencia a usar grandes bibliotecas frontend incluso para sitios web simples o tareas de guardar formularios en una base de datos
  • Para reducir la complejidad se crearon htmx y hyperscript
    • Mantener HTML simple
    • Evitar mucho JavaScript
  • React puede ser mejor para empleos y ciertos tipos de aplicaciones, pero se considera una elección que lleva al camino de la complejidad frontend

Modas, FOLD e impostor syndrome

  • En desarrollo hay muchas modas, y se nota especialmente en frontend
  • Hay que sospechar de los nuevos enfoques que se presentan como revolucionarios
    • En computación, muchas ideas ya se han intentado al menos una vez
    • Las malas ideas pueden volver con un nombre nuevo
  • Es bueno que un desarrollador senior diga públicamente “esto es demasiado complejo y cuesta entenderlo”
  • FOLD significa Fear Of Looking Dumb: el estado de no poder admitir la complejidad por miedo a parecer tonto
    • Cuando un senior dice que algo es complejo, a los junior también les resulta más fácil admitir que no lo entienden
    • FOLD es una de las principales fuentes de fuerza de la complejidad, especialmente entre desarrolladores jóvenes
  • Los desarrolladores oscilan entre sentir que dominan todo y sentir que no saben nada; el impostor syndrome es común
  • Si todos se sienten impostores, entonces nadie lo es, y los desarrolladores jóvenes pueden seguir su carrera aunque tengan frustraciones y preocupaciones

Lecturas recomendadas y conclusión

1 comentarios

 
GN⁺ 2023-10-31
Opiniones de Hacker News
  • Es extraño que la gente inteligente se sienta atraída por la complejidad como polillas al fuego
    Me tomó años aprender a luchar contra el impulso de sobreingenierizar
    Una vez que empiezas a verlo, es difícil ignorarlo, y ahora puedo darme cuenta de inmediato si un código está sobreingenierizado
    Por desgracia, como el 99% del código parece sobreingenierizado, y hay un fuerte incentivo para que el desarrollador maximice su insustituibilidad y las horas facturables
    Incluso los desarrolladores que parecen completamente mecánicos y sin ego suelen caer en la sobreingeniería, y eso opera de forma inconsciente
    Muchos desarrolladores no piensan conscientemente en cada línea de código que escriben; simplemente fijan un objetivo y sacan el primer código que los acerca un poco a ese objetivo, sin darse cuenta de que en cada paso había una alternativa mejor

    • En mi experiencia, casi nunca he visto que el incentivo de aumentar la insustituibilidad del desarrollador y las horas facturables sea la causa de la sobreingeniería
    • Sigo lidiando con este problema
      Primero, me gustan los acertijos interesantes. Sobre todo en código comercial, si se hace bien muchas veces resulta bastante aburrido, así que inconscientemente termino empujando funciones cuya implementación es divertida. Y aquí divertida al final significa funciones que vuelven todo excesivamente complejo
      Segundo, mientras programo algo, todas las decisiones que tomo me parecen obvias y necesarias. Solo más tarde, cuando me alejo del código e intento entenderlo otra vez con ojos frescos, veo el desastre que armé
      En la mayoría de los problemas, la solución más visible suele ser bastante compleja, y para saber dónde buscar una solución simple hace falta sabiduría, habilidad y conocimiento del dominio. Las soluciones simples y limpias casi nunca son evidentes
      “Estamos implementando poco a poco una cola de mensajes hecha a medias. Mejor usemos una cola de mensajes estándar”, “Estamos creando un protocolo de framing binario personalizado lleno de bugs. Mejor usemos protobuf/msgpack”, “No hagamos un protocolo RPC personalizado para traer datos; usemos REST sobre HTTP. Así podemos poner nginx en medio para cachear respuestas del backend y deshacernos también del caché personalizado”
    • Me recuerda a esa frase de una carta de cierto escritor: “No tuve tiempo de escribir una carta corta, así que escribí una larga”
      Al final, si tienes tiempo y te recompensan por hacerlo, puedes escribir código perfecto, simple y sin repetición. Pero la mayoría de las veces se recompensa más sacar algo, y demostrar capacidad técnica por la cantidad de trabajo, inteligencia y capacidad de diseño que se invirtió en lanzarlo
      La conclusión natural es una estructura donde todo termina convertido en un amasijo inflado y sobreingenierizado de soluciones temporales, que se reescribe cada 3 a 7 años
      No he visto datos, pero parece probable que la “vida media de las reescrituras” esté relacionada con la rotación o con la permanencia promedio en un trabajo. Aunque la gente se esfuerce más por no sobreingenierizar, igual es muy probable que termine reescribiéndose
      Como perfeccionista, esto me molesta muchísimo, pero a veces se recompensa más construir algo complejo y sobreingenierizado que quedarse pensando más tiempo en la mejor solución simple. Seguro hay mejores organizaciones, pero en 9 años trabajando como ingeniero de software todavía no he encontrado una
      El mejor contraejemplo que se me ocurre son los proyectos open source, pero incluso las buenas librerías suelen reescribirse o sacar nuevas versiones cuando cambia el mantenedor. Los incentivos monetarios son muy distintos entre open source y la chatarra empresarial que ven muchos ingenieros de software de tiempo completo en HN. Es parecido a comparar un artículo académico bien pulido con un email de trabajo escrito a la carrera
    • En mi caso, la sobreingeniería suele venir de lo contrario. Surge de pensar demasiado al escribir código y de tener ideas preconcebidas sobre cómo debería verse el codebase
      Si no recuerdo mal, Casey de Handmade Hero llamaba a su estilo de programación “basado en compresión”. Primero escribes código, y luego vas separando gradualmente las cosas que pertenecen juntas
      Abstraigo cuando aparece repetición; no abstraigo por haberlo diseñado conscientemente. Cada vez uso más este enfoque
    • Si la conclusión de que “el 99% del código parece sobreingenierizado” te hace dar una respuesta tipo 99%, ¿no crees que, aunque lo que hayas aprendido sea válido, podría haber un problema con el modelo mental que construiste?
      Eso no significa necesariamente que esté mal, pero si se trata de evaluar la calidad del trabajo de otras personas, parece muy probable que haya un sesgo fuerte
  • Me encanta este sitio y siempre me hace reír. Mi parte favorita, sobre todo, son los microservicios
    “grug se pregunta por qué el cerebro grande toma el problema más difícil, que es dividir correctamente un sistema, y además le mete llamadas de red. A grug eso le parece muy confuso”

    • Me gusta este sitio y lo leo seguido. La gente que conozco probablemente ya está harta del manifiesto de desarrollo de software de grug brain
      “La complejidad es muy, muy mala”
      “Si tuviera que elegir entre la complejidad y pelear mano a mano con un tiranosaurio, grug elegiría al tiranosaurio. Al menos grug puede ver al tiranosaurio”
  • Tengo algunas quejas con la parte de los tipos
    grug dice que más del 90% del valor de un sistema de tipos está en que, cuando presionas el punto en el teclado, mágicamente aparece una lista de cosas que puedes hacer, pero los juniors de nuestra empresa despliegan con frecuencia código que se rompe en producción por acceso a null, y Sentry nos avisa
    En periodos de desarrollo intensivo, sale más o menos un bug de acceso a null detectado por día por cada desarrollador junior
    Si usáramos un sistema de tipos de verdad con verificación estática, el IDE nos diría algo como “¿seguro de esto si podría ser null?”, y eso ayudaría muchísimo
    Y además, se puede tener autocompletado incluso sin tipos estáticos
    La parte que dice “los hechiceros de sistemas de tipos de gran cerebro dicen que la precisión de tipos es el núcleo del sistema de tipos, pero grug ha visto que esos hechiceros no despliegan código con frecuencia. El código no desplegado, en cierto sentido, será correcto, pero no es la clase de corrección de la que habla grug” me parece grosera e innecesaria
    He desplegado sobre todo código en C, PHP, Python, Haskell y Python con tipos, y en los lenguajes tipados la tasa de bugs que llegan a producción es mucho menor. Esa es una de las razones por las que me gustan los tipos
    Además, el refactoring se vuelve mucho más fácil. Cuando cambias algo muy usado, puedes verificar de forma confiable en segundos todas las llamadas rotas en toda la base de código, y eso ayuda mucho a mejorar iterativamente una base de código en crecimiento

    • ¿Está mal lo que dice? Por su experiencia expresada directamente, parece que tampoco acepta sistemas de tipos que obliguen a hacer pruebas formales
      Más bien acepta sistemas de tipos de compromiso que detectan algunos problemas y, sobre todo, ofrecen la magia del autocompletado, sin quedar atrapados en todos los detalles necesarios para una prueba completa de corrección
    • Solo es una broma. Probablemente yo tenga más posibilidades de pertenecer al grupo del que se están burlando, pero no creo que haya que tomárselo tan a pecho
      Parece que considera que los tipos estáticos al nivel de C o Java están bien o incluso son buenos, y que las cosas más extravagantes que ves en Rust o Scala son una pérdida de tiempo. Yo creo que está totalmente equivocado, pero así es la vida
    • ¿El texto original no está a favor de los lenguajes tipados? Al final me parece que tiene la misma postura, así que me confunde
    • Creo que el “hechicero” del que habla aquí no se refiere a alguien como tú
      Entendí que “hechiceros de sistemas de tipos de gran cerebro” significa gente que ni siquiera necesariamente escribe código, sino por ejemplo personas que venden cursos sobre el tema y entusiasman a otros
      Al principio del texto también usa la palabra “hechicero” al hablar de Agile, y hay bastante gente cuyo trabajo se parece más a vender cursos que al desarrollo real de software
    • Antes trabajé con un “hechicero de sistemas de tipos de gran cerebro”. De hecho, este texto me parece un análisis muy amable y generoso
      Es muy difícil soportar trabajar con gente obsesionada con la jerarquía de tipos, y la justificación casi siempre se queda en el mundo del “¿y si...?” y “pero eso no es sound”
      Gran parte del deterioro de calidad que provocan los sistemas de tipos viene de gente que cree que, si pone suficientes tipos, puede impedir completamente los bugs, y eso no es cierto
  • Creo que en los próximos meses todos se van a subir al tren de HTMX, y que eso les va a volar la cabeza a muchos desarrolladores jóvenes, además de ahorrar muchísima energía en todo el mundo y hacer feliz a mucha gente
    Y luego, cuando esos jóvenes curiosos sigan haciendo clic en los enlaces de htmx.org y descubran hyperscript, va a ser como el momento en que salen los vampiros en Dusk till Dawn

    • Que todos se suban al tren de HTMX es un bucle infinito en JavaScript
      Alguien se cansa de la complejidad de JavaScript y crea una librería simple de JavaScript, la gente corre hacia esa simplicidad, la librería se vuelve compleja porque tiene que soportar toda la funcionalidad web, y entonces alguien se vuelve a cansar de esa complejidad y crea otra librería simple de JavaScript
      Empecé con JavaScript puro y me peleé varios años con IE5/6, luego jQuery nos salvó a todos, AngularJS no me convenció y me lo salté, y cuando salió la beta de React me gustó por mis ideales de programación funcional. Ahora estoy avanzando entre transpiling, hot reload, Typescript y pilas de pesadas librerías de React
      También hice algunos proyectos con Intercooler y HTMX. HTMX es sólido en general, pero no es el primer tren que pasa por este pueblo
    • Ah, así que Carson Gross era el autor de grug brained developer y de htmx
      Me gustaría ver críticas reales a htmx
      Personalmente, creo que el gran problema persistente de la web ha sido encontrar cómo actualizar bien una página, y hemos seguido probando todo tipo de enfoques. Por eso aparece el cansancio, y algunas ideas se vuelven extremas o se llenan de chatarra con el tiempo
      Htmx parece una reacción al estilo grug: hacer menos e insistir en YAGNI
      El futuro es incierto, pero trabajos como signals todavía parecen algo casi sagrado. Se está experimentando mucho en varias direcciones, y todavía seguimos explorando este frente y otros
      MobX y Svelte también han recorrido ya un largo camino. El viaje aún no termina, y me gustaría pensar que seguir enredados con el monstruo de la complejidad no es una señal de debilidad, aunque una visión conservadora o grug podría verlo así, sino un esfuerzo legítimo por llegar no a algo simplemente simple, sino a algo simple y bueno
    • Me reí al ver esa línea y luego fui a comprobarlo de verdad
      _="on load wait 5s then transition opacity to 0 then remove me"
      ah, no
      _="on htmx:error(errorInfo) fetch /errors {method:'POST', body:{errorInfo:errorInfo} as JSON} "
      ¡ah, no!
  • Llevo 30 años trabajando como desarrollador, y reconozco que al principio era arrogante y pensaba que era más inteligente que los demás. En esa época yo estaba del lado de los “grandes cerebros” que aman todos los demonios de la complejidad
    Diez años después, me volví más cercano a un desarrollador “Grug brain”, y ahora me enfoco en la solución más simple posible que funcione, aun sabiendo que quizá no sea perfecta. Y eso está bien, porque me acerca más a lo correcto y permite mejorar de manera iterativa
    Lo mejor que puedes hacer como desarrollador es borrar código. Ahora mismo tenemos un requisito que mantuvimos durante dos años y de pronto ya no es un requisito, y me emociona muchísimo pensar en arrancar un montón de código. Porque todo se vuelve más simple

    • Cuando borras código, hay una sensación placentera difícil de describir en la parte de atrás de la cabeza
      Es como si pudieras sentir físicamente que se libera espacio
  • Grug también salió en los comentarios del post de Philosophy of Software Design hace una semana, y me pareció que la discusión ahí estuvo bastante buena. https://news.ycombinator.com/item?id=38011938
    “con grug siento exactamente lo mismo. No creo que la gente realmente esté de acuerdo en qué es simple, así que fingir que tu propia ‘simplicidad’ es algo tan obvio que hasta un cavernícola estaría de acuerdo se siente como pura pose”
    La simplicidad muchas veces es una de las cosas más complejas de descubrir. He visto a mucha gente convencida de que todo a su alrededor, salvo lo que ellos mismos hacen, es complejo y por lo tanto hay que doblarlo hacia la simplicidad. Se siente como una postura peligrosamente débil, aferrada a la autoridad

    • Es un buen punto. Aun así, creo que al final la clave es que una mala filosofía fomenta la complejidad
      La filosofía de grug brain consiste en elegir la simplicidad cueste lo que cueste, salvo cuando sea absolutamente imposible evitarlo
      La filosofía de cerebro grande, según la ve grug, consiste en elegir la reutilización cueste lo que cueste, salvo cuando sea absolutamente imposible evitarlo
      El problema con esa filosofía es que congela las decisiones de diseño de primera generación y vuelve más difícil la mejora iterativa
      Es cierto que encontrar la simplicidad es difícil, pero la clave para encontrarla es la mejora iterativa
    • Algo especialmente bueno del texto de Grug es que está lleno de matices y humildad. No es pose
    • Yo creo que basta con medir las líneas de código
      El mínimo local se mueve según la complejidad de lo que estás construyendo, así que hay espacio para que varias opiniones bastante inteligentes sean correctas al mismo tiempo
      No es una métrica perfecta, pero apunta en la dirección correcta, y en escala logarítmica funciona muy bien
  • He usado y explicado innumerables veces el concepto de la valla de Chesterton, así que está bueno conocer su nombre
    Pasa muchísimo al trabajar con desarrolladores nuevos. Ven “basura legacy vieja” y su primera reacción es querer arrancarla o tirarlo todo y rehacerlo desde cero
    En algunos casos dejar que lo intenten puede servirles para aprender, pero conviene recordar que la gente que estuvo antes que nosotros no eran todos unos completos idiotas, y por lo general había una razón para hacerlo así
    A veces sí es código viejo y desordenado que de verdad hay que reemplazar, pero incluso en esos casos muchas veces contiene un registro muy valioso, ganado con esfuerzo, de todos los rincones y casos límite que hay que entender y manejar para construir algo en ese dominio

  • La parte de testing está realmente muy buena. Coincide exactamente con lo que he venido aprendiendo durante años
    Las pruebas de integración están en el punto correcto para encontrar bugs
    Los mocks tienden a complicar demasiado las cosas; todavía los uso a veces, pero evito usarlos de forma sistemática
    Las pruebas unitarias son demasiado frágiles frente al refactoring, mientras que las pruebas de integración ayudan con el refactoring

    • Un truco que me gusta es tener pruebas de integración que siguen el escenario del demo, y correrlas justo antes de una demo. Si se puede, correrlas dos veces
    • No estoy para nada de acuerdo. Las pruebas de integración funcionan de maravilla hasta llegar a cierto nivel de escala o complejidad, pero una vez que cruzas esa línea se vuelven realmente malas
      Las pruebas unitarias son más difíciles de escribir y mantener, pero cuando fallan son mucho más fáciles de entender y depurar, así que a largo plazo aguantan mucho mejor
      La peor clase de prueba es la prueba de integración que depende en secreto de que otra prueba de integración se haya ejecutado antes. Funciona bien el 99% del tiempo, hasta que algún cambio altera el orden y en ese momento se rompe
    • La conclusión a la que llegué sobre testing es esta
      Si tu equipo tiene código simple, las pruebas ayudan mucho. Pero si tu equipo no tiene código simple, que suele ser lo normal, casi siempre conviene más dedicar ese tiempo a simplificar el código que a escribir pruebas
    • Creo que mucho depende de qué consideres una “unidad”
      Si tomas como unidad una sola leaf function, muchas veces es un nivel demasiado bajo, pero un módulo más grande con una interfaz relativamente estable puede ser una buena unidad productiva para testear
  • “la complejidad es muy muy mala”
    “la mejor arma contra el demonio espiritual de la complejidad es la palabra mágica ‘no’”
    “triste pero cierto: un consejo de carrera ideal es que, después de aprender a decir ‘sí’ y fracasar, aprendas a culpar a otro grug”
    Esa es la compleja sabiduría de grug

  • También se discutió en ese momento
    The Grug Brained Developer - https://news.ycombinator.com/item?id=31840331 - junio de 2022, 374 comentarios

    • la complejidad sigue siendo muy mala