35 puntos por GN⁺ 2024-12-23 | 4 comentarios | Compartir por WhatsApp
  • El software moderno se actualiza con frecuencia mediante despliegue continuo (CD) y pruebas automatizadas (CI), pero el "software pensado para usarse a largo plazo" requiere un enfoque distinto
    • Ejemplos: plantas nucleares, aviones, marcapasos, sistemas electorales, etc.
      • En ámbitos donde la confiabilidad y la estabilidad son críticas, se prefiere la estabilidad y los cambios predecibles por encima del cambio continuo

Principios clave del desarrollo de software a largo plazo

Dependencias

  • Las dependencias del software son un factor importante para el éxito a largo plazo
  • El software debe considerar su interacción con el mundo externo, y las decisiones fundamentales como el lenguaje de programación son importantes
  • Comprender la jerarquía de dependencias del software
    • Mundo externo: software cliente que no podemos controlar (por ejemplo, navegadores, etc.).
    • Decisiones fundamentales: elementos que solo pueden cambiarse reescribiendo toda la pila, como el lenguaje de programación.
    • Frameworks: como Spring Framework o React, fuertemente acoplados a la base de código. Se pueden cambiar, pero a un costo muy alto.
    • Base de datos: por lo general se puede reemplazar, pero requiere ajustes detallados y trabajo.
    • Bibliotecas auxiliares: bibliotecas reemplazables que ofrecen funciones específicas.
  • Con el tiempo, tanto las dependencias como el mundo externo cambian:
    • Los cambios en las dependencias pueden provocar modificaciones en el código o cambios de comportamiento.
    • La salida de nuevas versiones mayores puede causar problemas de compatibilidad.
    • Existe el riesgo de que un proyecto sea descontinuado o desaparezca.
    • Riesgos de seguridad: una dependencia puede verse comprometida por actores maliciosos (npm, PyPI, etc.).
    • Comercialización: nuevos propietarios respaldados por capital de riesgo (VC) pueden convertirlo en un servicio de pago.
    • Problemas de conflicto entre dependencias.
  • Aspectos a revisar al elegir dependencias pensando en el uso a largo plazo:
    • Nivel técnico: si es posible juzgar la calidad revisando el código fuente.
    • Base de usuarios: verificar quién lo está usando.
    • Objetivo de desarrollo: entender quién lo desarrolla y con qué propósito.
    • Apoyo financiero: si tiene financiamiento y de dónde proviene.
    • Mantenimiento: comprobar si hay lanzamientos de seguridad de forma periódica.
      • Si la comunidad podría asumir el mantenimiento.
      • Si yo mismo podría mantenerlo.
      • Si hace falta apoyo financiero para asegurar la sostenibilidad del proyecto.
    • Dependencias de las dependencias:
      • Revisar también el historial de seguridad de las dependencias transitivas.
  • Enfoque realista
    • Limitar dependencias:
      • Un proyecto con más de 1600 dependencias tiene muchas probabilidades de volverse inestable a medida que el código cambia rápidamente.
      • En proyectos con muchísimas dependencias, incluso es difícil saber qué código se está desplegando.
    • Agregar con cautela:
      • Al añadir una dependencia, asignarle una dificultad técnica para asegurar un tiempo natural de revisión.
      • En proyectos de largo plazo, conviene evitar dependencias innecesarias.

Dependencias de ejecución (Runtime Dependencies)

  • Lo discutido hasta ahora se limita a dependencias de build/compilación.
  • Sin embargo, los proyectos modernos suelen incluir también dependencias de ejecución:
    • Ejemplos: Amazon S3, Google Firebase.
    • Algunas se consideran casi estándares de facto (como S3).
    • Pero la mayoría de las dependencias de ejecución implican un fuerte lock-in con servicios específicos.
  • Dentro de 10 años, encontrar una alternativa que reemplace un servicio usado hoy puede implicar un costo muy alto.
  • Es necesario minimizar o vaciar la lista de dependencias de servicios de terceros:
    • En el desarrollo de software cloud native, en particular, es común usar muchos servicios avanzados de terceros.
    • En proyectos de largo plazo, estas dependencias conllevan un riesgo alto.
  • Las dependencias de servicios en tiempo de build también son un factor importante:
    • Por ejemplo, si npm install deja de funcionar, construir el software puede volverse imposible.
    • Eso puede reducir gravemente la reutilización del proyecto.
  • Revisar a fondo las dependencias de ejecución:
    • Reconocer los posibles problemas de lock-in y reducir o eliminar dependencias.
  • Asegurar la sostenibilidad a largo plazo:
    • Considerar de antemano la posibilidad de sustituir la nube o los servicios de terceros.

