2 puntos por GN⁺ 2025-01-26 | 1 comentarios | Compartir por WhatsApp
  • La cultura de desarrollo donde instalar paquetes se volvió la opción por defecto alimenta el dependency churn, que deriva en actualizaciones, parches, auditorías y gestión de dependencias transitivas, creando un costo oculto para la productividad
  • Mientras más maduro es el ecosistema de empaquetado, como en JavaScript y Rust, mayor es el impacto; hay casos como un proyecto nuevo de Tokio con 28 crates, Rocket con 172 crates y la CLI de MiniJinja con 142 dependencias
  • Incluso una función estable desde hace mucho, como terminal_size, cae en la paradoja de arrastrar crates adicionales y lanzamientos repetidos por cambios en las bibliotecas de abstracción de plataforma
  • Para funciones pequeñas, crear una implementación sin dependencias con ChatGPT o Cursor puede ser más rápido que buscar dependencias y mantener actualizaciones continuas, además de reducir la carga de compilar código externo
  • Bibliotecas para áreas difíciles como HTTP, QUIC, gráficos o tokio sí son necesarias, pero conviene cuestionar más la decisión de aceptar un gran grafo de dependencias por una sola función

El treadmill de mantenimiento que provoca el aumento de dependencias

  • Los desarrolladores instalan paquetes fácilmente por productividad, pero como resultado terminan atrapados en un dependency churn interminable de actualizaciones, parches, auditorías y gestión de dependencias transitivas
  • Este problema destaca más en ecosistemas con buenas soluciones de empaquetado, y JavaScript y Rust se ven especialmente afectados
  • Casos en Rust:
    • Un proyecto nuevo con Tokio trae 28 crates
    • Un proyecto nuevo con Rocket sube hasta 172 crates
    • MiniJinja puede existir con una sola dependencia, pero su variante CLI trae 142 dependencias
  • El problema central es que, por una función pequeña, se termina compilando y administrando mucho más código externo del realmente necesario

La paradoja del código estable que muestra terminal_size

  • terminal_size es, tal como indica su nombre, un crate para detectar el tamaño de la terminal
  • La API base que usa esta función ha sido en la práctica estable desde los inicios de las terminales informáticas, pero según el sistema operativo arrastra 3 o 4 crates adicionales
  • Se llega a la situación de compilar miles de otras funciones solo para verificar si la terminal es 80x25 o 120x40
  • Ese crate ha tenido 26 lanzamientos, pero una implementación propia puesta en un proyecto hace 10 años para la misma función sigue funcionando sin actualizaciones
  • La razón de tantos lanzamientos no es que la función haya cambiado, sino que las bibliotecas de abstracción de plataforma subyacentes siguen cambiando
  • En UNIX existe la excepción de necesitar una dependencia de libc
    • Rust no expone las constantes de libc de la plataforma, y esas constantes además no están estandarizadas
    • Aun así, libc es una dependencia común y ligera, por lo que es difícil evitarla

La seguridad y la cultura de reutilización de código refuerzan las dependencias

  • El enfoque tipo “big supply chain” desincentiva copiar y pegar funciones o usar unsafe directamente, y presiona para delegar en capas de abstracción de plataforma
  • También existen empresas que ofrecen herramientas para tratar problemas de dependencias y, en nombre de la seguridad, empujan a mantenerlas y actualizarlas constantemente
  • Pero muchas dependencias en sí mismas pueden ser una fuente principal de problemas de seguridad
  • El objetivo del código debería ser escribirse de forma que en algún momento alcance estabilidad y ya no necesite actualizaciones
  • En el ecosistema Rust, incluso una dependencia que funciona de forma estable puede recibir una mala evaluación en RUSTSEC si su bug tracker luce algo inactivo
  • La cultura corporativa de code review también influye en el open source
    • Es más probable que se recompense que se cuestione a un ingeniero que trae una biblioteca nueva y reluciente
    • Como resultado surgieron herramientas como Dependabot, y los proyectos reciben PR continuos de actualización de dependencias
    • Dentro de empresas, solo con vendoring, auditorías internas y upgrades a nivel organización ya se puede mantener ocupados de forma permanente a los equipos de ingeniería

