1 puntos por GN⁺ 2025-01-12 | 1 comentarios | Compartir por WhatsApp
  • El efecto Makefile se refiere al patrón de no usar desde cero herramientas complejas o poco familiares, sino copiar una configuración que funcionó bien antes y adaptarla a una nueva situación
  • En la etapa de resolución de problemas, reutilizar ejemplos verificados puede ser una respuesta de ingeniería razonable, porque reduce el riesgo de bugs
  • Pero, desde la perspectiva del diseño, puede verse como una señal de usabilidad: la herramienta es difícil o molesta de usar desde cero, por lo que los cambios se van acumulando en copias
  • Flujos similares de copiar y modificar aparecen no solo en Make, sino también en GitHub Actions, GitLab CI/CD, configuraciones de linters y formatters, y sistemas de build
  • Quienes diseñan herramientas deberían revisar si la configuración y la sintaxis propia son realmente necesarias, si pueden reutilizar convenciones familiares de CLI y si los usuarios terminan dependiendo de copiar y pegar

Qué es el efecto Makefile

  • El efecto Makefile es el fenómeno de no escribir desde cero una herramienta con cierto nivel de complejidad o falta de familiaridad, sino copiar y modificar un ejemplo anterior que “funcionó bien”
  • El flujo típico es el siguiente
    • Hay que completar una tarea común, y una tarea muy similar o idéntica ya se realizó antes
    • Make o una herramienta parecida se elige como la herramienta adecuada para esa tarea
    • En lugar de escribir un nuevo Makefile, un ingeniero copia el Makefile de un trabajo anterior y lo ajusta hasta que funcione en el nuevo entorno
  • Make es solo el caso representativo; como cada equipo o persona domina herramientas distintas, algunas familias de herramientas son más propensas a este efecto y otras relativamente menos
  • El chiste de un “Makefile heredado” se refiere a la situación en la que un Makefile recibido de un ingeniero senior, un profesor, etc., se sigue transmitiendo con apenas pequeñas modificaciones

Problemas de diseño de herramientas que revela la copia repetida

  • Cuando hay que resolver un problema de inmediato, reutilizar un ejemplo que funciona puede ser una opción simple
    • Como la mayor parte del trabajo no cambia, en teoría se pueden introducir menos bugs nuevos
    • El efecto Makefile no es, en esencia, un fenómeno malo ni ineficiente
  • En el momento de diseño, se vuelve una señal de que la herramienta, o la forma de aplicarla, es demasiado compleja o engorrosa
    • Los usuarios copian una y otra vez soluciones existentes en vez de resolver el problema desde cero
    • Si este efecto aparece en casos de uso simples, la herramienta puede ser demasiado compleja para ese propósito
  • Este patrón también aparece en herramientas centradas en configuración fuera de Make
    • En configuraciones de CI/CD, se copia el YAML de GitHub Actions o GitLab CI/CD desde la última configuración que funcionó, se ejecuta repetidamente y se corrige
    • Las configuraciones de linters y formatters copian entre proyectos un conjunto de reglas base, y luego lo endurecen o relajan según las condiciones locales
    • Las configuraciones no triviales de sistemas de build tienden a parecerse a sistemas de build anteriores

Costos en aprendizaje, debugging y seguridad

  • Las herramientas donde el efecto Makefile es fuerte pueden obstaculizar un aprendizaje amplio
    • Un pequeño grupo de personas expertas configura la herramienta, y el resto copia y modifica entendiendo solo las partes necesarias
    • El grafo de dependencias de un sistema de build tiene una complejidad inherente, pero tener que recordar la diferencia entre $< y $^ en Make no la tiene
  • Cuando falta soporte de diagnóstico y debugging, los usuarios terminan dependiendo de ejecutar la herramienta muchas veces y obtener solo pequeñas pistas
    • En CI/CD, se termina haciendo debugging estilo print atravesando capas de red y orquestación de VMs intermedias
  • También pueden aumentar los riesgos de seguridad
    • Las medidas de seguridad suelen requerir entender en profundidad el “por qué” del comportamiento
    • Los sistemas donde aparece el efecto Makefile a menudo permiten confusiones entre código y datos, o confusiones generales de señalización en banda (in-band signaling)
    • La inyección de plantillas de GitHub Actions es un ejemplo representativo