Pruebas, pruebas y más pruebas

  • La necesidad de hacer pruebas es un principio básico con el que todos están de acuerdo:
    • Escribir tantas pruebas como sea posible.
    • No todas las pruebas tienen el mismo valor, pero rara vez uno se arrepiente de haber escrito pruebas.
  • Las pruebas son esenciales sobre todo en proyectos con muchas dependencias:
    • Ayudan a detectar problemas temprano cuando las dependencias cambian o se desvían con el tiempo.
  • El papel de las pruebas
    • Ayudan a resolver problemas:
      • Permiten ajustarse rápidamente a los cambios.
    • Apoyan el refactoring:
      • Dan confianza al eliminar o cambiar dependencias del código.
    • Son útiles para el mantenimiento a largo plazo:
      • Incluso después de que el desarrollo haya estado detenido por más de 3 años, permiten confirmar que el sistema sigue funcionando.
      • También permiten verificar que la funcionalidad se mantenga con nuevos compiladores, runtimes y sistemas operativos.
  • Las pruebas no son un costo, sino una inversión
    • Escribe más pruebas:
      • Son la base del mantenimiento y la estabilidad.
      • Al modificar o ampliar el código, las pruebas brindan un gran respaldo mental.

Complejidad: el jefe final del desarrollo de software

  • La complejidad es el enemigo supremo del desarrollo de software:
    • Incluso los mejores desarrolladores o equipos pueden ser derrotados por la complejidad.
    • Por efecto de la entropía y del comportamiento humano, la complejidad siempre aumenta.
    • Si no se gestiona de forma consciente, el proyecto puede caer en un estado imposible de mantener.
  • Relación entre complejidad y cantidad de código
    • Cantidad de código y complejidad:
      • Cuando hay poco código, incluso si es algo complejo, sigue siendo manejable.
      • A medida que el código crece, hay que mantener la simplicidad para conservar el control.
      • La complejidad manejable debe permanecer dentro de la capacidad del equipo y dentro del "triángulo verde".
    • Límites de la complejidad:
      • Incluso aumentando el tamaño del equipo o contratando desarrolladores extraordinarios, hay un límite para lo que se puede manejar.
      • Si se supera ese límite, el proyecto entra en un estado imposible de mantener.
  • Por qué el código siempre se mueve "hacia la esquina superior derecha" (en el gráfico):
    • Más solicitudes de funcionalidades.
    • Intentos innecesarios de optimización.
    • Al corregir bugs, se agrega código nuevo en vez de reducir la complejidad existente.
  • El costo de un mal diseño de API:
    • Ejemplo: la función CreateFile en la mayoría de los casos no crea un archivo.
    • Esa clase de confusión aumenta la carga cognitiva adicional y la posibilidad de errores.
  • Estrategias para gestionar la complejidad
    • Refactoriza temprano y con frecuencia:
      • Elimina código innecesario e invierte tiempo en simplificar.
    • Invierte en pruebas:
      • Cuantas más pruebas haya, más fácil será reducir complejidad.
    • Importancia de gestionar la complejidad:
      • Si no se trabaja por adelantado para simplificar, los proyectos de largo plazo corren el riesgo de terminar en un estado de "mantenimiento imposible".

Escribe código aburrido y simple. Más simple todavía. Y más aburrido.