Las funciones pequeñas se pueden hacer directamente

  • Un camino más simple es escribir directamente el código que se necesita
  • Puede requerir más trabajo inicial, pero una vez escrito, ese código no necesita crates nuevos ni obliga a esperar a que un autor upstream corrija edge cases
  • Si el código se rompe en su propio uso, se corrige directamente, y el código que funciona no tiene por qué subirse obligatoriamente al treadmill de mantenimiento
  • En 2025, herramientas como ChatGPT o Cursor pueden generar rápido una implementación sin dependencias para funciones pequeñas y comunes
  • Muchas funciones pequeñas tienen un overhead de mantenimiento bajo, y pueden resultar menos costosas que las actualizaciones continuas de dependencias
  • Si se trata de unas cuantas líneas de código, no hace falta compilar miles de líneas ajenas por una sola función

Valorar más las bajas dependencias

  • No todas las dependencias son malas
    • Bibliotecas gráficas que abstraen drivers complejos
    • Implementaciones de protocolos como HTTP y QUIC
    • Bibliotecas importantes como tokio, que no se pueden quitar ni hay intención de quitarlas
  • Aun así, si por usar una sola función terminas compilando cientos de funciones, eso debería verse como una señal de alerta
  • Deberíamos reconocer más la decisión de escribir funciones pequeñas directamente para evitar el grafo de dependencias transitivas
  • También deberíamos mirar con más sospecha los grandes grafos de crates y valorar positivamente el código simple y estable que puede quedarse años sin tocarse
  • sha1-smol se volvió originalmente, bajo el nombre sha1, el crate estándar para calcular hashes SHA1, pero después cedió ese nombre a rust-crypto y recibió presión para alinearse con un ecosistema crypto más grande
    • Si se usa el nuevo crate sha1, vienen 10 dependencias adicionales
    • Fue difícil evitarlo porque el nombre del registry importaba y había exigencias de compatibilidad con traits
  • MiniJinja destaca en su README sus pocas dependencias
$ cargo tree
minimal v0.1.0 (examples/minimal)
└── minijinja v2.6.0 (minijinja)
    └── serde v1.0.144
  • MiniJinja tiene un PR para eliminar su última dependencia
  • En las situaciones adecuadas, deberíamos celebrar el hazlo tú mismo y dar más reconocimiento a quienes crean bibliotecas open source con dependencias bajas o nulas