Preguntas que deben hacerse quienes diseñan herramientas

  • Al crear una herramienta nueva, hay que observar si los usuarios pueden escribirla desde cero o si solo pueden buscar y copiar un “ejemplo que funciona”
  • Estas son algunas preguntas de revisión
    • ¿La configurabilidad es realmente necesaria?
    • ¿Hace falta una sintaxis propia?
    • ¿Se pueden reutilizar la sintaxis y las convenciones familiares de otras herramientas o de la CLI?
    • ¿Incluso quien la creó termina copiando y pegando su uso? Si es así, es probable que otros usuarios también lo hagan
  • Sobre el término “cargo cult”, el artículo de Ken Shirriff aborda el problema de que es una jerga técnica inapropiada

1 comentarios

 
GN⁺ 2025-01-12
Opiniones de Hacker News
  • “Invariablemente se descubre que un sistema complejo que funciona evolucionó a partir de un sistema simple que funcionaba. Un sistema complejo diseñado desde cero nunca funciona, y no se puede parchar para que funcione. Hay que volver a empezar con un sistema simple que funcione”
    — John Gall (1975), Systemantics: How Systems Really Work and How They Fail
    https://en.wikipedia.org/wiki/John_Gall_(author)#Gall's_law

    • Por eso siempre soy escéptico con los lenguajes y frameworks nuevos. En las diapositivas de PowerPoint se ven geniales, pero no queda claro cómo se verán en un sistema complejo y mantenido durante mucho tiempo.
      Normalmente empiezan a pegarles funcionalidades como parches para manejar casos especiales, y eso parece una señal de que esas excepciones seguirán multiplicándose sin fin.
      Hay una diferencia sutil pero importante entre “aplicar la experiencia” y “diseñar un sistema completamente nuevo alrededor de una sola cosa que a uno le molesta personalmente”.
  • Tengo otra hipótesis. Alrededor del 10% de los desarrolladores realmente entiende los principios y puede empezar algo desde cero, y otro 40% puede ocuparse del trabajo diario copiando y pegando desde código local, Stack Overflow, GitHub y LLM, entendiendo más o menos qué está pasando.
    El 50% restante no sabe mucho más allá de algunos acertijos de LeetCode, y tampoco tiene una comprensión real de lo que copia y pega.
    Con esa distribución, es probable que más de la mitad de los Makefile sean fragmentos copiados y pegados que funcionan más o menos. Si lo trajeron de algún lugar donde ya funcionaba, entonces el trabajo queda hecho por ahora y pasan al siguiente ticket.
    No estoy culpando a la herramienta en sí. Makefile es bien conocido y no es verboso en proyectos pequeños. Puede ser una mala elección para un monstruo de 10,000 archivos, pero también he visto Makefile limpios en proyectos enormes. Personalmente no sería mi primera opción, pero me gustan los Makefile y los he usado de forma intermitente durante más de 30 años.

    • Creo que lo que la gente no sabe muchas veces se debe a que no tiene tiempo para averiguarlo. Hay un límite a la cantidad de batallas que uno puede pelear en un día.
      Por ejemplo, cuando un programador de C++ trabaja en un ticket, ¿hasta cuántas capas del stack necesita conocer? ¿Tiene que saber los nombres de los registros de la CPU? ¿Qué necesita saber un investigador de IA que solo usa Jupyter? Es recomendable aprender lo máximo posible sobre las herramientas y el stack, pero el tiempo es limitado.
    • Usar un Makefile muy simple incluso en un proyecto de 10,000 archivos es, en realidad, algo trivial. La mayoría de los Makefile que vi en proyectos open source eran mucho más complejos que un buen Makefile.
      Si en un Makefile veo reglas individuales para construir un único archivo, casi siempre lo considero un error.
      Normalmente solo debería haber reglas generales de build para construir todos los archivos de un tipo determinado.
      Un Makefile casi nunca debería contener listas de archivos fuente ni listas de dependencias; debería bastar con una lista de directorios donde están los archivos fuente.
      Make debería buscar en los directorios fuente para encontrar archivos, clasificarlos por tipo, crear la lista de dependencias y llamar a la regla de build adecuada. Al menos en GNU make es muy sencillo y está en el manual de usuario.
      Escrito así, el esfuerzo de crear o modificar el Makefile es igual de insignificante tanto si el proyecto tiene 1 archivo como si tiene 10,000. Tampoco hace falta actualizar el Makefile cada vez que se crea, renombra, mueve o elimina un archivo fuente.
    • Me gustan los Makefile, pero solo los uso para cosas personales. Cada vez que creo un proyecto personal nuevo, agrego un Makefile en la raíz, aunque el target sea solo el build más básico de ese lenguaje.
      Es porque cada lenguaje y framework tiene un “orden” de build distinto y no puedo recordarlos todos. Pero $ make es fácil.
    • Creo que poder configurar algo y entender de verdad cómo funciona son cosas bastante distintas.
      Estoy de acuerdo en que muchos desarrolladores de apps productivos quizá no puedan configurar un proyecto nuevo desde cero, pero muchas veces eso es más una cuestión de conocer las reglas mágicas y los conjuros correctos para hacer encajar varias herramientas que de tener una comprensión profunda.
  • Otro factor que he visto en el trabajo son las herramientas y sistemas que los desarrolladores tendrán que manejar algún día, pero que normalmente sienten que tienen poco valor aprender
    Por ejemplo, los sistemas de build y la configuración de CI. Son indispensables, pero los desarrolladores no creen que deban lidiar con ellos todos los días. CI se ve como un sistema de “configurar y olvidar”, y la reacción es: ¿de verdad tengo que aprender todo esto solo para compilar la app?
    Los desarrolladores esperan que “simplemente funcione”, y si hay complejidad, asumen que la maneja otro equipo, alguien con un rol como el mío. Por eso, cada vez que un desarrollador toca este sistema, tiene un fuerte incentivo a copiar la última configuración que funcionaba, pasarla adelante y volver al trabajo “de verdad”
    La mejor solución es encontrarse con los desarrolladores a medio camino. Hay que ofrecer herramientas tan simples o complejas como requiera la tarea, tener documentación y minimizar la creencia de que es “magia”. Herramientas como Make suelen fallar en este punto porque se sienten demasiado complejas y como una caja negra

    • Creo que el gran problema de la configuración de CI suele ser este: es lenta, propietaria, maneja secretos que limitan quién puede trabajar con ella y, en general, no se puede ejecutar localmente
      Por eso el ciclo de retroalimentación se vuelve larguísimo y el trabajo en sí se vuelve muy doloroso
    • El mejor flujo que he visto fue usar CI/CD como un wrapper muy delgado alrededor de scripts o un Makefile dentro del repositorio
      Si un desarrollador puede ejecutar localmente parte o la totalidad del trabajo de “CI/CD”, puede verlo, controlarlo y entenderlo. Eso ayuda muchísimo a dar sensación de propiedad y seguridad, en lugar de sentir que “CI/CD es cosa de otro mundo”
      No siempre funciona. Una vez un equipo de dos personas tenía CI/CD con un wrapper delgado y aun así lo trataba como un proceso alienígena y se negaba a tocarlo
    • La cafetera de la oficina tampoco es algo de “configurar y olvidar”, pero no se espera que todas las personas que la usan compartan por igual la responsabilidad de mantenerla
      Del mismo modo, CI necesita propiedad, y hacer que la responsabilidad recaiga en el último desarrollador que intentó usar CI no es una forma eficiente de trabajar
    • Make es una de las herramientas de build más simples. Comparado con cosas como Grunt o Webpack, se parece más a un martillo que a un taladro minero
      La solución es no adoptar herramientas que usan las grandes empresas solo porque las usan las grandes empresas. Será una opinión impopular, pero creo que CI/CD no hace falta en la mayoría de los lugares donde se usa
      Aunque implique algunos pasos más, hay que encontrar una forma de hacer build y despliegue minimizando absolutamente las partes móviles. Después hay que evaluar con cuidado el costo de qué partes automatizar. Incorporar un sistema grande para hacer algo simple normalmente no vale la pena a largo plazo
      Si de verdad necesitas CI/CD, lo vas a saber porque habrá un punto de dolor. Si ese sistema les causa dolor a los desarrolladores, no es la herramienta adecuada
    • Si make te parece “demasiado complejo y como una caja negra”, es porque todavía no viste cmake
  • Make y los Makefile son increíblemente simples cuando no los generó automáticamente autoconf. Si los generó autoconf, no deberías modificarlos: son un artefacto de build. Si es posible, también conviene deshacerse de autoconf
    En términos más amplios, este efecto realmente existe. Puedes ser víctima de él o aprovecharlo. La forma de aprovecharlo es esta: escribes un poco de código o lo copias y pegas de algún lado, lo usas en un proyecto y lo ajustas según haga falta. Cuando empiezas el siguiente proyecto, vuelves a copiar ese código y lo modificas para el segundo proyecto. Ves si puedes aplicar esos cambios de vuelta al proyecto original. Si ambos funcionan y están sincronizados, extraes ese código y lo conviertes en una biblioteca. A veces hacen falta más proyectos para refinarlo hasta que tenga forma de biblioteca. En el mejor de los casos, lo publicas como open source para que otros también lo usen

    • Los Makefile también son extremadamente limitados. La determinación de actualidad basada en timestamps se rompe con frecuencia en los sistemas modernos de control de versiones
      Como Git no registra timestamps internamente, el mtime de un archivo puede actualizarse aunque el contenido sea el mismo, y de hecho eso pasa a menudo, lo que provoca rebuilds innecesarios
      Tampoco maneja bien en absoluto herramientas modernas cuyas entradas o salidas son directorios completos, ni herramientas cuyo nombre de salida no se conoce antes de ejecutarlas
      Me gusta make, lo he usado bien a pesar de sus desventajas y conozco los workarounds y hasta los workarounds de esos workarounds. Pero cuando una herramienta empieza a no encajar perfectamente con las expectativas de make, la dificultad y la complejidad de escribir un Makefile correcto se disparan rápidamente
    • Los Makefile son simples, pero muy a menudo están mal. Es sorprendentemente difícil escribir un Makefile que funcione correctamente en situaciones que no sean “compilar desde cero”
      No es broma. El hecho mismo de que exista el concepto de make clean es una prueba concluyente
  • Makefile quizá sea una analogía equivocada. El problema que la mayoría de la gente tiene con los Makefile es que los escribe muy rara vez. Tienen una idea general de lo que hace make, pero no tienen la memoria muscular para escribirlos desde cero
    Aun así, el punto es correcto. He visto mucho código copiado de la web con un montón de código muerto que en realidad no se usa. Una buena práctica es seguir borrando hasta que algo se rompa, y revertir solo la parte que se rompió. Hay que borrar todo lo posible, y definitivamente todo lo que no se esté usando actualmente

    • El mismo problema aparece en muchas herramientas: Makefile, configuración de KVM, configuración de Docker, pipelines de CI/CD, etc.
      Mi solución hasta ahora ha sido crear un repositorio separado con notas y programas de ejemplo en scripts de shell sobre estas herramientas, bibliotecas y frameworks
      Cada vez que necesito volver a usar una herramienta, consulto las notas para refrescar la memoria, y si aprendo algo nuevo, las actualizo. Ahora también puedo apuntar un LLM a ese repositorio y hacerle preguntas
      Ese repositorio es personal y contiene información sobre herramientas disponibles públicamente. El conocimiento específico de una organización lo guardo en un repositorio similar pero separado, y lo destruyo cuando termina el contrato con el cliente o el empleador
  • La mejor forma de llamar a esto es desarrollo de culto cargo. El culto cargo surgió en el Pacífico durante la Segunda Guerra Mundial, cuando los isleños nativos vieron aviones casi milagrosos que traían comida, alcohol y suministros, y empezaron a imitar lo que hacían los soldados, rezando para que aviones de bambú y dispositivos de coco impresionaran a los dioses y los suministros volvieran a llegar.
    El problema era que los isleños no entendían la ciencia detrás de cosas como aviones, radios y armas.
    De la misma manera, el desarrollador de culto cargo ve los posibles resultados, pero no entiende los primeros principios; imita las acciones de los sumos sacerdotes de la tecnología con la esperanza de poder copiar el éxito.
    Así termina repitiendo copiar, pegar, probar, toquetear, buscar, jalar, empujar y ajustar, esperando que esta vez más o menos funcione, aunque sea con ciertos datos de un martes por la noche.

    • Yo no vería esto como desarrollo de culto cargo. El culto cargo se parece más a imitar sin entender una práctica que solo funcionaba dentro de un contexto más amplio, aun cuando ese contexto ya desapareció.
      En la práctica, consiste en repetir acciones o rituales que por sí mismos no tienen efecto, esperando obtener los resultados que obtuvo otra empresa que realizó los mismos rituales.
      Lo que dice el artículo original es distinto. Lo que se copia, el código, sí tiene efecto por sí mismo. Se puede probar y determinar si funciona.
      Esta distinción es importante. Como los síntomas del efecto Makefile del artículo original y los del culto cargo son distintos, tratarlos como si fueran lo mismo dificulta el diagnóstico. En el culto cargo se pierde tiempo, por superstición, en cosas que en realidad no funcionan. En el efecto Makefile, algo que se ha demostrado que funciona se copia junto con fragmentos parecidos a órganos vestigiales, y con el tiempo se vuelve cada vez más difícil de mantener.
    • Soy el autor del artículo. Esto está en la nota al pie 1.
      En concreto, no creo que esto sea exactamente igual al culto cargo. El culto cargo implica falta de comprensión. Pero uno puede entender bien un sistema y aun así vivir principalmente copiando y pegando, porque la complejidad intrínseca de ese sistema incentiva hacerlo. Ese era el punto central del artículo.
    • Muchas veces copio y pego cosas que yo mismo escribí en el pasado. Sucede cuando una herramienta es demasiado molesta y el costo de cambio de contexto cognitivo es difícil de asumir.
      Normalmente entiendo lo que ocurre por dentro, pero entrar en ese “modo” es cognitivamente pesado, así que lo evito cuando puedo.
      Las herramientas que caen en esta categoría suelen ser herramientas de operaciones, como CI/CD, k8s y Docker: tienen una complejidad enorme, pero no son el problema central que estoy resolviendo. Make, en particular, hace difícil evitar el cambio de contexto, así que últimamente simplemente lo evito.
      No tiene que ver con conjuros mágicos; sé cuál es el compromiso que estoy haciendo. Aun así, existe el riesgo de que se convierta en el efecto Makefile.
    • El artículo también lo trata en cierta medida en la nota al pie 1:
      “El efecto Makefile se parece a otros fenómenos, como el culto cargo, la normalización de la desviación y los ‘lenguajes de solo escritura’. En este artículo sostendré que difiere un poco de cada uno en que no es inherentemente ineficiente ni malo, sino que aborda el resultado de ciertos diseños”.
    • Siento que mi comprensión de la programación está cerca del nivel de “principiante entre principiantes”. Entonces, ¿significa eso que para evitar una mentalidad de “culto cargo” hace falta un flujo estructurado como educación → aprendizaje → ejecución → fracaso → repetición?
      Entonces, si alguien de verdad quiere “salir de la isla y pilotar el avión”, ¿eso significa que, al final, la universidad es el camino más cercano a lo “real”?
      Sé que es difícil hablar en términos absolutos, que hay muchas excepciones y que cada persona tiene distintas formas de justificar “yo no puedo hacer eso”. Aquí lo pregunto desde el punto de vista de una teoría óptima. El camino óptimo para evitar conductas de culto es entender el todo, y ¿ese “todo” no viene de la educación superior?
      Lógicamente, aunque uno estudie con seriedad a partir de libros e intente igualar o superar la “integridad” de la universidad, en ciertos aspectos parece que quedaría por debajo del tiempo pasado con docentes conocedores y en un aula. Incluso suponiendo que la misma persona se esfuerce igual en ambos caminos.
  • Otro factor es la frecuencia de uso. Escribo textos grandes en LaTeX una vez al año o menos.
    El nivel de LaTeX que uso no es el de una herramienta difícil, pero si solo la uso unas semanas al año, no hay forma de recordar los detalles de uso, así que normalmente empiezo un documento nuevo copiando y pegando uno anterior.

    • Tecnologías que uso con relativa frecuencia, como Makefile y scripts de shell, intento aprenderlas para evitar el “efecto Makefile”.
      Pero con LaTeX, aunque antes lo usé muchísimo, siempre copiaba y pegaba desde una plantilla. Las presentaciones en beamer y los diagramas en TikZ eran aún peor: copiaba desde presentaciones o diagramas anteriores en vez de desde una plantilla.
      TikZ me parece una herramienta intrínsecamente compleja y creo que no la aprendí lo suficiente.
      LaTeX creo que sí lo aprendí lo suficiente, así que puede haber otra razón.
      En mi opinión, puede ser un problema de valores predeterminados razonables. Una buena herramienta debería ofrecer buenos valores predeterminados. Pero LaTeX no es una buena herramienta según ese criterio. Mis documentos suelen empezar más o menos así:
      \documentclass[paper=a4, DIV9, 12pt, abstracton, headings=normal,  
      captions=tableheading]{scrartcl}  
      \usepackage[T1]{fontenc}  
      \usepackage[utf8]{inputenc}  
      \usepackage[english,german,ngerman]{babel}  
      \usepackage[english,german]{fancyref}  
      % ...  
      \usepackage{microtype}  
      \usepackage{hyperref}  
      
      La mayor parte de esto sirve para obtener soporte básico no ASCII necesario para mi idioma nativo, o para activar valores predeterminados razonables como papel A4 y microtype. Con herramientas modernas como pandoc/Markdown, quizá no haría falta.
      Así que muchas veces el objetivo de copiar y pegar estas cosas es obtener buenos valores predeterminados que una mejor herramienta habría ofrecido desde el inicio.
    • Uso LaTeX con más frecuencia, pero yo también empiezo con un encabezado copiado y pegado. Incluye los paquetes necesarios para mi idioma (fontenc, babel, paquetes locales de composición tipográfica), paquetes que casi siempre necesito como graphicx/fancyhdr/hyperref/geometry, y definiciones de símbolos y nombres útiles en mi campo.
      Si no vas a escribir sin matemáticas o solo texto puro en inglés, LaTeX es casi como “baterías no incluidas”.
  • Esto es desarrollo impulsado por copiar y pegar [0], y tampoco es algo relacionado solo con Makefile. Tiene que ver con que toda la industria copia código de un lado a otro sin saber siquiera qué está copiando
    Sinceramente, creo que Copilot empeoró esto, porque estamos aceptando bloques de código en las bases de código con los ojos cerrados
    [0] https://andrew.grahamyooll.com/blog/copy-pasta-driven-develo...

    • Hay que culpar a la gente del lado de negocio. Intenté convertirme en experto en make probablemente al menos 7 veces seguidas, pero nunca me dieron tiempo para lidiar con eso todos los días hasta memorizarlo por completo
      En algún momento simplemente me rendí. Se volvió un conocimiento arcano y esotérico con el que no podía desarrollar memoria muscular, y que tenía que volver a aprender desde cero cada vez que lo necesitaba. Así que me pasé a herramientas más simples
      La pérdida del trabajo profundo no es culpa de los buenos programadores, sino de la gente del lado de negocio
  • He visto demasiadas veces cómo copian tal cual un Makefile de otro proyecto y luego lo modifican para que “funcione”, dejando intactos pasos y targets de build innecesarios que no tienen ninguna relación
    Personalmente, eso me molesta mucho

    • Si no hay absolutamente nada de documentación, ¿cómo se supone que sabes qué es relevante y qué no?
      ¿Por prueba y error? Entonces, que te diviertas :p
  • Es cierto que hay muchas herramientas más complejas de lo necesario, pero no creo que Make en sí sea un buen ejemplo de eso
    En las herramientas modernas, el problema de la complejidad suele empeorar por documentación deficiente, documentación tipo recetario, documentación que no explica el modelo conceptual necesario para razonar sobre cómo funciona la herramienta, o documentación sin especificaciones suficientemente detalladas y precisas
    Make no es así. Está bien documentado y, si realmente te tomas el tiempo de leer la documentación, no es difícil de entender
    El enfoque tipo recetario mencionado arriba lleva a una cultura que subestima la importancia de aprender y entender las herramientas que uno usa, y de la documentación exhaustiva que lo hace posible. O quizá la causalidad sea al revés
    En cualquier caso, creo que el problema es que dedicamos muy poco tiempo a crear documentación de herramientas que sea completa y esté actualizada, que no diseñamos lo suficiente las herramientas para que esa documentación sea posible desde el principio, y que asignamos muy pocos recursos a enseñar y aprender las herramientas necesarias