"Depurar es dos veces más difícil que escribir un programa. Así que, si al escribir código lo haces lo más inteligente posible, ¿cómo vas a depurarlo?" - Brian Kernighan

  • Escribe código súper aburrido y claro:
    • Conviene preferir código naive, pero intuitivamente comprensible.
    • "La optimización premium es la raíz de todos los males."
  • Optimiza solo cuando sea realmente necesario:
    • Si por ser demasiado simple llega a causar problemas, no es difícil agregar complejidad después.
    • Puede que ese momento nunca llegue.
  • Evita escribir código complejo:
    • Espera hasta que sea verdaderamente necesario.
    • Es muy poco probable que te arrepientas de haber escrito código simple.
  • El código de alto rendimiento o ciertas funcionalidades pueden funcionar solo en entornos específicos.
    • Ejemplos:
      • LMDB: PowerDNS pasó por muchas dificultades antes de usarlo de manera estable.
      • RapidJSON: biblioteca JSON acelerada con SIMD. Tiene gran rendimiento, pero condiciones de uso exigentes.
  • Incluso si tienes la confianza de pensar "puedo superar esta limitación":
    • Puede que este año sí puedas, pero dentro de 5 años tú o quien te suceda podría tener dificultades.
    • El mismo principio aplica a los lenguajes de programación complejos.
  • Conclusión:
    • Simplifica el código:
      • Hazlo realmente simple. Y luego más simple todavía.
    • Deja la optimización para después:
      • La complejidad puede añadirse cuando haga falta, pero si la introduces desde el principio, mantener el software se vuelve difícil.

Desarrollo de software basado en LinkedIn

  • Realidad vs. ideal
    • Enfoque ideal: al elegir dependencias, hace falta una evaluación y revisión exhaustivas (usando la lista de verificación propuesta arriba).
    • Enfoque realista: a veces se prueba una tecnología atractiva y, si funciona, se sigue usando tal cual.
  • Por qué resulta atractivo
    • Una tecnología recomendada por una figura conocida o un influencer en LinkedIn.
    • El "framework más nuevo" elogiado con entusiasmo en comunidades como Hacker News.
  • Las tecnologías de moda no tienen suficiente validación a largo plazo:
    • Puede que no sean adecuadas para proyectos de software que deban mantenerse por más de 10 años.
    • Es más probable que las tecnologías nuevas presenten problemas de estabilidad y mantenibilidad en sus primeras etapas.
  • Recomendaciones
    • Úsalas solo en áreas experimentales:
      • Prueba primero las nuevas tecnologías en proyectos pequeños o en áreas no críticas.
    • Considera el efecto Lindy:
      • La vida útil de una tecnología tiende a ser proporcional al tiempo que ya lleva en uso.
      • Cuanto más antigua sea una tecnología, más se puede esperar estabilidad a largo plazo.
  • Las tecnologías nuevas son atractivas, pero para proyectos a largo plazo suelen ser más adecuadas las tecnologías probadas y estables.

Logging, telemetría y rendimiento

  • Si el software no se actualiza ni se despliega de forma continua:
    • Es muy posible no recibir retroalimentación inmediata cuando un sitio web se rompe.
    • Puede pasar mucho tiempo desde el despliegue hasta la resolución del problema real.
  • Implementa logging y telemetría exhaustivos desde la primera versión:
    • Registra el rendimiento, los fallos y la actividad del software.
    • Con el tiempo, los datos acumulados resultan muy útiles para resolver bugs poco frecuentes.
  • Problemas por falta de logging:
    • Se desplegó una interfaz de usuario y un usuario que había creado 3000 carpetas reportó un problema.
    • El usuario solo dijo "no funciona", y tomó meses identificar la causa raíz.
    • Con logging de rendimiento y telemetría, el problema se habría resuelto mucho más rápido.
  • El logging y la telemetría son esenciales:
    • Diseña el software para poder monitorear a fondo su actividad.
    • En despliegues y mantenimiento de largo plazo, ayudan enormemente a resolver problemas inesperados.

Documentación

  • Importancia de la documentación:
    • No se trata solo de escribir buena documentación de API, sino de explicar "por qué se diseñó así".
    • Registrar las ideas y la filosofía de cómo funciona el sistema.
    • Hay que dejar constancia de por qué se separó una solución y cuál es la base de decisiones de diseño no intuitivas.
  • Materiales útiles además de la documentación de arquitectura:
    • Publicaciones internas de blog: donde los desarrolladores comparten discusiones libres sobre el diseño del sistema.
    • Entrevistas al equipo: registros de conversaciones sobre el contexto de las decisiones de diseño.
    • Este tipo de documentos permite transferir conocimiento dentro del equipo incluso con el paso del tiempo.
  • Deja comentarios en el código:
    • A pesar de la moda de decir que "el buen código no necesita comentarios", los comentarios que explican el 'por qué' del código son indispensables.
    • Es importante explicar por qué existe una función en particular.
  • Escritura de mensajes de commit:
    • Los mensajes de commit son el núcleo del historial de trabajo. Ayudan a rastrear por qué se hicieron cambios en el código.
    • Conviene ofrecer un entorno donde los usuarios puedan consultar fácilmente esos mensajes.
  • Reservar tiempo para documentar:
    • En los días en que el desarrollo no avanza bien, dedica tiempo a dejar comentarios y registros útiles.
    • A nivel de equipo, asigna tiempo regularmente para la documentación.
  • Registra por qué se diseñó así:
    • Dentro de 7 años, cualquier material que pueda transmitir la filosofía y el contexto a un equipo nuevo será valiosísimo.
  • Deja historia a través de comentarios y mensajes de commit:
    • Son elementos esenciales no solo durante el desarrollo, sino también para el mantenimiento a largo plazo.

