Desarrollador de cerebro grug (2022)
(grugbrain.dev)- A través del testimonio de un desarrollador de “cerebro pequeño” con muchos años programando, enfatiza que el mayor enemigo del desarrollo de software es la complejidad y que la actitud de reducirla debe estar en el centro del trabajo real
- El arma básica para frenar la complejidad es decir “no”, y cuando hace falta negociar, busca soluciones realistas con la regla 80/20, prototipos, refactorizaciones pequeñas y abstracción tardía
- En pruebas, una vez que el código ya está algo consolidado, pone en el centro las pruebas de integración, usando pruebas unitarias y unas pocas pruebas end-to-end como apoyo; cuando aparece un bug, primero lo reproduce con una prueba de regresión y luego lo corrige
- Herramientas, sistemas de tipos, logging, depuradores y APIs simples reducen la carga de memoria e inferencia del desarrollador, pero el uso excesivo de genéricos, callbacks, microservicios y frameworks de frontend puede aumentar la complejidad
- Hay que acercarse con humildad al código existente y a los procesos de la organización, no eliminar a la ligera código que no se entiende y fomentar una cultura donde se pueda decir “esto es demasiado complejo”
La complejidad es el enemigo eterno del desarrollador
- El enemigo más peligroso en el desarrollo de software es la complejidad
- La complejidad entra poco a poco en la base de código y hace que, al cambiar una parte, se rompan otras que parecían no tener relación
- Grug la compara con un “demonio” invisible, una presencia que el desarrollador no puede ver directamente ni derrotar fácilmente
- La complejidad también puede entrar por medio de desarrolladores o project managers bien intencionados
- Cuantas más funciones, abstracciones y procesos se agregan, más difícil se vuelve entender el código
- El propio Grug reconoce que a veces él mismo fue quien introdujo complejidad
Cómo usar “no” y “ok”
- El arma más fuerte para bloquear la complejidad es el “no”
- No construir funciones innecesarias
- No crear abstracciones innecesarias
- Pero aunque “no” sea un buen consejo de ingeniería, no siempre favorece la carrera profesional
- Decir “yes” puede llevar a más recompensas o a puestos de gestión
- Aun así, si uno quiere ser honesto consigo mismo como desarrollador, “no” es importante
- Cuando hace falta negociar, responde “ok” y luego busca una solución 80/20
- La 80/20 solution es un enfoque que satisface el 80% de los requisitos con el 20% del código
- Aunque no tenga todos los detalles decorativos, puede entregar la mayor parte del valor y contener la complejidad
- A veces considera mejor resolverlo de forma simple sin explicarle todos los detalles de implementación al project manager
Estructuración del código y refactorización
- Al inicio de un proyecto no conviene dividir la aplicación demasiado pronto
- Al principio, la forma del sistema aún no está clara y todavía no se entiende por completo qué se está construyendo
- Con el tiempo aparecen buenos puntos de corte (cut point)
- Un buen punto de corte tiene una interfaz estrecha con el resto del sistema
- Oculta la complejidad interna con una pequeña cantidad de funciones o abstracciones
- Grug lo compara con encerrar al demonio de la complejidad dentro de una modificación
- Cuando un desarrollador de cerebro grande quiere crear muchas abstracciones al inicio del proyecto, hace falta una forma de limitar el daño
- Se puede redirigir hacia entregables como diagramas UML, que no dañan directamente el código
- Pedir “una demo que funcione mañana” hace que se enfrente rápido al código real y a la realidad
- A este enfoque de demo se le puede llamar prototipo
- La refactorización resulta útil más adelante, cuando el código ya está más consolidado
- Cuanto más grande es la refactorización, mayor es la probabilidad de fracasar
- Prefiere avanzar en pasos pequeños manteniendo el sistema siempre en funcionamiento
- Las pruebas end-to-end son un salvavidas durante la refactorización, pero cuando fallan puede ser difícil entender la causa
- La abstracción excesiva puede llevar al fracaso de la refactorización y del sistema mismo
- J2EE aparece como ejemplo de abstracción excesiva
- La adopción de OSGi fue un intento de reducir la complejidad, pero terminó creando una complejidad aún mayor y requirió varios años de retrabajo
Estrategia de pruebas
- Las pruebas ahorran mucho tiempo, pero es escéptico con la idea de “probar primero sí o sí”
- Si todavía no se entiende el dominio, es difícil saber qué probar
- Grug prefiere escribir la mayor parte de las pruebas después del prototipo, cuando el código empieza a consolidarse
- Aunque las pruebas se escriban más tarde, hace falta disciplina
- No hay que saltarse las pruebas con la excusa de “en mi máquina funciona”
- No hay garantía de que funcione en otra máquina o en la misma máquina en el futuro
- Distingue los roles de las pruebas unitarias, de integración y end-to-end
- Las unit tests ayudan al inicio del proyecto, pero pueden romperse fácilmente con cambios de implementación y dificultar la refactorización
- Las pruebas end to end muestran el comportamiento del sistema completo, pero cuando fallan es difícil identificar la causa y, si fallan seguido, pueden terminar siendo ignoradas
- Las integration tests están lo bastante arriba como para verificar la corrección del sistema y lo bastante abajo como para ver la causa con el depurador, así que están cerca del “sweet spot”
- La forma ideal de la composición de pruebas sería la siguiente
- Al principio, algunas pruebas unitarias
- A medida que aparecen puntos de corte y el sistema se estabiliza, pruebas de integración fuertes
- Un conjunto pequeño y bien mantenido de pruebas end-to-end que cubra solo las funciones de UI más comunes y los edge cases importantes
- Prefiere usar mocks solo en raras ocasiones y, aun cuando hacen falta, solo en unidades grandes como los límites del sistema
- Al corregir bugs, hace una excepción: primero reproduce el bug con una prueba de regresión y luego lo corrige
Proceso, agile y actitud frente al código existente
- Agile no es lo peor, pero tampoco le parece algo claramente bueno
- Puede ser razonablemente útil como forma de organizar el desarrollo
- Desconfía de los expertos en agile que, cada vez que algo falla, dicen que “no se hizo agile correctamente”
- Para el éxito son más importantes los prototipos, las herramientas y contratar buenos desarrolladores
- Los procesos agile pueden ayudar, pero tomarlos demasiado en serio puede ser perjudicial
- no silver club: no existe una bala de plata que resuelva todos los problemas del software
- Chesterton’s Fence se usa como advertencia sobre eliminar código existente
- Si no se conoce el propósito de una cerca, primero hay que entenderla y no quitarla de inmediato
- Aunque el código sea feo, si hoy funciona hay que respetarlo
- Sobre todo en sistemas grandes, primero hay que entender y luego mejorar
- Las pruebas pueden dar pistas sobre por qué existe cierta “cerca”
Herramientas y sistema de tipos
- Las herramientas reducen la carga de lo que el desarrollador tiene que recordar e inferir por sí mismo
- Al entrar a un entorno nuevo, invertir tiempo en aprender las herramientas alrededor puede mejorar la productividad
- Si no hay documentación, puede hacer falta preguntarles a otros desarrolladores para entenderlas
- El autocompletado del IDE evita tener que memorizar toda la API
- Dice que programar en Java es casi imposible sin autocompletado
- Un buen depurador es muy importante
- Hay que aprender a fondo funciones como breakpoints condicionales, evaluación de expresiones y exploración de stack
- Considera que aprender a usar el depurador puede enseñarle a un desarrollador nuevo más sobre computadoras que una clase universitaria
- El mayor valor del sistema de tipos es que “al presionar punto (.) aparecen las cosas que se pueden hacer”
- La corrección de tipos también está bien, pero para Grug el soporte de herramientas y el autocompletado valen más
- La abstracción excesiva de tipos o los genéricos pueden dificultar el código real del trabajo
- Prefiere limitar el uso de genéricos principalmente a clases contenedoras
Expresiones, DRY y separación de responsabilidades
- Prefiere código fácil de depurar antes que código corto
- En vez de poner condiciones complejas en una sola línea, dividirlas en variables intermedias facilita ver el resultado y el significado de cada expresión
- Aunque aumente el número de líneas, entender y depurar la condición se vuelve más fácil
- DRY es un buen consejo, pero hace falta equilibrio
- Un código duplicado simple y claro puede ser mejor que callbacks, closures o modelos de objetos complejos
- Si eliminar duplicación aumenta la complejidad, puede salir peor
- Es más crítico con Separation of Concern
- Un ejemplo típico en desarrollo web es separar CSS, HTML y JavaScript
- Grug prefiere como alternativa locality of behavior
- Si el código relacionado está cerca del objeto que se comporta, al mirar ese objeto se entiende enseguida qué hace
- Los closures son útiles cuando se usan para lo correcto, como abstraer operaciones sobre colecciones
- Pero, igual que la sal, los sistemas de tipos o los genéricos, un poco basta; demasiado puede ser dañino
- El “callback hell” de JavaScript aparece como ejemplo de abuso de closures
Logging, concurrencia y optimización
- El logging es muy importante, especialmente en entornos de despliegue en la nube
- Deja logs en cada rama lógica importante
- Si una solicitud pasa por varias máquinas, pone un request ID en todos los logs para poder agruparlos
- Si es posible, ajusta los niveles de log dinámicamente
- Si es posible, también ajusta el nivel de log por usuario
- Las librerías de logging en Java pueden ser complejas, pero invertir bien en la infraestructura de logging da una gran recompensa después
- La concurrencia es algo a lo que hay que tenerle miedo
- Siempre que puede usa modelos simples, como handlers de requests web sin estado y colas de trabajo remoto independientes
- Considera que la optimistic concurrency funciona bien en el mundo web
- La thread local variable la usa ocasionalmente, sobre todo al escribir código de framework
- Incluso estructuras de datos concurrentes como ConcurrentHashMap en Java deben usarse con cuidado
- La optimización debe empezar cuando ya existe un perfil de rendimiento real
- Está de acuerdo con “premature optimization is the root of all evil”
- El cuello de botella real puede ser distinto de lo esperado
- No hay que mirar solo la CPU; una llamada de red puede equivaler a millones de ciclos de CPU, así que conviene reducirlas si es posible
API, parsing y patrón visitor
- Una buena API hace que el desarrollador no tenga que pensar tanto
- Una mala API aparece cuando está diseñada alrededor de la implementación interna o del modelo de dominio, o cuando es demasiado abstracta
- Para casos simples prefiere una API simple y, para casos complejos, una API más compleja: una estratificación por niveles
- En APIs orientadas a objetos, considera mejor adjuntar la funcionalidad al objeto objetivo
- Pone como mal ejemplo en Java tener que convertir una lista a Stream para filtrarla y luego volver a recolectarla como List
- Cree que una operación común como
filter()debería estar en list y devolver list
- Los parsers recursive descent le parecen una forma divertida y hermosa
- Critica a los generadores de parser por ser difíciles de entender y difíciles de depurar
- Afirma que los parsers de producción reales casi siempre son de descenso recursivo
- Recomienda Crafting Interpreters de Bob Nystrom
- Se puede leer gratis en línea, pero también recomienda comprar el libro
- Aun así, dice que el visitor pattern es una trampa
- Su evaluación del Visitor pattern es breve: “bad”
Frontend, microservicios y modas
- Los microservicios son riesgosos porque “agregan llamadas de red al problema más difícil de dividir correctamente un sistema”
- En desarrollo frontend la complejidad es especialmente fuerte
- Critica situaciones donde incluso para guardar un formulario simple o hacer un sitio tipo brochure se usan librerías SPA, APIs JSON con GraphQL y un backend HTTP
- Dice que al separar frontend y backend se crean dos lugares donde puede vivir la complejidad
- Grug dice que creó htmx y hyperscript para bajar la complejidad
- Prefiere mantener HTML simple y no usar demasiado JavaScript
- Reconoce que React puede ser mejor para conseguir trabajo y para ciertos tipos de aplicaciones
- Dice que en desarrollo hay muchas modas, especialmente en frontend
- Comenta que el backend se volvió más aburrido y que muchas malas ideas ya se intentaron antes
- Recomienda acercarse a los nuevos enfoques revolucionarios con cautela, como con una pizca de sal
- Muchas ideas ya se probaron al menos una vez, y reciclar malas ideas puede hacer perder tiempo
Miedo y síndrome del impostor
- Es bueno que los desarrolladores senior digan públicamente “esto es demasiado complejo”
- A los desarrolladores les cuesta admitir que no entienden algo por Fear Of Looking Dumb (FOLD)
- Si un senior lo reconoce primero, a los junior también se les hace posible hablar de complejidad y de falta de comprensión
- FOLD es una fuente importante de poder para la complejidad, especialmente entre desarrolladores jóvenes
- Considera que ayudan el sentido del humor y recordar fracasos del pasado
- El síndrome del impostor también es común en desarrollo
- Grug dice que vive entre la sensación de controlarlo todo y la de no tener idea de lo que está haciendo
- Incluso con los logros open source de htmx y _hyperscript, sigue temiendo equivocarse y fracasar
- Si todos sienten síndrome del impostor, prefiere asumir que entonces nadie es realmente un impostor
Lecturas recomendadas y conclusión
- Lecturas recomendadas
- La conclusión final es simple
- La complejidad es muy, muy mala
1 comentarios
Opiniones de Hacker News
Si el profesor Carson está leyendo los comentarios, quiero agradecerle de corazón por todos sus aportes durante todo este tiempo.
En la universidad no entendía por qué aprendíamos HTMX, ni por qué le entusiasmaba tanto, pero después de unos años ahora lo entiendo. Todo era HTML over the wire.
Trabajando como Staff Ruby on Rails Engineer, vi su trabajo en Hotwire, y también fue genial verlo aparecer de vez en cuando en Hacker News o conversando con desarrolladores de Hotwire en GitHub. Es una luz para la comunidad de programación, y se le respeta y agradece muchísimo.
Coincido con la frase “un buen depurador vale tanto como una piedra brillante; de hecho, vale más”.
He pasado tanto por startups pequeñas como por equipos ‘élite’ de Big Tech, pero casi siempre yo era la única persona del equipo que usaba un depurador. En el mundo real, al menos en tecnologías web, parece que la mayoría depura con sentencias
print.Detenerse en una línea de código interesante durante la ejecución de una prueba y ver la pila de llamadas que llevó hasta ahí es mucho más fácil que intentar ejecutar el código hacia adelante en la cabeza. Si eres un grug joven, esta técnica es como un pequeño superpoder, así que, si puedes, vale la pena dedicar tiempo a hacerla funcionar en tu propia base de código.
Decían que no usan mucho los depuradores más allá de ver un stack trace o el valor de una o dos variables, y que es fácil perderse en los detalles de estructuras de datos complejas y flujos de control; consideraban más productivo poner sentencias de salida y código de autoverificación en puntos clave.
En general estoy de acuerdo. Para casi todo lo que hago, el ciclo hipótesis-log-ejecución me lleva a la respuesta mucho más rápido. No se trata de intentar ejecutar el código en la cabeza, sino de que ya tengo un modelo de trabajo de cómo se ejecuta el código, sé qué salida debería producir si ese modelo es correcto, y con una salida incorrecta puedo intuir rápidamente qué está pasando en realidad.
[0] The unreasonable effectiveness of print debugging (349 points, 354 comments) April 2021 https://news.ycombinator.com/item?id=26925570
En una arquitectura de malla de microservicios, incluso ejecutar algo correctamente en local es difícil, y muchas veces los entornos de prueba tampoco están configurados para poder adjuntar un depurador paso a paso. Al final solo queda depurar con
print, e incluso eso no sirve si el sistema de logging en sí es el problema o si el programa muere antes de hacer flush de los logs.Al poco tiempo siempre hace falta. Pero incluso colaborando con otras personas, me sorprendió ver gente que directamente no sabe usar un depurador. Les dices “pon un breakpoint ahí”, “ahora entra en la función y mira el estado de las variables”, “eso sáltalo”, y cada vez te devuelven una mirada en blanco.
Estrictamente hablando, no pongo y luego borro sentencias
print, sino que agrego código de logging que voy a dejar. Si es una interfaz importante, normalmente empiezo registrando a nivel INFO la entrada/salida de funciones y los valores de los parámetros, y a medida que uso el sistema, si veo lugares que conviene investigar más, agrego logs más detallados.También cuido bastante el formato de los logs. Cuando trabajaba con sistemas distribuidos, fue muy útil que el prefijo de cada línea de log estuviera exactamente alineado. Poniendo el ID del nodo, el pid y el timestamp con ancho fijo, podía descargar y ordenar los logs de todo el clúster, y ver el comportamiento de varios nodos intercalado en un solo archivo.
Pero en cuanto el depurador se mete en lo profundo de una biblioteca o framework que uso, me pierdo y deja de gustarme. Ese framework o biblioteca acumula decenas de miles de horas-persona de trabajo, y siento que está muy por encima de mi nivel.
Hay muchas frases que son joyas acá, pero esta sobre microservicios es la que más me gusta: “grug se pregunta por qué gente inteligente toma el problema más difícil de dividir correctamente un sistema y encima le mete llamadas de red”
Están felices de partir una web app trivial con unos cinco formularios en “microservicios”, hacer que compartan la misma base de datos, agregar una capa de gestión de API, una cola para trabajos batch masivos (de megabytes), un sistema de notificaciones por email y hasta una plataforma de observabilidad hecha en casa, y luego convertir formularios web simples en una SPA porque “así es más fácil”
Ahora entiendo que la “arquitectura” y los “patrones” son un programa de creación de empleo para desarrolladores inútiles. Si no fuera por eso, probablemente estarían en la calle con un cartel que diga “escribo JavaScript por un sándwich”
Para ellas, si algo no está expuesto como una llamada de API, no es más que un bloque opaco de código que no pueden entender ni reutilizar
Varios paquetes pueden colaborar internamente y exponer solo una API pequeña al resto del codebase. El hecho de que haya una red obliga a los módulos a intercambiar solo datos, no callbacks ni comportamiento, y los presiona a hacer evolucionar las interfaces de forma compatible hacia atrás. Así se puede hacer “hot reload” de distintos módulos en momentos distintos sin que todo explote
Creo que se podría obtener casi todo eso sin un salto de red real, pero todavía no he visto un intento serio
No se pueden ejecutar sin orquestadores como K8S, y como son difíciles de instalar y operar, favorecen la venta de nubes administradas. Hacen que se use más ancho de banda de red y más CPU, y ambas cosas se cobran
Dificultan compartir y mantener estados complejos o grandes dentro de la aplicación, lo que empuja a usar bases de datos administradas o servicios de colas de eventos como sustitutos. En un monolito bastaría con usar una cola o canales, pero en microservicios terminas queriendo una bestia enorme como Kafka
También se vuelve difícil ejecutarlos localmente, así que necesitas entornos de desarrollo en la nube, y quizá varios entornos de desarrollo y prueba. Terminas dependiendo de características como la forma de networking de una nube específica, lo que aumenta el lock-in en la nube
No sé si recuerdan la época en que la nube se vendía como algo que reduciría los costos de IT. Era ridículo; desde los 2000 sabía que era una pavada y pensaba que al final iba a aumentar todos los costos
Porque así es más fácil de gestionar; es más una cuestión de forma de organizar el desarrollo que una decisión técnica. Si la alternativa es un monorepo, personalmente creo que eso es peor
“Si grug tiene que elegir entre complejidad y un 1:1 contra un tiranosaurio, grug elige al tiranosaurio. Al menos grug puede ver al tiranosaurio”
Pienso en esta frase al menos una vez por semana
Este grug está en un 1:1 contra un tiranosaurio transparente, y está maldito
Uno de los valores de este texto es que alguien capaz de hacer cosas más sofisticadas y complejas por experiencia intenta evitarlas deliberadamente
Sin duda hay momentos y lugares donde se necesita sofisticación y un mayor nivel de abstracción. Pero la filosofía grug dice que hacer esas cosas no tiene valor intrínseco, y me parece un consejo bastante razonable
También he visto que la asistencia con IA funciona mejor con código consistente, común y centrado en datos. Puede depender del contexto
El desarrollador principiante escribe código simple, el desarrollador intermedio escribe código complejo y el desarrollador experto vuelve a escribir código simple
Qué clase se puede dar por supuesto que alguien ya tomó depende de cada contexto
Una de las ironías del desarrollo de software moderno es que introducimos complejidad pensando que “al final nos va a ahorrar tiempo”.
A veces es cierto y de hecho ahorra tiempo, pero no siempre, y quizá ni siquiera con frecuencia.
DRY a veces lleva a abstracciones prematuras. En el momento en que piensas “este patrón también se va a usar en otros lados, así que voy a extraer la parte común”, entra el demonio de la complejidad.
Queremos atrapar tantos bugs como sea posible en tiempo de compilación, pero para eso el compilador tiene que saber más sobre lo que realmente intentamos hacer, y terminamos creando tipos complejos que erosionan la comprensión.
Creamos macros complejas o DSL para evitar boilerplate, pero por la ley de las abstracciones con fugas, en el momento en que hay que conocer la implementación real, la cabeza explota.
Lo difícil de todos estos ejemplos es que a veces son buenas ideas. Creo que saber juzgar cuándo introducir complejidad resulta en simplificación es señal de un buen ingeniero de software.
Idealmente, la lógica de negocio debería definirse en un solo lugar, pero otras cosas pueden duplicarse si hace falta, y eso no es malo en sí mismo.
Para moderar DRY también se enfatiza la “regla de tres”: copiar/pegar código está bien hasta tres veces, y después vale la pena considerar una abstracción. Por supuesto, no hay una regla empírica que sirva para todos los casos, y ese instinto es difícil de enseñar.
Normalmente hago apps web con frameworks tipo Sinatra, como Flask, y escribo funciones que responden a patrones de URL. Una “pantalla” puede consistir en una o más funciones que trabajan juntas y las plantillas HTML correspondientes.
Si la aplicación configura cosas como la conexión a la base de datos, ubicaciones de archivos y encabezados/pies de página HTML, fuera de eso hay muy poco acoplamiento entre pantallas. Si necesito una pantalla nueva, puedo copiar una existente y modificarla, o pedirle a un LLM que cree la pantalla o el endpoint, y si el resultado es malo, volver a hacerlo.
En un trabajo anterior estábamos creando un framework llamado Themis para generar conjuntos de entrenamiento de ML, usando microservicios, React, Docker, etc. El requisito real era seguir agregando tareas nuevas y, para cada tarea, crear rápidamente una pantalla simple pero muy optimizada. Si hay que hacer 20.000 juicios, ya cuesta hacer un clic por cada uno; si son cuatro clics, se vuelven 80.000 y uno se rinde.
Con la estructura de ese momento, había que escribir el endpoint de API de una aplicación JAXB y los componentes de una app monolítica en React, y luego esperar 20 minutos a que TypeScript, Docker y javac terminaran. Con suerte arrancaba; si no, había que empezar de nuevo.
Escribí una crítica de Themis y diseñé Nemesis con el objetivo de desarrollar tareas nuevas rápidamente, pero en mi trabajo anterior fue el camino no tomado. Aun así, Nemesis y yo hemos procesado desde entonces millones de instancias de tareas.
No significa que haya que extraer un helper que solo se usa en un lugar. Aunque haya un montón de lógica en una función, clase, archivo, etc., si no está copiada, sigue siendo DRY.
Las abstracciones prematuras sí existen. Tampoco ayuda que las clases de CS en general enseñen a hacer eso. Si le das a un junior una base de datos MySQL, puede que lo primero que intente hacer sea abstraer MySQL.
Sea complejo o simple, si el resultado no aporta valor, no importa. Primero hay que concentrarse en aportar más valor del que se quita, y preocuparse por la complejidad después.
Una de mis formas favoritas de usar LLM es pasarle este ensayo y pedirle que comente el problema que estoy tratando en ese momento con la persona de grug-brained developer. Es bueno para aliviar el estrés.
Me pregunto si basta con empezar el prompt con algo como “actúa como el Grug Brained Developer de este ensayo”.
“La complejidad es muy mala” es totalmente cierto
En todos mis años de ingeniería de software, esta idea ha sido uno de los pocos principios que se ha demostrado constantemente verdadero en todas las situaciones. Algunos problemas son complejos por naturaleza, pero incluso entonces es mucho mejor tomarse el tiempo para llegar a la solución más simple
Mis trabajos más efectivos llegaron después de cuestionar enfoques anteriores y simplificarlos de forma radical. Puede que se pierda algo de flexibilidad potencial, pero la mayoría de las veces no hacía falta tanta flexibilidad como creíamos
Por ejemplo, desde que existen LLM bastante buenos y capaces de actuar como agentes, evito los tipos de TypeScript excesivamente complejos, frágiles y difíciles de depurar, y escribo código a modo de especificación para luego pedirle al LLM que genere estáticamente el código basado en eso
La dependencia de ESLint del proyecto se rompía constantemente después de actualizar versiones, muchas reglas no eran lo bastante sofisticadas para evitar falsos positivos, y mantenerlo bien con TypeScript y VSCode también era complejo. Al cambiar a Biome.js, resultó más simple y suficientemente efectivo, pero últimamente le están apareciendo bugs. Aun así, me di cuenta de que el linting es algo útil, no algo a lo que haya que dedicarle demasiado tiempo de mantenimiento, así que lo quité de la cadena de herramientas de build, y ya no hace falta tenerlo siempre activado en VSCode. Basta con ejecutar Biome de vez en cuando para revisar solo el estilo y el formato del código
Al crear una herramienta personalizada de migración de datos para el proyecto, consideré que las migraciones hacia adelante eran necesarias, pero que las migraciones hacia atrás no valían el tiempo ni la complejidad de implementarlas. Si hay que hacer rollback de una base de datos con datos, se puede restaurar un backup; y si no hay datos o no es una DB de producción, se puede empezar desde cero con un script de inicialización versionado
Tampoco tengo claro cómo el tercero es más simple. Con una mentalidad matemática, se puede crear fácilmente un espacio biyectivo. Imitar las migraciones hacia atrás por otros medios podría ser más difícil. Claro que depende de los detalles, así que no es una regla general
El inteligente Rich decía que complect es entrelazar cosas, y en eso estoy de acuerdo. Rich decía que la complejidad es mala, pero con eso no estoy de acuerdo. Entrelazar cosas es necesario. Si no están conectadas entre sí, no se pueden resolver problemas
No puedo creer que este texto sea de 2022
Habría dicho con seguridad que lo leí hace 10 años y que ya era un clásico en ese entonces
Triste, pero cierto: el consejo profesional ideal es aprender a decir “sí” y luego, si algo falla, aprender a echarle la culpa a otro grug
Cuando entré por primera vez al mundo corporativo, pensé que esto no era verdad y que simplemente faltaba comunicación en los equipos técnicos. Yo estaba equivocado, y grug tenía razón