1 comentarios

 
GN⁺ 2025-01-26
Opiniones en Hacker News
  • Me gusta el lenguaje Rust en sí, pero no me gusta el ecosistema de dependencias de Rust. Mucha gente se queja de que en C++ es difícil agregar dependencias, pero más bien eso me parece una característica. Porque te obliga a pensar si realmente las necesitas
    En C++ yo controlo las dependencias, pero en Rust enseguida pasan de 100 y termino rindiéndome. Desde el punto de vista de seguridad, honestamente no puedo saber qué estoy distribuyendo
    Además, Rust no tiene compatibilidad ABI ni una cultura de bibliotecas compartidas. Por eso siento que rompe el modelo de distribución de paquetes del sistema operativo. Cuando eliges una distribución de Linux, estás confiando en las personas que construyen esa distribución; Rust se parece más a Python, donde cualquiera puede subir algo a PyPI

    • Debian/Ubuntu puede considerarse seguro porque los mantenedores firman y esos mantenedores están verificados. Al final, la estructura se basa en confiar en que Debian/Ubuntu solo permite firmar paquetes a personas confiables
      ¿Y Docker/Python/Rust? No conozco en absoluto a quienes hicieron mi imagen de Docker, mi paquete de PyPI o mi crate de Rust
      En la práctica, volvimos a la época de pasarnos EXE y DLL en archivos ZIP. Solo que ahora lo llamamos contenedor y lo ejecutamos orgullosamente con permisos de root
      Como dice el autor, a veces lo mejor es simplemente incorporar el código fuente de las dependencias. Antes a esto se le llamaba vendoring y era bastante importante en Rails/Ruby. Tiene la gran ventaja de no verse afectado después por tomas maliciosas de paquetes y, si quieres, puedes fusionar tú mismo los parches de seguridad del upstream
    • No sé en qué trabajan exactamente las personas con esta perspectiva como para poder reinventar la rueda en cada proyecto solo porque les asustan riesgos hipotéticos. La mayoría de esos riesgos siguen presentes en las reimplementaciones internas, y los bugs también
      No se me ocurre ningún proyecto en el que el síndrome NIH haya tenido un efecto neto positivo. Incluso las dependencias no esenciales ahorran muchísimo tiempo
      Si en cada proyecto se rehace una parte de las dependencias opcionales, me pregunto de dónde sale el tiempo para corregir bugs adicionales, casos límite, múltiples versiones de dependencias transitivas y soporte para varias plataformas
    • Si desde el principio existe un gestor de paquetes y se reduce mucho la fricción para agregar y distribuir paquetes, el ecosistema del lenguaje pierde algo muy útil: una buena presión de selección natural
      Si a eso se le suma un repositorio donde cualquiera puede subir cosas y con poca curaduría, como PyPI, npm o Cargo, junto con una biblioteca estándar pequeña, hay que prepararse para sufrir
    • Me pregunto si una función como “una colección de bibliotecas que se pueden traer fácilmente” se vuelve una antifunción por el hecho de facilitar traer bibliotecas de apoyo. Esto parece más un problema de decisiones de los desarrolladores que de Rust o JS
      El desarrollador elige qué dependencias traer. Las dependencias que nadie usa desaparecerán, y la razón por la que la mayoría de las bibliotecas tienen muchas dependencias es que muchos desarrolladores prefieren construir cosas encima de dependencias
      Si hablamos del sistema de build, Cargo no obliga a usar Crates.io. Solo lo facilita. También se pueden usar dependencias basadas en rutas, como con CMake/Vcpkg o Conan, o crear la biblioteca directamente
      Incluso si usas Crates.io, si no te gustan los cambios, basta con fijar versiones. Solo facilita obtener la versión más reciente
      En Rust es fácil crear software sobre software existente. Si no te gusta el software existente o su ritmo de cambio, no culpes a Cargo: hazlo de la forma que prefieras
    • El verdadero problema de que sea difícil agregar dependencias en C++ es que es difícil agregarlas de una forma en la que “todos puedan compilar sin problemas”. Aunque funcione en mi computadora, puede no funcionar en el entorno de usuarios o contribuidores potenciales, y eso se vuelve un problema
      Las distribuciones siguen compilando los paquetes de Rust desde el código fuente y vendorizan las dependencias de crates en el repositorio. Es más doloroso porque hay más dependencias y se actualizan con más frecuencia, pero eso es independiente de las bibliotecas compartidas
  • No es exacto decir que la API para averiguar el tamaño de la terminal ha sido estable durante 50 años. Hasta donde sé, TIOCGWINSZ ioctl nunca se estandarizó, y tiene varios nombres según Unix y BSD
    La función tcgetwinsize() recién entró en POSIX en 2024, y todo este tema tiene una historia bastante lamentable https://news.ycombinator.com/item?id=42039401. Es decir, esto ya pasa incluso antes de llegar al lado de Windows

    • La impresión que me dio el artículo es más bien que la implementación alternativa no es simple porque sea mejor, sino porque solo maneja su propio caso de uso
      A veces eso está bien, pero puede empeorar el software para personas con otros casos de uso o para quienes lo ejecutan en sistemas que el autor no entiende ni usa
      El verdadero valor de una biblioteca está en encargarse de que incluso problemas que parecen simples en realidad tienen mucha complejidad. Tampoco creo que 3 o 4 dependencias en una biblioteca que corre tanto en Windows como en Linux sean demasiadas. En el mejor de los casos, quien usa esa biblioteca tiene más experiencia en esa área y puede encargarse de partes desconocidas para mí
    • No se estandarizaron, pero esas llamadas no cambian. En particular, las llamadas de Windows están compiladas dentro de una enorme cantidad de binarios, así que la estabilidad ABI está garantizada
      Sin duda hay problemas con ioctl, pero los cambios que produjeron varias releases en terminal-size o sus dependencias no tienen que ver con las constantes ioctl/TIOCGWINSZ ni con la estructura winsize. Ese código no cambió
    • En este caso, el crate terminal-size simplemente llama a tcgetwinsize de Rustix, y Rustix a su vez llama a tcgetwinsize de libc. Así que, si haces lo mismo directamente, puedes reducir bastante las dependencias, y el costo sería básicamente el soporte para Windows
      Que esta API haya sido estable durante 50 o 25 años es un detalle. Esa dependencia ni siquiera pretende manejar esa complejidad, y tampoco es probable que esa función cambie o desaparezca en el futuro cercano
    • La terminal parece muy simple, pero es un buen ejemplo de algo que se volvió un gran dolor de cabeza porque al inicio había demasiados proveedores y no se consolidó un estándar de la industria. Ni siquiera queda claro si lo más cercano es VT100 o VT102
      La mayoría escribe directamente ahí, pero funciones como el tamaño de la terminal o el modo raw son un desastre y requieren cosas como ioctl. Honestamente, no es muy bueno
      Pero las bibliotecas son aún peores. Si no quieres enlazar con ncurses, que Dios te ayude
  • Hace poco reviví la primera webapp de mi startup, creada en 2006. Era un sitio de redes sociales centrado en compartir medios y, para los estándares de la época, era una pila LAMP bastante simple. Usaba PHP 5 y MySQL 3.2, pero tenía en general las funciones de redes sociales de ese momento.
    Quería probar por mi cuenta nuevas tecnologías de CI/CD, así que estoy creando un proceso de despliegue usando esta app como proyecto de aprendizaje, con más ingeniería de la necesaria. Podría haber usado WordPress o una app Hello World, pero esto es mucho más divertido.
    Casi todo el PHP lo escribí yo mismo. También hice mis propias bibliotecas para autenticación/autorización, plantillas, manejo de formularios, etc., y solo usé una biblioteca PEAR para el envío de correos. El frontend era HTML puro, casi sin JavaScript, y para reproducir medios usaba Flash. En 2006 la mayoría de las cosas se hacían así.
    Volver a ejecutar una app de 19 años me tomó apenas alrededor de una hora. Cambié el viejo driver mysql de PHP por mysqli y ajusté el esquema y algunas consultas para MySQL 8. Principalmente fue encerrar con backticks palabras que ahora son reservadas y corregir valores predeterminados de columnas que se volvieron más estrictos. Lo único que no funcionó fue Flash.
    En cambio, en mi trabajo actual operamos decenas de apps Spring Boot escritas en Java 8, y tenemos páginas y páginas de vulnerabilidades derivadas de decenas de dependencias. Si actualizas una, también tienes que subir una cadena de otras bibliotecas, y por las dependencias transitivas se vuelve una pesadilla. Por eso solo atendemos lo mínimo de las vulnerabilidades más críticas, y no hay un plan realista para actualizar todo.
    Lo gracioso es que lo que hacía la app PHP de 2006 y lo que hacen las apps Spring Boot actuales no es tan distinto. Al final todo es CRUD; solo que ahora tiene alrededor mucha más decoración y herramientas empresariales.

    • Al ver Go y el mundo Linux de la familia C, me convenció la filosofía de bibliotecas gruesas. Tomas una biblioteca que resuelve el problema de un área y le agregas lo que necesites.
      No arrastras una dependencia distinta por cada punto de una checklist. Puede haber trabajo duplicado, pero la ruta de actualización se vuelve mucho más fácil.
    • La mayoría de las nuevas tecnologías de CI/CD se volvieron estándar por la complejidad de mantener dependencias de terceros y entornos de ejecución que cambian todo el tiempo. En una vieja aplicación LAMP desplegada con scp, en general eso no es un problema.
    • Estoy de acuerdo en parte, pero no quiero pasar tiempo reinventando la rueda y metiéndole bugs.
      Por ejemplo, con formatos estándar de mensajes de comunicación como FHIR o HL7, nadie querría implementar por cuenta propia toda la compleja definición del estándar.
      Escribir tus propias funciones criptográficas también suele ser pegarte un tiro en el pie, y los problemas graves de seguridad descubiertos durante años lo han demostrado.
      Hoy estamos en una época en la que queremos enfocarnos en resolver problemas de negocio más que en cómo se construye correctamente una solución. Con la llegada de la IA, esto se vuelve aún más importante, porque todo el código se siente como si estuviera pegado a ciegas.
      Dedicar tiempo a construir todo uno mismo puede ser ventajoso a largo plazo, pero primero hay que sobrevivir a la competencia. Tal vez un competidor ya capturó el mercado al principio con código rápido y desechable.
    • En mi trabajo tenemos una política de análisis estático bastante estricta, y se volverá más estricta a partir de abril. Me pregunto si han visto https://docs.openrewrite.org/ para actualizar dependencias automáticamente.
      Hace poco migramos de Java 8, Spring Boot 2 y Swagger a Java 17, Spring Boot 3.3 y OpenAPI 3, y fue bastante painless.
      Todavía hay que subir algunas dependencias directas y transitivas, pero el mayor obstáculo se resolvió con la migración.
    • El punto importante aquí es que en Rust prácticamente no existe el problema de las dependencias transitivas. Si subes un crate y también suben sus dependencias públicas, y esas dependencias están expuestas en la API y tienes que interactuar con ellas, entonces obviamente debes actualizarlas también.
      Pero mientras no enlaces contra una biblioteca C, las dependencias transitivas privadas no importan en absoluto. En el mismo árbol de dependencias puedes tener tantas versiones de un crate incompatibles con SemVer como quieras, e incluso depender directamente de varias versiones si hace falta.
      No necesitas actualizaciones masivas al estilo Java; solo subes la dependencia vulnerable. Tengo entendido que C# tiene una funcionalidad parecida, aunque es algo más verbosa.
  • Estoy 100% de acuerdo. NodeJS tuvo un gran impacto en mi carrera, pero NPM me dejó un trauma mayor que una infancia desastrosa.
    Imagina a un programador nuevo que está haciendo una app para mostrársela a sus amigos. Quiere agregar una nueva dependencia, pero no es compatible con otra, así que decide actualizar todo. Al instante nada funciona y Babel empieza a gritar.
    Uno piensa que de alguna forma lo resolverá, pero pronto se encuentra con issues abiertos en Git donde literalmente ni siquiera funciona lo básico. Por ejemplo, en Expo hay un issue abierto en el que un proyecto nuevo básico de React Native no compila en Android.
    La mitad de las veces parece que a nadie le importa, y en esos casos la solución tampoco está en el ecosistema de Node, sino en algún lugar del ecosistema de Android. Es cinta adhesiva hasta el fondo. Aun así, si un proyecto de miles de millones de dólares puede distribuir una plantilla que no funciona, eso también da confianza para no sentir síndrome del impostor cuando la mitad de mi proyecto paralelo no anda.

    • Ese es un problema de Node, no de Rust. Las dependencias no necesitan “caerse bien” entre sí. Puedes tener todas las versiones al mismo tiempo y nada se rompe.
    • Que Node.js y npm se salieran de control fue una señal de alarma para mí hace unos 15 años.
      Empecé a ver el momento de incorporar la primera dependencia como un fracaso personal. Porque en ese instante, en vez de simplemente cargar JS común con una etiqueta script, de pronto hacía falta todo un sistema de empaquetado y organización solo para usar un package.json.
  • Fue algo que me sorprendió al pasar de Go al ecosistema de Rust. Un proyecto maduro en Go, por ejemplo el backend web de producción de alguna empresa, puede tener apenas 10 a 20 dependencias, incluyendo dependencias transitivas.
    Como dice el texto, incluso un proyecto pequeño en Rust tiene muchas probabilidades de terminar con muchas más, y si se hacen tareas asíncronas, casi está garantizado.
    No sé bien si esto se debe a la cultura o a las características del lenguaje. Por ejemplo, en Go las interfaces se satisfacen implícitamente, así que no hace falta traer algo para decir que se está implementando.

    • Una de las grandes razones es que Go tiene una biblioteca estándar excelente y muy completa, mientras que Rust realmente no.
      En Go hay cosas incluidas en el lenguaje o en la biblioteca estándar que en Rust requieren dependencias: green threads, canales, expresiones regulares, cliente HTTP, servidor HTTP, manejo de tiempo, flags de línea de comandos, logger, lectura/escritura de GIF animados, etc.
      No es que me encante el lenguaje Go en sí, pero en herramientas y biblioteca estándar va a la cabeza, y sin duda es lo mejor que he usado.
    • La diferencia entre Rust y Go está sobre todo en la biblioteca estándar. La biblioteca estándar de Rust es deliberadamente pequeña.
    • La enorme biblioteca estándar de Go ayuda mucho a reducir la cantidad de dependencias.
  • Estoy muy de acuerdo con la tesis. Permitir que las abstracciones de otras bibliotecas queden expuestas fuera de tu propia biblioteca tiene muchos costos ocultos. No digo que nunca deba hacerse, pero al decidirlo hay que sopesar bien los costos futuros.
    Si el paquete que envuelve algo cambia de diseño o de objetivo con frecuencia, aparece inestabilidad. Una funcionalidad que originalmente era la forma más simple de garantía en ingeniería de software desaparece o se fragmenta.
    También dificulta que participen en el ecosistema personas propietarias que quizá no sean contribuyentes open source de carrera, pero sí conozcan muy bien un área concreta. Si “hice una forma prolija de hacer X” termina convertido en semanas de discusión, negociación y política sobre que “X debe encajar debajo de Y porque a la gente tal vez le guste Z”, se desperdicia el tiempo de todos.
    Algo que aprendí en la vida es que lo más simple es lo que más perdura. Deberíamos elogiar más seguido los minilitos (miniliths) y los monolitos. Tampoco es un problema exclusivo de Rust; lo he visto en varios lenguajes. Muchas veces las comunidades open source empujan con fuerza paquetes de tamaño atómico, y a menudo me pregunto si no responde más a plantar bandera o transferir propiedad que al interés real de la comunidad de usuarios.

  • En 2025 llegué un poco de casualidad a la idea de que es más rápido pedirle a ChatGPT o Cursor que cree implementaciones sin dependencias de funciones comunes, y cada vez estoy más de acuerdo. Sobre todo después de sufrir el infierno de dependencias de una app grande en React.
    También hay que pensar en las dependencias de SaaS o servicios de terceros. Muchas de ellas son patrones comunes y problemas ya resueltos, así que un LLM puede replicarlas rápidamente.

    • Hoy es mucho más probable que antes empiece con una pequeña función utilitaria interna implementada por IA antes de agregar una biblioteca.
      Para problemas de alcance limitado funciona bastante bien, y puedo hacer que la IA produzca una implementación más completa y robusta que la que escribiría yo directamente.
      Si más adelante decido agregar una biblioteca, ya existe una encapsulación natural. Solo tengo que cambiar la implementación de mi función para que use la biblioteca, y no necesariamente tocar todos los lugares donde se usa. Cuando llega el momento, también es más fácil probar varias bibliotecas.
  • Irónicamente, es bastante interesante. Armin es el autor original de Flask, el framework web de Python. En una época similar también existía Bottle, una biblioteca muy parecida.
    Tenían casi las mismas funciones, pero Flask se volvió muy popular, mientras que yo siempre preferí Bottle. Como era un solo archivo y no tenía dependencias, era muy fácil copiarlo directamente a un proyecto.
    También era fácil modificarlo, y con el tiempo llegué a entenderlo por completo. Para servidor y websockets le agregaba Gevent, pero con ese enfoque se podían construir proyectos bastante pesados.
    Incluso hoy tengo un fuerte impulso de usar Bottle para proyectos web pequeños. Lo malo es que no siguió muchas de las prácticas modernas que Python adoptó durante años, así que ahora se ve un poco anticuado.

  • Para construir cosas por cuenta propia se necesita una capacidad de ingeniería competente. Si solo tienes ingenieros que siempre han tomado bibliotecas de ecosistemas como NPM o PyPI, les va a costar desarrollar por sí mismos soluciones para muchos problemas. Más aún si esas soluciones deben durar y tener la flexibilidad necesaria.
    “No programarte a ti mismo hasta un callejón sin salida” requiere mucha práctica.
    He visto muchas veces casos en los que es fácil hacerlo mejor que una biblioteca existente. En un proyecto implementé un parser para una variante de Markdown con metadatos al inicio del archivo. Usaba una gramática pequeña, y el parser quedó en menos de una pantalla de código.
    Pero la biblioteca de frontend que parseaba el mismo archivo era mucho peor de lo esperado. Se rompía si el identificador de metadatos tenía un guion, y resultó que estaba usando directamente el identificador de metadatos como nombre de miembro de un objeto. En vez de usar un objeto JSON simple, restringía artificialmente la elección de nombres y se rompía si había guiones, como en something-something.
    Al final descartaron mi parser, con el argumento de que la gente tendría que “mantenerlo”. Pero simplemente funcionaba, y podía adaptarse fácilmente a cambios en la gramática. Con saber un poco sobre parsers, no tenía nada difícil de entender.
    Aunque cueste creerlo, parece que en el equipo nadie salvo yo había escrito un parser con una biblioteca generadora de parsers. Y hay muchos otros ejemplos como este.

  • Si para usar una sola función terminas compilando cientos, debería prenderse una alarma. Hace aproximadamente un año trabajé en un proyecto para actualizar dependencias de terceros.
    Una de ellas era una biblioteca matemática muy completa, con todo tipo de funciones. Al investigar un poco, resultó que lo único que usábamos era un solo método para encontrar la mediana de una lista.
    Le señalé al ingeniero responsable la página de Wikipedia, eliminé la dependencia y le pedí que escribiera un único método que realizara esa operación matemática.
    Pero el verdadero problema no es usar dependencias de terceros en sí, sino que necesitamos el concepto de traer solo una parte acotada de una biblioteca. Si solo necesitas una pequeña porción de una biblioteca enorme, ¿por qué traerla completa? He oído propuestas de “microframeworks” como una forma de resolver esto.

    • En Rust, las bibliotecas pueden definir features que se activan o desactivan de forma condicional, así que existe una manera integrada de controlar qué se incluye realmente. Tokio es un buen ejemplo: quizá sorprenda saber que Tokio solo necesita dos dependencias directas obligatorias en total. Todo lo demás es opcional. https://github.com/tokio-rs/tokio/blob/ee19b0ed7371b069112b9...
      Lamentablemente, no parece que la gente revise con cuidado el conjunto de features predeterminadas que usan sus dependencias ni que las reduzca activamente. La sintaxis para traer features adicionales es simple, pero para quitar features opcionales activadas por defecto hay que especificar no-default-features y aun así volver a agregar una por una las features predeterminadas que sí se quieren, lo cual es más engorroso.
      Peor aún: para que una biblioteca permita recortar features innecesarias de sus propias dependencias, tiene que crear features propias y mapearlas a las features de cada dependencia. Por ejemplo, si tienes 5 dependencias y cada una tiene 1 dependencia obligatoria y 4 opcionales, para darle al usuario control total sobre las features transitivas tendrías que crear 20 features de mapeo en tu propia biblioteca. Y eso ni siquiera incluye las features que crearías para cuidar a los usuarios aguas abajo de tu propio código.
      Cada vez veo más que la usabilidad alrededor de las features, en especial para reducir el bloat innecesario, es tan mala que termina siendo un catalizador de los problemas de tiempos de compilación en Rust. No parece que este tema se discuta mucho cuando surge, así que quizá debería escribir una entrada de blog con mi postura fuerte para poder señalarla si el estado actual continúa.
    • Si haces eso, ocurren cosas como leftpad en el ecosistema JS.
      Si el sistema de confianza es bueno, por un lado no creo que sea un problema.
      También me gusta hasta cierto punto el enfoque tipo Shadcn: si te gusta un componente, lo copias a tu propia biblioteca. Pero si aparece una vulnerabilidad, no puedes saber si te afecta.
      Para cierto código está bien, pero en otros casos realmente debes depender de que lo revise la mayor cantidad de ojos posible.
    • Para quien no lo sepa, en pseudocódigo sería algo así:
      Median(list) { let len = length(list) if len % 2 == 0 { let x = floor(len/2) return (list[x] + list[x+1]) / 2 } return list[len/2] }
      Eso sí, asume que la lista ya está ordenada. Resistí la tentación de llamar a is_odd aquí.