Composición del equipo

  • La continuidad del equipo y el éxito a largo plazo del software:
    • Algunos programas se diseñan para tener soporte durante 80 años. En este tipo de proyectos de largo plazo, mantener al equipo es clave.
    • En el entorno moderno de desarrollo, un promedio de 3 años ya se considera una permanencia larga.
    • La buena documentación y las pruebas pueden compensar hasta cierto punto la rotación del equipo, pero tienen límites.
  • Ventajas de la permanencia a largo plazo:
    • Mantener integrantes del equipo por más de 10 años:
      • Es importante contratarlos como personal real y gestionar bien a los desarrolladores.
      • Se considera un "hack" clave para el éxito de proyectos a largo plazo.
  • Problemas de depender de terceros externos:
    • Los desarrolladores subcontratados suelen irse después de entregar el código al sistema.
    • Es una forma muy ineficiente de trabajar si el objetivo es un software sostenible durante más de 10 años.
  • Crear un entorno donde los miembros del equipo puedan permanecer juntos a largo plazo.
  • Hace falta una estrategia para minimizar la dependencia de consultores externos y aumentar la sostenibilidad del equipo interno.

Considera el open source

  • Ventajas del open source:
    • Mantener la calidad del código mediante revisión externa:
      • La mirada externa exige estándares más altos a los desarrolladores.
    • Es un mecanismo poderoso para sostener mejores estándares de código.
  • La realidad al prepararse para abrir el código:
    • Las empresas o gobiernos suelen afirmar que prepararse para liberar como open source toma de varios meses a varios años.
    • Razones:
      • Internamente es común escribir código que da vergüenza mostrar hacia afuera.
      • Antes de abrirlo, hace falta ordenar y limpiar el código.
  • Evaluar la aplicabilidad:
    • El open source no siempre es una opción posible.
    • Si es posible, es una buena forma de elevar la calidad del código y la transparencia.
  • El open source es una estrategia importante que conviene aprovechar cuando se pueda.
  • La mirada externa y los estándares altos ayudan a mantener el proyecto en la dirección correcta.

Revisión del estado de salud de las dependencias

  • El problema de los cambios en las dependencias:
    • Las dependencias pueden cambiar o desviarse con el tiempo de maneras distintas a lo esperado.
    • Si eso se deja pasar:
      • aparecen bugs
      • fallas de build
      • y otros resultados decepcionantes.
  • Se recomienda una revisión de salud periódica:
    • Revisiones periódicas de dependencias:
      • Dan la oportunidad de detectar problemas con anticipación.
      • También permiten descubrir nuevas funciones de las dependencias que podrían simplificar el código o eliminar otras dependencias.
    • Importancia del mantenimiento preventivo:
      • Si no planificas tú mismo tiempo para revisar, al final tendrás que dedicarlo a la fuerza cuando aparezcan problemas.
  • Analogía de mantenimiento:
    • Un dicho de los mecánicos:
      • "Planifica tú mismo el tiempo de mantenimiento. Si no, el equipo lo planificará por ti."
  • La revisión periódica de dependencias es una actividad esencial para la estabilidad y eficiencia del software a largo plazo.
  • Sirve para resolver problemas con anticipación y también para descubrir cambios positivos.

Libros de referencia principales

Para terminar

Recomendaciones clave para el desarrollo de software a largo plazo:

  • Mantén la simplicidad:
    • ¡Hazlo simple, y luego más simple todavía! Como la complejidad puede añadirse cuando haga falta, no lo vuelvas excesivamente complejo desde el inicio.
    • Para conservar la simplicidad, hacen falta refactorizaciones periódicas y eliminación de código.
  • Piensa con cuidado en las dependencias:
    • Cuantas menos dependencias, mejor. Revísalas y audítalas con atención.
    • Si no puedes auditar 1600 dependencias, necesitas replantear el plan.
    • Evita decisiones guiadas por tendencias o modas (por ejemplo, desarrollo basado en LinkedIn).
    • Revisión periódica de dependencias: monitorea continuamente su estado.
  • Pruebas, pruebas y más pruebas:
    • Permiten detectar a tiempo dependencias cambiantes.
    • Dan confianza al refactorizar y ayudan a mantener la simplicidad.
  • Documentación:
    • Documenta no solo el código, sino también la filosofía, las ideas y el contexto de por qué se hizo así.
    • Será un recurso valiosísimo para futuros integrantes del equipo.
  • Mantén un equipo estable:
    • Para inversiones en proyectos de largo plazo, considera contratación de largo plazo.
    • Apoya a los integrantes del equipo para que puedan comprometerse con el proyecto durante muchos años.
  • Considera el open source:
    • Si es posible, úsalo para mantener estándares de código más altos.
  • Logs y telemetría de rendimiento:
    • Cumplen un papel importante para detectar y resolver problemas temprano.
  • Puede que estas recomendaciones no sean nuevas, pero vale la pena reflexionar sobre ellas a fondo, especialmente porque desarrolladores con mucha experiencia siguen enfatizándolas.

4 comentarios

 
kandk 2024-12-30

La capacidad de ingeniería más importante es separar la capa donde la estabilidad es crucial de la capa donde la velocidad es crucial, y definir cómo manejar la relación entre ambas.
Si Toss hubiera buscado solo la estabilidad, no sería diferente de otros bancos.

 
kandk 2024-12-30

Lo peligroso pasa también con SpaceX. Lo mismo con Tesla...

 
aer0700 2024-12-25

¿Será que el desarrollo guiado por el currículum es el problema?

 
GN⁺ 2024-12-23
Opinión de Hacker News
  • Actualizar activamente la cadena de herramientas es una parte importante del proceso de desarrollo. Muchas empresas dejan fuera de sus prioridades las actualizaciones de la toolchain, lo que provoca problemas como vulnerabilidades de seguridad. Se crea una rama con cada nueva versión del compilador o del sistema de build para verificar el estado de compilación y, si hay errores, se tratan como bugs y se corrigen de inmediato. Esto ayuda a modernizar y refactorizar gradualmente la base de código con las funciones más recientes del lenguaje.

  • Las dependencias de terceros suelen ser decepcionantes a largo plazo. En proyectos nuevos, usar dependencias de terceros puede resolver problemas en el corto plazo, pero a largo plazo es mejor reemplazarlas con código propio.

  • Es necesario hacer vendoring de las dependencias y gestionarlas mediante code review. A menudo, la calidad del código de terceros es baja y en muchos casos es mejor escribirlo uno mismo.

  • Se está trabajando en un proyecto que busca escalabilidad a largo plazo usando Qt, CMake y C++ moderno. Este stack tecnológico sigue ofreciendo funciones y mejoras de forma continua.

  • Trabajar en Emacs Lisp fue una experiencia refrescante. Una ventaja es que funciona de forma estable incluso si las librerías no se actualizan. La experiencia con Gatsby y Node fue difícil debido a los problemas provocados por las actualizaciones.

  • Es importante escribir código simple. El código complejo debe escribirse solo cuando sea necesario, y uno no se arrepiente del código simple.

  • La documentación de los sistemas y del código es importante. Cuanta más experiencia se tiene en desarrollo de software, más clara se vuelve la importancia de documentar.

  • Las pruebas cumplen un papel importante en la planificación. Tomando como referencia la forma de desarrollo de la NASA, hay que centrarse en encontrar errores de programación. En el desarrollo de software médico se evita la interpretación y no se usa asignación dinámica de memoria.

  • La mejor manera de escribir software que dure mucho tiempo es escribir código "aburrido". Hay que evitar dependencias y mantenerse fiel a lo básico.

  • Existe experiencia de haber tenido dificultades con problemas de dependencias en Python. A esto se le llama "DLL Hell", y COM intentó resolverlo, pero no tuvo éxito.

  • Las prácticas aplicadas al software industrial no son lo suficientemente robustas como para aplicarse al software general. Los ingenieros intentan mitigar riesgos, pero nosotros nos enfocamos en mitigar el riesgo.