Erhalten Sie Zugang zu diesem und mehr als 300000 Büchern ab EUR 5,99 monatlich.
Hoy en día, los ingenieros de software necesitan saber no solo cómo programar eficazmente, sino también cómo desarrollar prácticas de ingeniería para que la base de código sea sostenible y funcione bien. Este libro hace hincapié en esta diferencia, entre la programación y la ingeniería de software. ¿Cómo pueden gestionar los ingenieros de software una base de código viva que evoluciona y responde a requisitos y demandas cambiantes a lo largo de su vida? A partir de su experiencia en Google, los ingenieros de software Titus Winters y Hyrum Wright, junto con el escritor técnico Tom Manshreck, presentan una mirada sincera y perspicaz sobre cómo construyen y mantienen el software algunos de los principales profesionales del mundo. Este libro trata de la cultura, los procesos y las herramientas de ingeniería exclusivas de Google, y de cómo estos aspectos contribuyen a la eficacia de una organización de ingeniería de software. Explorará tres principios fundamentales que las organizaciones de software deben tener en cuenta a la hora de diseñar, establecer la arquitectura, escribir y mantener el código: - Cómo afecta el tiempo a la sostenibilidad del software y cómo hacer que su código resista el paso del tiempo. - Cómo afecta la escala a la viabilidad de las prácticas de software dentro de una organización de ingeniería de software. - Qué contrapartidas debe tener en cuenta el ingeniero de software al evaluar las decisiones de diseño y los desarrollos.
Sie lesen das E-Book in den Legimi-Apps auf:
Seitenzahl: 1261
Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:
Ingeniería de softwareen Google
Lecciones sobre programación aprendidasa lo largo del tiempo
Organizadas por Titus Winters,Tom Manshreck y Hyrum Wright
Ingeniería de software en Google
Lecciones sobre programación aprendidasa lo largo del tiempo
Organizadas por Titus Winters,Tom Manshreck y Hyrum Wright
Edición original publicada en inglés por O’Reilly con el título Software Engineering at Google, ISBN 978-1-492-08279-8 © 2020 Google, LLC.
This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same.
Título de la edición en español:
Ingeniería de software en Google
Primera edición en español, 2022
© 2022 MARCOMBO, S.L.
www.marcombo.com
Diseño de portada: Karen Montgomery
Ilustración: Rebecca Demarest
Traducción: Francisco Martínez Carreño
Corrección: Mónica Muñoz Marinero
Cualquier forma de reproducción, distribución, comunicación pública o transformación de esta obra solo puede ser realizada con la autorización de sus titulares, salvo excepción prevista por la ley. La presente publicación contiene la opinión del autor y tiene el objetivo de informar de forma precisa y concisa. La elaboración del contenido, aunque se ha trabajado de forma escrupulosa, no puede comportar una responsabilidad específica para el autor ni el editor de los posibles errores o imprecisiones que pudiera contener la presente obra.
ISBN: 978-84-267-3487-7
Producción del ePub: booqlab
Prólogo
Prefacio
Parte I. Tesis
1. ¿Qué es la ingeniería de software?
Tiempo y cambio
Ley de Hyrum
Ejemplo: ordenación hash
¿Por qué no aspirar a que «nada cambie»?
Escala y eficiencia
Políticas que no escalan
Políticas que escalan adecuadamente
Ejemplo: actualización del compilador
Desplazamiento hacia la izquierda
Contrapartidas y costes
Ejemplo: rotuladores
Aportaciones a la toma de decisiones
Ejemplo: compilaciones distribuidas
Ejemplo: decidir entre tiempo y escala
Revisar decisiones, cometer errores
Ingeniería de software frente a programación
Conclusión
Resumen
Parte II. Cultura
2. Cómo trabajar bien en equipo
Ayúdeme a ocultar mi código
El mito del genio
La ocultación se considera perjudicial
Detección temprana
El factor autobús
Ritmo del progreso
En resumen, no se esconda
Todo es cuestión de equipo
Los tres pilares de la interacción social
¿Por qué importan estos pilares?
Humildad, respeto y confianza en la práctica
Cultura post mortem sin sentimiento de culpa
Ser Googley
Conclusión
Resumen
3. Compartir conocimientos
Desafíos para el aprendizaje
Filosofía
Preparación del escenario: seguridad psicológica
Tutoría
Seguridad psicológica en grupos grandes
Aumente sus conocimientos
Haga preguntas
Comprenda el contexto
Escalado de las preguntas: pregunte a la comunidad
Chats de grupo
Listas de correo electrónico
YAQS: plataforma de preguntas y respuestas
Escalado del conocimiento: siempre hay algo que enseñar
Horas de oficina
Charlas y clases de tecnología
Documentación
Código
Escalado de los conocimientos de la organización
Cultivar la cultura de compartir el conocimiento
Establecimiento de fuentes canónicas de información
Manténgase al día
Legibilidad: tutorías estandarizadas a través de la revisión del código
¿Qué es el proceso de legibilidad?
¿Por qué someterse a este proceso?
Conclusión
Resumen
4. Ingeniería para la equidad
Los prejuicios son la norma
Comprensión de la necesidad de la diversidad
Desarrollo de capacidades multiculturales
Hacer que se pueda procesar la diversidad
Rechazo de enfoques singulares
Desafío a los procesos establecidos
Valores frente a resultados
Mantener la curiosidad, seguir adelante
Conclusión
Resumen
5. Cómo liderar un equipo
Gerentes y líderes en tecnología (y ambos)
El gerente de ingeniería
El líder en tecnología
El gerente líder de tecnología
Pasar de la función de colaborador individual a la función de liderazgo
Lo único que hay que temer es…, bueno, todo
Liderazgo de servicio
El gerente de ingeniería
«Gerente» es una palabra de cuatro letras
El gerente de ingeniería en la actualidad
Antipatrones
Antipatrón: contratar a personas fáciles de manejar
Antipatrón: ignorar a las personas de bajo rendimiento
Antipatrón: ignorar los problemas de carácter personal
Antipatrón: ser amigo de todos
Antipatrón: comprometer el listón de contratación
Antipatrón: tratar al equipo como si fueran niños
Patrones positivos
Perder el ego
Ser un maestro zen
Ser catalizador
Eliminar obstáculos
Ser maestro y mentor
Establecer metas claras
Ser honesto
Rastrear la satisfacción
La pregunta inesperada
Otros consejos y trucos
Las personas somos como las plantas
Motivación intrínseca frente a motivación extrínseca
Conclusión
Resumen
6. Liderazgo a escala
Siempre hay que decidir
La parábola del aeroplano
Identificación de las orejeras
Señalar las contrapartidas clave
Decidir y, después, repetir
Siempre hay que dejar solo al equipo
Su misión: formar a un equipo «autónomo»
División del espacio del problema
Siempre hay que mantenerse escalando
El ciclo del éxito
Lo importante frente a lo urgente
Aprender a dejar caer pelotas al suelo
Proteja su energía
Conclusión
Resumen
7. Medición de la productividad de la ingeniería
¿Por qué debemos medir la productividad de la ingeniería?
Triaje: ¿vale la pena medirlo?
Selección de métricas significativas con objetivos y señales
Objetivos
Señales
Métricas
Uso de datos para validar métricas
Actuar y realizar un seguimiento de los resultados
Conclusión
Resumen
Parte III. Procesos
8. Guías de estilo y normas
¿Por qué tenemos normas?
Creación de normas
Principios rectores
Guía de estilo
Cambio de las normas
El proceso
Árbitros de estilo
Excepciones
Orientación
Aplicación de las normas
Comprobadores de errores
Formateadores de código
Conclusión
Resumen
9. Revisión del código
Flujo de revisión del código
Cómo funciona la revisión de código en Google
Beneficios de la revisión de código
Corrección de código
Comprensión de código
Coherencia del código
Beneficios psicológicos y culturales
Compartir conocimientos
Mejores prácticas de la revisión de código
Sea cortés y profesional
Escriba cambios pequeños
Escriba descripciones de los cambios que tengan calidad
Mantenga al mínimo el número de revisores
Automatizar donde sea posible
Tipos de revisiones de código
Revisiones del código greenfield
Cambios de comportamiento, mejoras y optimizaciones
Corrección de errores y reversiones
Refactorizaciones y cambios a gran escala
Conclusión
Resumen
10. Documentación
¿Qué calificar como «documentación»?
¿Por qué es necesaria la documentación?
La documentación es como el código
Conozca a su audiencia
Tipos de audiencias
Tipos de documentación
Documentación de referencia
Documentos de diseño
Tutoriales
Documentación conceptual
Páginas de destino
Revisiones de la documentación
Filosofía de la documentación
QUIÉN, QUÉ, CUÁNDO, DÓNDE y POR QUÉ
El principio, la parte central y el final
Parámetros de la documentación de calidad
Documentación obsoleta
¿Cuándo necesita a escritores técnicos?
Conclusión
Resumen
11. Descripción general de las pruebas
¿Por qué escribimos pruebas?
La historia de Google Web Server
Pruebas al ritmo del desarrollo moderno
Escribir, ejecutar, reaccionar
Ventajas de probar el código
Diseño de un conjunto de pruebas
Tamaño de las pruebas
Alcance de las pruebas
Regla de Beyoncé
Nota sobre la cobertura del código
Pruebas a escala de Google
Dificultades de un gran conjunto de pruebas
Historial de pruebas en Google
Clases de orientación
Pruebas certificadas
Pruebas en los aseos
La cultura de pruebas actualmente
Límites de las pruebas automatizadas
Conclusión
Resumen
12. Pruebas unitarias
Importancia del mantenimiento
Prevención de pruebas frágiles
Esfuércese en lograr pruebas que no cambien
Pruebas a través de API públicas
Comprobar el estado, no las interacciones
Escriba pruebas claras
Haga que las pruebas sean completas y concisas
Pruebe los comportamientos, no los métodos
No ponga la lógica en las pruebas
Escriba mensajes de error claros
Pruebas y uso compartido de código: DAMP, no DRY
Valores compartidos
Configuración compartida
Helpers compartidos y validación
Definición de infraestructura de pruebas
Conclusión
Resumen
13. Dobles de pruebas
El impacto de los dobles de pruebas en el desarrollo del software
Dobles de pruebas en Google
Conceptos básicos
Un ejemplo de dobles de pruebas
Costuras
Marcos de trabajo de simulación
Técnicas para utilizar los dobles de pruebas
Simulación
Stubbing
Pruebas de interacción
Implementaciones reales
Preferencia de las implementaciones reales a las aisladas
Cómo decidir cuándo utilizar una implementación real
Simulación
¿Por qué son importantes las simulaciones?
¿Cuándo deben escribirse las simulaciones?
Fidelidad de las simulaciones
Las simulaciones se deben probar
¿Qué hacer si no hay una simulación disponible?
Stubbing
Los peligros de abusar del stubbing
¿Cuándo es adecuado utilizar stubbing?
Pruebas de interacción
Preferencia de las pruebas de estado a las pruebas de interacción
¿Cuándo son apropiadas las pruebas de interacción?
Mejores prácticas para las pruebas de interacción
Conclusión
Resumen
14. Pruebas más grandes
¿Qué son las «pruebas más grandes»?
Fidelidad
Brechas frecuentes en las pruebas unitarias
¿Por qué no realizar pruebas más grandes?
Pruebas de gran tamaño en Google
Pruebas más grandes y el tiempo
Pruebas más grandes a escala de Google
Estructura de una prueba grande
El sistema bajo prueba
Datos para la prueba
Verificación
Tipos de pruebas más grandes
Prueba funcional de uno o más binarios interactivos
Pruebas de los navegadores y dispositivos
Pruebas de rendimiento, carga y estrés
Pruebas de la configuración de implementación
Pruebas exploratorias
Pruebas de regresión de diferencias A/B
UAT
Sistemas de sondeo y análisis canario
Recuperación en caso de catástrofe e ingeniería del caos
Evaluación al usuario
Grandes pruebas y el flujo de trabajo del desarrollador
Creación de pruebas grandes
Ejecución de pruebas grandes
Propiedad de las pruebas grandes
Conclusión
Resumen
15. Depreciación
¿Por qué hacer depreciación?
¿Por qué es tan difícil la depreciación?
Depreciación durante el diseño
Tipos de depreciación
Depreciación recomendada
Depreciación obligatoria
Advertencias de depreciación
Gestión del proceso de depreciación
Propietarios de los procesos
Hitos
Depreciación de las herramientas
Conclusión
Resumen
Parte IV. Herramientas
16. Control de versiones y gestión de ramas
¿Qué es el «control de versiones»?
¿Por qué es importante el control de versiones?
VCS centralizado frente a VCS distribuido
Fuente de verdad
Control de versiones frente a gestión de dependencias
Gestión de ramas
El trabajo en curso es similar a una rama
Ramas de desarrollo
Ramas de versión
Control de versiones en Google
One-Version
Escenario: varias versiones disponibles
La norma «One-Version»
Casi sin ramas longevas
¿Qué pasa con las ramas de versión?
Monorepos
Futuro del control de versiones
Conclusión
Resumen
17. Code Search
La interfaz de usuario de Code Search
¿Cómo utilizan los Googlers Code Search?
¿Dónde?
¿Qué?
¿Cómo?
¿Por qué?
¿Quién y cuándo?
¿Por qué una herramienta web independiente?
Escala
Vista global del código sin necesidad de configuración
Especialización
Integración con otras herramientas para desarrolladores
Presentacición de las API
Impacto de la escala en el diseño
Latencia de la consulta de búsqueda
Latencia del índice
Implementación de Google
Índice de búsqueda
Clasificación
Selección de contrapartidas
Completitud: repositorio en head
Completitud: todos los resultados frente a los más relevantes
Completitud: head versus ramas versus toda la historia versus espacios de trabajo
Expresividad: token frente a subcadena frente a expresión regular
Conclusión
Resumen
18. Sistemas de compilación y filosofía de la compilación
Propósito de un sistema de compilación
¿Qué sucede si no existe un sistema de compilación?
Pero todo lo que necesito ¡es un compilador!
¿Scripts de shell al rescate?
Sistemas de compilación modernos
Todo se trata de dependencias
Sistemas de compilación basados en tareas
Sistemas de compilación basados en artefactos
Compilaciones distribuidas
Tiempo, escala, contrapartidas
Tratamiento de los módulos y las dependencias
La utilización de módulos detallados y la regla 1:1:1
Minimización de la visibilidad de los módulos
Gestión de dependencias
Conclusión
Resumen
19. Critique, herramienta de revisión del código de Google
Principios de las herramientas de revisión del código
Flujo de revisión de código
Notificaciones
Nivel 1: realización de un cambio
Diferenciaciones
Resultados del análisis
Integración estricta de herramientas
Nivel 2: revisión de la solicitud
Niveles 3 y 4: comprensión y comentarios del cambio
Comentarios
Comprensión del estado de un cambio
Nivel 5: aprobación del cambio (puntuación del cambio)
Nivel 6: confirmación del cambio
Después de la confirmación: seguimiento del historial
Conclusión
Resumen
20. Análisis estático
Características del análisis estático eficaz
Escalabilidad
Usabilidad
Lecciones clave para hacer que el análisis estático funcione
Céntrese en la satisfacción de los desarrolladores
Haga que el análisis estático forme parte del flujo principal de trabajo del desarrollador
Permita la contribución de los usuarios
Tricorder: plataforma de análisis estático de Google
Herramientas integradas
Canales de retroalimentación integrados
Sugerencias de correcciones
Personalización por proyectos
Presubmits
Integración en el compilador
Análisis durante la edición y navegación por el código
Conclusión
Resumen
21. Gestión de dependencias
¿Por qué resulta tan difícil la gestión de dependencias?
Requisitos conflictivos y dependencias de diamante
Importación de dependencias
Promesas de compatibilidad
Consideraciones al hacer la importación
Cómo gestiona Google la importación de dependencias
Gestión de dependencias, en teoría
Nada cambia (también conocido como el «modelo de dependencias estáticas»)
Versionado semántico
Modelos de distribución por paquetes
Live at Head
Limitaciones de SemVer
SemVer puede que asegure una compatibilidad superior a la real
SemVer podría exagerar
Motivaciones
Minimum Version Selection
Entonces, ¿funciona SemVer?
Gestión de dependencias con recursos infinitos
Exportación de dependencias
Conclusión
Resumen
22. Cambios a gran escala
¿Qué es un «cambio a gran escala»?
¿Quién negocia con las LSC?
Obstáculos a los cambios atómicos
Limitaciones técnicas
Fusión de conflictos
Sin cementerios encantados
Heterogeneidad
Pruebas
Revisión de código
Infraestructura LSC
Políticas y cultura
Comprensión de la base de código
Gestión de cambios
Pruebas
Soporte del lenguaje
El proceso LSC
Autorización
Creación de cambios
Fragmentación y envío
Limpieza
Conclusión
Resumen
23. Integración continua
Conceptos de IC
Bucles de retroalimentación rápida
Automatización
Prueba continua
Desafíos de la IC
Pruebas herméticas
Integración continua en Google
Caso de estudio de IC: Google Takeout
Pero no puedo permitirme IC
Conclusión
Resumen
24. Entrega continua
Modismos de entrega continua en Google
La rapidez es un deporte de equipo: cómo dividir una implementación en partes manejables
Evaluación de cambios en el aislamiento: funciones de protección de banderas
La lucha por conseguir agilidad: configuración de un tren de lanzamiento
Ningún binario es perfecto
Cumpla con su fecha límite de lanzamiento
Calidad y enfoque en el usuario: envíe solo lo que se utilice
Desplazamiento a la izquierda: tomar antes decisiones basadas en los datos
Cambio de la cultura del equipo: creación de disciplina en el despliegue
Conclusión
Resumen
25. La computación como servicio
Dominio del entorno informático
Automatización del trabajo
Contenerización y tenencia múltiple
Resumen
Escritura de software para la computación gestionada
Arquitectura para el fracaso
Trabajos por lotes frente a trabajos de servicio
Gestión del estado
Conexión a un servicio
Código único
CaaS con el tiempo y la escala
Los contenedores como abstracción
Un servicio para gobernarlos a todos
Configuración enviada
Elección de un servicio informático
Centralización frente a personalización
Nivel de abstracción: sin servidores
Público versus privado
Conclusión
Resumen
Parte V. Conclusión
Epílogo
Índice
Siempre me han fascinado infinitamente los detalles de cómo hace Google las cosas.
He preguntado a mis amigos de Google para conseguir información sobre cómo funcionan realmente las cosas dentro de la empresa.
¿Cómo gestionan el depósito del código monolítico y masivo sin caerse?
¿Cómo colaboran con éxito decenas de miles de ingenieros en miles de proyectos?
¿Cómo mantienen la calidad de sus sistemas?
Trabajar con antiguos empleados de Google solo ha aumentado mi curiosidad. Si alguna vez ha trabajado con un exingeniero de Google (o «Xoogler», como se lo llama a veces), sin duda habrá escuchado la frase «en Google nosotros…». Salir de Google a otras empresas parece ser una experiencia impactante, al menos desde el lado de la ingeniería. Por lo que este forastero puede decir, los sistemas y procesos para escribir código en Google deben estar entre los mejores del mundo, dada la escala de la empresa y la frecuencia con la que la gente canta sus alabanzas.
En la ingeniería de software en Google, un conjunto de Googlers (y algunos Xooglers) nos brindan un plan extenso para muchas de las prácticas, herramientas e incluso elementos culturales que subyacen en la ingeniería de software en Google. Es fácil centrarse en las increíbles herramientas que Google ha creado para ayudar a escribir código, y este libro ofrece muchos detalles sobre esas herramientas. Pero también va más allá de la simple descripción de las herramientas, para ofrecernos la filosofía y los procesos que siguen los equipos de Google. Estos se pueden adaptar a una variedad de circunstancias, tenga o no la escala y las herramientas. Para mi deleite, hay varios capítulos en los que se profundiza en varios aspectos de las pruebas automatizadas, un tema que continúa encontrando demasiada resistencia en nuestra industria.
Lo mejor de la tecnología es que nunca hay una sola forma de hacer algo. En cambio, existen una serie de contrapartidas que todos debemos tener en cuenta, dependiendo de las circunstancias de nuestro equipo y la situación. ¿Qué podemos obtener de forma económica del código abierto? ¿Qué puede construir nuestro equipo? ¿Qué tiene sentido apoyar para nuestra escala? Cuando interrogaba a mis amigos Googlers, quería que me hablaran del mundo en el extremo de la escala: rico en recursos, tanto en talento como en dinero, con grandes exigencias en el software que se crea.
Esta información anecdótica me dio ideas sobre algunas opciones que, de otro modo, no habría considerado.
Con este libro, hemos escrito esas opciones para que todos las lean. Por supuesto, Google es una empresa única y sería una tontería suponer que la forma correcta de administrar su organización de ingeniería de software es copiar con precisión su fórmula. Aplicado de manera práctica, este libro le dará ideas sobre cómo se pueden hacer las cosas y mucha información que puede utilizar para reforzar sus argumentos para adoptar las mejores prácticas como las pruebas, la compartición de conocimientos y la creación de equipos colaborativos.
Es posible que nunca tenga usted que construir algo parecido a Google, y que ni siquiera desee recurrir en su organización a las mismas técnicas que ellos aplican. Pero, si no está familiarizado con las prácticas que ha desarrollado Google, se está perdiendo una perspectiva sobre la ingeniería de software que proviene de decenas de miles de ingenieros que han trabajado en colaboración en el software durante más de dos décadas. Ese conocimiento es demasiado valioso para ignorarlo.
—Camille Fournier
Autora, The Manager’s Path
Este libro se titula Ingeniería de software en Google. ¿Qué entendemos exactamente por «ingeniería de software»? ¿Qué distingue a la «ingeniería de software» de la «programación» o la «informática»? ¿Y por qué Google tendría una perspectiva única que añadir al corpus de bibliografía previo sobre ingeniería de software escrita durante los últimos cincuenta años?
Las expresiones «programación» e «ingeniería de software» se han utilizado de manera intercambiable durante bastante tiempo en nuestra industria, aunque cada término tiene un énfasis diferente y diversas implicaciones. Los estudiantes universitarios tienden a estudiar Ciencias de la computación y consiguen trabajo escribiendo código como «programadores».
La «ingeniería de software», sin embargo, suena más seria, como si implicara la aplicación de algunos conocimientos teóricos para construir algo real y preciso. Los ingenieros mecánicos, los ingenieros civiles, los ingenieros aeronáuticos y los de otras disciplinas de la ingeniería practican la ingeniería. Todos trabajan en el mundo real y utilizan la aplicación de sus conocimientos teóricos para crear algo real. Los ingenieros de software también crean «algo real», aunque es menos tangible que las cosas que crean otros ingenieros.
A diferencia de las profesiones de ingeniería más establecidas, la teoría o práctica actual de la ingeniería de software no es tan rigurosa. Los ingenieros aeronáuticos deben seguir pautas y prácticas rígidas, porque los errores en sus cálculos pueden causar daños reales; la programación, en general, no ha seguido tradicionalmente prácticas tan rigurosas. Pero, a medida que el software se integra más en nuestras vidas, debemos adoptar y confiar en métodos de ingeniería más rigurosos. Esperamos que este libro ayude a otros a ver un camino hacia prácticas de software más confiables.
Proponemos que la «ingeniería de software» abarque no solo el acto de escribir código, sino todas las herramientas y procesos que utiliza una organización para construir y mantener ese código a lo largo del tiempo. ¿Qué prácticas puede introducir una organización de software que mejor mantengan su valioso código a largo plazo? ¿Cómo pueden los ingenieros hacer más sostenible la base de código y más rigurosa la propia disciplina de la ingeniería del software? No tenemos respuestas esenciales a estas preguntas, pero esperamos que la experiencia colectiva de Google durante las últimas dos décadas ilumine posibles caminos para encontrar esas respuestas.
Una idea clave que compartimos en este libro es que la ingeniería de software se puede considerar como una «programación integrada a lo largo del tiempo». ¿Qué prácticas podemos introducir en nuestro código para hacerlo sostenible, capaz de reaccionar ante los cambios necesarios, a lo largo de su ciclo de vida, desde su concepción hasta su introducción, pasando por su mantenimiento y su eliminación?
En el libro, se hace hincapié en tres principios fundamentales que, en nuestra opinión, las organizaciones de software deberían tener en cuenta a la hora de diseñar, crear la arquitectura y escribir su código:
Tiempo y cambio
Cómo deberá adaptarse el código a lo largo de su vida.
Escala y crecimiento
Cómo deberá adaptarse una organización a medida que evoluciona.
Contrapartidas y costes
Cómo una organización toma decisiones, basándose en las lecciones del tiempo y el cambio, la escala y el crecimiento.
A lo largo de los capítulos, hemos tratado de enlazar con estos temas y señalar las formas en que tales principios afectan a las prácticas de ingeniería y permiten que sean sostenibles (véase el capítulo 1 para un análisis más completo).
Google tiene una perspectiva única sobre el crecimiento y la evolución de un ecosistema de software sostenible, derivado de nuestra escala y longevidad. Esperamos que las lecciones que hemos aprendido sean útiles a medida que su organización evolucione y adopte prácticas más sostenibles.
Dividimos los temas de este libro en tres aspectos principales del panorama de la ingeniería de software de Google:
• Cultura
• Procesos
• Herramientas
La cultura de Google es única, pero las lecciones que hemos aprendido en el desarrollo de nuestra cultura de ingeniería son de amplia aplicación. En los capítulos sobre la cultura (parte II), se destaca la naturaleza colectiva de una empresa de desarrollo de software, que el desarrollo de software es un esfuerzo de equipo y que los principios culturales adecuados son esenciales para que una organización crezca y se mantenga en buen funcionamiento.
Las técnicas descritas en los capítulos de «Procesos» (parte II) resultan familiares para la mayoría de los ingenieros de software, pero la base de código de gran tamaño y larga duración de Google proporciona una prueba de esfuerzo más completa para desarrollar las mejores prácticas. En esos capítulos, hemos intentado enfatizar lo que hemos descubierto que funciona a lo largo del tiempo y a escala, así como identificar áreas en las que aún no tenemos respuestas satisfactorias.
Por último, los capítulos dedicados a las herramientas (parte IV) ilustran el modo en que aprovechamos nuestras inversiones en la infraestructura de herramientas para proporcionar beneficios a la base de código, a medida que crece y envejece. En algunos casos, estas herramientas son específicas de Google, aunque indicamos las alternativas de código abierto o de terceros cuando corresponde. Esperamos que estos conocimientos básicos se apliquen a la mayoría de las organizaciones de ingeniería.
La cultura, los procesos y las herramientas que se describen en este libro describen las lecciones que, con suerte, un ingeniero de software típico aprende en el trabajo. Ciertamente, Google no tiene el monopolio de los buenos consejos, y nuestras experiencias, presentadas aquí, no pretenden dictar lo que su organización debe hacer. Este libro es nuestra perspectiva, pero esperamos que le resulte útil, ya sea adoptando estas lecciones directamente o utilizándolas como punto de partida a la hora de considerar sus propias prácticas, especializadas para su propio ámbito de problemas.
Esta obra tampoco pretende ser un sermón. El propio Google todavía aplica de forma imperfecta muchos de los conceptos de estas páginas. Las lecciones que hemos aprendido las hemos asimilado a través de nuestros fracasos: todavía cometemos errores, implementamos soluciones imperfectas y necesitamos repetir para mejorar. Sin embargo, el gran tamaño de la organización de ingeniería de Google garantiza que exista una diversidad de soluciones para cada problema. Esperamos que este libro contenga lo mejor de ese grupo.
Este libro no pretende contener el diseño de software, una disciplina que requiere su propio libro (y para el que ya existe mucho contenido). Aunque en la obra hay algo de código con fines ilustrativos, los principios son neutrales en cuanto al lenguaje y hay pocos consejos de «programación» en estos capítulos. Como resultado, este texto no trata muchos temas importantes en el desarrollo de software: administración de proyectos, diseño de application programming interfaces (API), fortalecimiento de la seguridad, internacionalización, marcos de interfaz de usuario u otras inquietudes específicas del lenguaje. Su omisión en este libro no implica su falta de importancia. En cambio, optamos por no incluirlos aquí sabiendo que no podríamos brindarles el tratamiento que merecen. Hemos intentado que las discusiones en este libro sean más sobre ingeniería y menos sobre programación.
Este texto ha sido un trabajo hecho con amor por parte de todos quienes han contribuido, y esperamos que lo reciba tal como se entrega: como una ventana que permite ver cómo una gran organización de ingeniería de software crea sus productos. También esperamos que sea una de las muchas voces que ayuden a que nuestra industria adopte prácticas más progresistas y sostenibles. Más importante aún, esperamos que disfrute de su lectura y pueda adaptar algunas de sus lecciones a sus propias inquietudes.
‒ Tom Manshreck
Este elemento significa una nota general.
Durante muchos años, O’Reilly Media y Marcombo han proporcionado la máxima formación en tecnología y negocios, conocimientos y perspectivas para ayudar a las empresas a tener éxito.
Nuestra red única de expertos y personal innovador comparte su conocimiento y experiencia a través de libros, artículos y distintas plataformas de aprendizaje en línea. Por ejemplo, la plataforma de aprendizaje en línea de O’Reilly le brinda acceso bajo demanda a cursos de capacitación en vivo, rutas de aprendizaje en profundidad, entornos de codificación interactivos y una amplia colección de texto y vídeos de O’Reilly y más de doscientos editores. Para más información, por favor, visite http://oreilly.com.
Tenemos una página web para este libro, donde enumeramos erratas, ejemplos y cualquier información adicional. Puede acceder a esta página en https://oreil.ly/software-engineering-at-google.
Un libro como este no sería posible sin el trabajo de muchas personas. Todo el conocimiento contenido en este libro nos ha llegado a todos a través de la experiencia de tantos otros en Google a lo largo de nuestras carreras. Somos los mensajeros; otros llegaron antes que nosotros, a Google y a otros lugares, y nos enseñaron lo que ahora les presentamos. No podemos enumerarlos a todos aquí, pero deseamos expresar nuestros reconocimientos.
También nos gustaría agradecerle a Melody Meckfessel su apoyo a este proyecto en sus inicios, así como a Daniel Jasper y Danny Berlin, por apoyarlo hasta su finalización.
Este libro no habría sido posible sin el enorme esfuerzo de colaboración de los organizadores, autores y editores. Aunque a los autores y editores se los reconoce específicamente en cada capítulo o rótulo, nos gustaría dedicar tiempo para reconocer a aquellos que contribuyeron a cada capítulo proporcionando comentarios, discusiones y reseñas para la reflexión.
•¿Qué es la ingeniería de software?: Sanjay Ghemawat, Andrew Hyatt
•Trabajar bien en equipo: Sibley Bacon, Joshua Morton
•Compartir conocimientos: Dimitri Glazkov, Kyle Lemons, John Reese, David Symonds, Andrew Trenk, James Tucker, David Kohlbrenner, Rodrigo Damazio Bovendorp
•Ingeniería para la equidad: Kamau Bobb, Bruce Lee
•Cómo liderar un equipo: Jon Wiley, Laurent Le Brun
•Liderazgo a escala: Bryan O’Sullivan, Bharat Mediratta, Daniel Jasper, Shaindel Schwartz
•Medición de la productividad de la ingeniería: Andrea Knight, Collin Green, Caitlin Sadowski, Max-Kanat Alexander, Yilei Yang
•Guía de estilo y normas: Max Kanat-Alexander, Titus Winters, Matt Austern, James Dennett
•Revisión del código: Max Kanat-Alexander, Brian Ledger, Mark Barolak
•Documentación: Jonas Wagner, Smit Hinsu, Geoffrey Romer
•Descripción general de las pruebas: Erik Kuefler, Andrew Trenk, Dillon Bly, Joseph Graves, Neal Norwitz, Jay Corbett, Mark Striebeck, Brad Green, Miško Hevery, Antoine Picard, Sarah Storck
•Examen de la unidad: Andrew Trenk, Adam Bender, Dillon Bly, Joseph Graves, Titus Winters, Hyrum Wright, Augie Fackler
•Dobles de pruebas: Joseph Graves, Gennadiy Civil
•Pruebas más grandes: Adam Bender, Andrew Trenk, Erik Kuefler, Matthew Beaumont-Gay
•Depreciación: Greg Miller, Andy Shulman
•Control de versiones y gestión de ramas: Rachel Potvin, Victoria Clarke
•Búsqueda de código: Jenny Wang
•Sistemas de compilación y filosofía de la compilación: Hyrum Wright, Titus Winters, Adam Bender, Jeff Cox, Jacques Pienaar
•Critique, herramienta de revisión del código de Google: Mikołaj Dądela, Hermann Loose, Eva May, Alice Kober-Sotzek, Edwin Kempin, Patrick Hiesel, Ole Rehmsen, Jan Macek
•Análisis estático: Jeffrey van Gogh, Ciera Jaspan, Emma Söderberg, Edward Aftandilian, Collin Winter, Eric Haugh
•Gestión de las dependencias: Russ Cox, Nicholas Dunn
•Cambios a gran escala: Matthew Fowles Kulukundis, Adam Zarek
•Integración continua: Jeff Listfield, John Penix, Kaushik Sridharan, Sanjeev Dhanda
•Entrega continua: Dave Owens, Sheri Shipe, Bobbi Jones, Matt Duftler, Brian Szuter
•La computación como servicio: Tim Hockin, Collin Winter, Jarek Kuśmierek
Además, nos gustaría expresar nuestro agradecimiento a Betsy Beyer, por compartir su conocimiento y experiencia al publicar el libro original Site Reliability Engineering, que hizo que nuestra experiencia fuera mucho más fluida. Christopher Guzikowski y Alicia Young de O’Reilly hicieron un trabajo increíble, al lanzar y dirigir este proyecto hasta su publicación.
Los organizadores también quieren mostrar particularmente su agradecimiento a las siguientes personas:
Tom Manshreck: a mi madre y a mi padre, por hacerme creer en mí mismo y por trabajar conmigo en la mesa de la cocina para hacer los deberes.
Titus Winters: a papá, por mi camino; a mamá, por mi voz; a Victoria, por mi corazón; a Raf, por darme la espalda. También al señor Snyder, Ranwa, Z, Mike, Zach, Tom (y todos los Paynes), mec, Toby, cgd y Melody, por las lecciones, la tutoría y la confianza.
Hyrum Wright: a mamá y papá, por sus ánimos; a Bryan y los habitantes de Bakerland, por mi primera incursión en el software; a Dewayne, por continuar ese viaje; a Hannah, Jonathan, Charlotte, Spencer y Ben, por su amor e interés; a Heather, por estar ahí, a pesar de todo.
Autor: Titus WintersEditor: Tom Manshreck
Nada está construido en piedra; todo está construido sobre arena, pero debemos construir como si la arena fuera piedra.
—Jorge Luis Borges
Podemos distinguir tres diferencias críticas entre la programación y la ingeniería de software: el tiempo, la escala y las contrapartidas que entran en juego. En un proyecto de ingeniería de software, lo que más debe preocupar a los ingenieros es el transcurso del tiempo y la eventual necesidad de cambios. En una organización de ingeniería de software, nos deben preocupar, en mayor medida, la escala y la eficiencia, tanto en lo que se refiere al software que creamos como a la organización encargada de crearlo. Por último, como ingenieros de software, se nos pide que adoptemos decisiones cada vez más complejas y cuyos resultados son progresivamente de mayor trascendencia, frecuentemente en función de estimaciones imprecisas en lo que respecta al tiempo y el crecimiento.
En Google, decimos a veces que «la ingeniería de software es la programación integrada que se alcanza con el transcurso del tiempo». La programación es, sin duda, una parte importante de la ingeniería de software: después de todo, la programación es, antes que nada, la forma de generar nuevo software. Si aceptamos esta distinción, también queda claro que es posible que necesitemos delimitar entre tareas de programación (desarrollo) y tareas de ingeniería de software (desarrollo, modificación y mantenimiento). La incorporación del factor tiempo añade una importante y nueva dimensión a la programación. Los cubos no son cuadrados; la distancia no es la velocidad. La ingeniería de software no es programación.
Una forma de apreciar el impacto del tiempo en un programa es pensar en la pregunta: «¿Cuál es el tiempo de vida útil esperado1 de nuestro código?». Las respuestas razonables a esta pregunta pueden variar aproximadamente en un factor de 100 000. Es tan razonable pensar en un código que debe durar unos minutos como imaginar un código cuyo tiempo de vida durará décadas. Generalmente, el código que se sitúa a corto plazo de ese espectro no se ve afectado por el factor tiempo. Es poco probable que necesitemos adaptarnos a una nueva versión de las bibliotecas subyacentes, del sistema operativo (SO), del hardware, o a la versión del lenguaje, en el caso de un programa cuya utilidad dura solo una hora. Estos sistemas de corta duración son, efectivamente, un problema «solo» de programación, de la misma manera que un cubo al que se comprime lo suficiente para reducir una de sus dimensiones a cero se convierte en un cuadrado. A medida que ampliamos ese tiempo para permitir una vida útil más prolongada, el cambio adquiere una mayor importancia. En un lapso de una década o más, la mayoría de las dependencias de los programas, ya sean implícitas o explícitas, probablemente cambiarán. Este reconocimiento está en la raíz de la distinción que hacemos entre ingeniería de software y programación.
Esta diferenciación está en el centro de lo que llamamos sostenibilidad del software. El proyecto es sostenible si, durante el tiempo de vida útil previsto del software, somos capaces de reaccionar ante cualquier cambio importante que se produzca, ya sea por motivos técnicos o empresariales. Es importante destacar que solo buscamos tener la capacidad de actuar, ya que se puede optar por no realizar una determinada actualización, bien por falta de valor o debido a otras prioridades2. Cuando somos absolutamente incapaces de reaccionar ante un cambio en la tecnología subyacente o en la orientación del producto, estamos haciendo una apuesta de alto riesgo con la esperanza de que dicho cambio nunca llegue a ser crítico. Para proyectos a corto plazo, esa podría ser una apuesta segura. Para los que duran varias décadas, posiblemente no3.
Otra forma de ver la ingeniería de software es teniendo en cuenta la escala. ¿Cuántas personas están involucradas? ¿Qué papel desempeñan en el desarrollo y mantenimiento a lo largo del tiempo? Una tarea de programación es, a menudo, un acto de creación individual, pero una tarea de ingeniería de software supone un esfuerzo de equipo. Un primer intento de definir la ingeniería de software dio lugar a una buena definición desde este punto de vista: «El desarrollo de programas de múltiples versiones en los que intervienen varias personas4». Esto sugiere que la diferencia entre la ingeniería de software y la programación es una cuestión tanto de tiempo como de personas. La colaboración en equipo suscita nuevos problemas, pero también proporciona más potencial que cualquier programador individual para crear programas eficaces.
La organización del equipo, la composición del proyecto y las políticas y prácticas de un proyecto de software dominan este aspecto de la complejidad de la ingeniería de software. Dichos problemas son inherentes a la escala: a medida que la organización crece y sus proyectos se amplían, ¿se vuelve más eficiente en la producción de software? El flujo de trabajo de desarrollo, ¿es más eficiente a medida que crecemos, o nuestras políticas de control de versiones y estrategias de pruebas son proporcionalmente más costosas? Los problemas de escala sobre la comunicación y el aumento de los recursos humanos se han discutido desde el inicio de la ingeniería de software, a partir de la aparición del libro Mythical Man Month5. Estos problemas de escala son, a menudo, asuntos concernientes a las políticas y se refieren, fundamentalmente, a la cuestión de la sostenibilidad del software: ¿cuánto costará hacer las cosas que tenemos que hacer de forma repetida?
También podemos decir que la ingeniería de software es diferente de la programación en términos de la complejidad de las decisiones que deben adoptarse y de lo que está en juego. En ingeniería de software nos vemos obligados, frecuentemente, a evaluar las contrapartidas entre varios caminos que seguir, a veces con mucho en juego y, a menudo, con métricas de valor imperfectas. El trabajo de un ingeniero de software, o un responsable de ingeniería de software, consiste en aspirar a la sostenibilidad y a la gestión de los costes de escalado para la organización del producto y del flujo de trabajo de desarrollo. Con esas aportaciones en mente, evaluamos las contrapartidas y adoptamos decisiones racionales. A veces, podemos aplazar los cambios relativos al mantenimiento o incluso adoptar políticas que no se adaptan bien, sabiendo que tendremos que revisar esas decisiones. Esas opciones deben ser explícitas y estar claras en lo que se refiere a los costes diferidos.
En ingeniería de software, rara vez existe una solución única para todos, y el mismo principio se aplica a este libro. Dado un factor de 100 000 para respuestas razonables sobre «¿cuánto tiempo de vida tendrá este software?», un rango de quizá un factor de 10 000 para «¿cuántos ingenieros hay en la organización?» y quién sabe qué rango para «¿cuántos recursos de cálculo hay disponibles para el proyecto?», la experiencia de Google probablemente no coincidirá con la suya. Nuestro objetivo, en este libro, es presentar lo que hemos descubierto que funciona en la creación y el mantenimiento de un software que esperamos dure décadas, con decenas de miles de ingenieros y recursos informáticos que se encuentran repartidos por todo el mundo. La mayoría de las prácticas que consideramos necesarias a esa escala también funcionarán adecuadamente para proyectos más pequeños: lo podemos considerar como una referencia sobre un ecosistema de ingeniería que creemos que podría ser bueno a medida que escalamos. En algunos lugares, la escala muy grande tiene sus propios costes y nos complacería no tener que pagar gastos generales adicionales. Los citamos como advertencia. Con suerte, si su organización crece lo suficiente como para preocuparse por esos costes, podrá encontrar una respuesta mejor.
Antes de abordar los detalles sobre el trabajo en equipo, la cultura, las políticas y las herramientas, analicemos primero los temas fundamentales del tiempo, la escala y las contrapartidas.
Cuando un principiante está aprendiendo a programar, el tiempo de vida útil del código resultante se mide, generalmente, en horas o días. Las tareas de programación y los ejercicios tienden a escribirse una vez, con poca o ninguna refactorización y, ciertamente, sin la necesidad de mantenimiento a largo plazo. Estos programas, a menudo, no se reconstruyen ni se ejecutan después de su elaboración inicial. Este hecho no es sorprendente en un entorno pedagógico. Quizá en la educación secundaria o posterior, podemos encontrar algún curso sobre proyectos en equipo o una tesis práctica. De ser así, es probable que estos proyectos sean el único momento en el que el tiempo de vida del código de los estudiantes sea de aproximadamente un mes o más. Es posible que esos desarrolladores necesiten refactorizar algún código, tal vez como respuesta a cambios en los requisitos, pero es poco probable que se les pida que se ocupen de cambios más extensos en su entorno.
También encontramos desarrolladores de código de corta duración en configuraciones industriales habituales. Las aplicaciones móviles, a menudo, tienen un tiempo de vida útil bastante corto6 y, para bien o para mal, es relativamente frecuente la reescritura de todo. Los ingenieros que se encuentran en su etapa inicial pueden optar, con razón, por centrarse en los objetivos inmediatos, en lugar de hacerlo en las inversiones a largo plazo: es posible que la empresa no viva lo suficiente para cosechar los beneficios de una inversión en infraestructura que se amortiza lentamente. Es razonable pensar que un desarrollador de startups en serie pueda tener diez años de experiencia en desarrollo y poca o ninguna experiencia en el mantenimiento de cualquier software que se espera que exista durante más de uno o dos años.
En el otro extremo del espectro, algunos proyectos de éxito tienen un tiempo ilimitado de vida útil: no podemos pronosticar, de forma razonable, un punto final para Google Search, el kernel de Linux o el proyecto del servidor HTTP Apache. Para la mayoría de los proyectos de Google, debemos asumir que tendrán un tiempo de vida indefinido; no podemos pronosticar cuándo dejaremos de tener la necesidad de actualizar nuestras dependencias o versiones del lenguaje. A medida que aumentan sus tiempos de vida, estos proyectos de larga duración acaban teniendo un aire diferente al de las tareas de programación o al de desarrollo de nuevas empresas.
Consideremos la figura 1.1, en la que se muestran dos proyectos de software situados en los extremos opuestos de este espectro de «tiempo de vida útil esperado». Para un programador que trabaja en una tarea con un tiempo de vida útil previsto de horas, ¿qué tipo de mantenimiento es razonable esperar? Es decir, si aparece una nueva versión del sistema operativo mientras está trabajando en una secuencia de comandos de Python que se ejecutará una vez, ¿debería dejar lo que está haciendo y actualizarlo? Por supuesto que no: la actualización no es crítica. Pero, en el extremo opuesto del espectro, que Google Search esté atascada en una versión de nuestro sistema operativo de los años noventa sería claramente un problema.
Figura 1.1.Tiempo de vida útil e importancia de las actualizaciones
Los puntos bajo y alto en el espectro del tiempo de vida esperado sugieren que hay una transición en algún lugar. En un sitio determinado, a lo largo de la línea entre un programa único y un proyecto que dura décadas, ocurre una transición: un proyecto debe comenzar a reaccionar a factores externos cambiantes7. Para cualquier proyecto para el que no hemos planeado actualizaciones desde el principio, esa transición es, probablemente, muy penosa por tres razones, cada una de las cuales agrava a las demás:
• Llevamos a cabo una tarea que aún no se ha realizado para este proyecto; se han incorporado más supuestos que estaban ocultos.
• Es poco probable que los ingenieros que intentan realizar la actualización tengan experiencia en este tipo de tareas.
• El tamaño de la actualización suele ser mayor de lo habitual, y se realizan de una sola vez actualizaciones correspondientes a varios años, en lugar de llevar a cabo una actualización más gradual.
Y, por lo tanto, después de pasar una vez por una actualización de este tipo (o abandonar a medio camino), es bastante razonable sobrestimar el coste de hacer una actualización posterior y, en consecuencia, decidir no repetir «nunca más». Las empresas que llegan a esta conclusión, o bien terminan comprometiéndose a simplemente tirar cosas y reescribir el código, o bien deciden no volver a actualizar. En lugar de adoptar un enfoque natural de evitar una tarea dolorosa, a veces la respuesta más responsable es invertir en hacerla menos penosa. Todo depende del coste de la actualización, el valor que proporciona y el tiempo de vida útil esperado del proyecto en cuestión.
Superar no solo esa primera gran actualización, sino llegar al punto en el que el proyecto pueda mantenerse actualizado de manera confiable en el futuro, es su esencia de la sostenibilidad a largo plazo. La sostenibilidad requiere planificar y gestionar el impacto de los cambios necesarios. Para muchos proyectos en Google, creemos que hemos logrado este tipo de sostenibilidad, en gran parte a través del método de prueba y error.
Entonces, concretamente, ¿en qué se diferencia la programación a corto plazo de la generación de código con un tiempo de vida útil esperado mucho más largo? Con el paso del tiempo, debemos ser mucho más conscientes de la diferencia entre «funciona» y «se puede mantener». No existe una solución perfecta para identificar estos problemas. Esto es lamentable, porque mantener el software a largo plazo es una batalla constante.
Si estamos manteniendo un proyecto que utilizan otros ingenieros, la lección más importante sobre «funciona» versus «se puede mantener» es lo que hemos venido en llamar la ley de Hyrum: «Con un número suficiente de usuarios de una API, no importa lo que prometa en el contrato: todos los comportamientos observables de su sistema dependerán de alguien».
Según nuestra experiencia, este axioma es un factor dominante en cualquier debate sobre el cambio de software a lo largo del tiempo. Es conceptualmente similar a la entropía: en los debates sobre el cambio y el mantenimiento en el tiempo, se debe tener en cuenta la ley de Hyrum8, al igual que, en los debates sobre la eficiencia o la termodinámica, se debe tener en cuenta la entropía. El hecho de que la entropía nunca disminuya no significa que no debamos intentar ser eficientes. El hecho de que la ley de Hyrum se aplique al mantenimiento del software no significa que no podamos planificarlo o intentar comprenderlo mejor. Podemos mitigarla, pero sabemos que nunca se podrá erradicar.
La ley de Hyrum representa el conocimiento práctico de que, incluso con las mejores intenciones, los mejores ingenieros y concienzudas prácticas para la revisión de código, no podemos asumir la adhesión perfecta a los contratos publicados o a las mejores prácticas. Como propietario de una API, obtendrá algo de flexibilidad y libertad, al ser claro sobre las promesas de la interfaz, pero, en la práctica, la complejidad y dificultad de un determinado cambio también depende de lo útil que encuentren los usuarios algún comportamiento observable de su API. Si los usuarios no pueden confiar en tales cosas, su API será fácil de cambiar. Con suficiente tiempo y los suficientes usuarios, incluso el cambio más inocuo romperá algo9. El análisis que usted haga del valor de ese cambio debe incorporar la dificultad de investigar, identificar y resolver esas roturas.
Consideremos el ejemplo de la ordenación de la iteración de hash. Si insertamos cinco elementos en un conjunto basado en hash, ¿en qué orden los sacamos?
La mayoría de los programadores saben que las tablas hash no están ordenadas de forma obvia. Pocos conocen los detalles de si la tabla hash concreta que están usando tiene la intención de proporcionar ese orden en particular para siempre. Esto puede parecer normal, pero, durante la última década o dos, la experiencia de la industria de la computación en el uso de estos tipos ha evolucionado:
• Los ataques por inundaciones de hash10 proporcionan un mayor incentivo para la iteración de hash no determinista.
• Los beneficios en cuanto a la eficiencia derivados de la investigación de algoritmos hash mejorados o contenedores hash requieren cambios en el orden de iteración hash.
• Según la ley de Hyrum, los programadores escribirán programas que dependan del orden en que se recorre una tabla hash, si tienen la posibilidad de hacerlo.
Como resultado, si le pregunta a cualquier experto «¿puedo asumir una secuencia de salida concreta para mi contenedor de hash?», ese experto presumiblemente responderá: «No». En general, eso es correcto, pero quizá simplista. Una respuesta más matizada es: «Si tu código es de corta duración, sin cambios en el hardware, ni en el tiempo de ejecución del lenguaje o en la elección de la estructura de datos, esa suposición está bien. Si no sabe cuánto tiempo vivirá su código, o no puede prometer que nada de lo que dependa de usted cambiará, tal suposición es incorrecta». Además, incluso si su propia implementación no depende del orden del contenedor hash, la podría utilizar otro código que implícitamente crea tal dependencia; por ejemplo, si la biblioteca pone en serie valores como respuesta a una llamada a procedimiento remoto (RPC), el programa que llama a RPC podría terminar dependiendo del orden de esos valores.
Este es un ejemplo muy básico de la diferencia entre «funciona» y «es correcto». Para un programa de corta duración, dependiendo del orden de iteración sobre sus contenedores, no causará ningún problema técnico. Para un proyecto de ingeniería de software, sin embargo, esa dependencia de un orden definido es un riesgo; si se da el tiempo suficiente, algo hará que sea beneficioso cambiar ese orden de iteración. Ese beneficio puede manifestarse de varias maneras, ya sea en lo que se refiere a la eficiencia, la seguridad o, simplemente, la protección de la estructura de datos para permitir futuros cambios. Cuando ese beneficio esté claro, deberá sopesar las contrapartidas entre ese beneficio y el pesar de romper con sus desarrolladores o clientes.
Algunos lenguajes aleatorizan específicamente el ordenamiento del hash entre versiones de bibliotecas o incluso entre ejecuciones del mismo programa en un intento de evitar dependencias. Pero, incluso así, todavía puede haber algunas sorpresas relacionadas con la ley de Hyrum: hay un código en el que se usa el orden de iteración hash como un generador ineficiente de números aleatorios. Eliminar tal aleatoriedad ahora invalidaría a esos usuarios. Así como la entropía aumenta en todos los sistemas termodinámicos, la ley de Hyrum se aplica a todos los comportamientos observables.
Pensando en las diferencias entre el código escrito con una mentalidad de «funciona ahora» y la otra de «funciona indefinidamente», podemos extraer algunas relaciones claras. Si consideramos el código como un artefacto con el requisito de un tiempo de vida útil (muy) variable, podemos comenzar a categorizar los estilos de programación: el código que depende de características frágiles e inéditas de sus dependencias probablemente se describa como «sucio» o «inteligente», mientras que el código que sigue las mejores prácticas y se ha planificado para el futuro es más probable que se describa como «limpio» y «susceptible de mantenimiento». Ambos tienen sus propósitos, pero el que seleccionemos depende, fundamentalmente, del tiempo de vida útil esperado del código en cuestión. Se dice que es «programación si “inteligente” es un cumplido, pero es ingeniería de software si “inteligente” es una acusación».
Implícita en toda esta discusión sobre el tiempo y la necesidad de reaccionar al cambio está la suposición de que el cambio podría ser necesario. ¿Lo es?
Como ocurre con todo lo demás en este libro, depende. Nos comprometemos fácilmente con «la mayoría de los proyectos, durante un periodo de tiempo suficientemente largo, pero es posible que sea necesario cambiar todo lo que hay debajo». Si tenemos un proyecto escrito en C puro, sin dependencias externas (o solo dependencias externas que prometen una gran estabilidad a largo plazo, como POSIX), es posible que podamos evitar cualquier forma de refactorización o actualización difícil. C hace un gran trabajo, al proporcionar estabilidad; en muchos aspectos, ese es su propósito fundamental.
La mayoría de los proyectos están expuestos, sobre todo, a cambios en la tecnología subyacente. La mayor parte de lenguajes de programación, así como los tiempos de ejecución, cambian mucho más de lo que lo hace C. Incluso las bibliotecas implementadas en C puro pueden cambiar para admitir nuevas funciones, lo que puede afectar a los usuarios intermedios. Los problemas de seguridad se revelan en todo tipo de tecnologías, desde procesadores hasta bibliotecas de redes y códigos de aplicaciones. Cada elemento de tecnología de la que depende su proyecto tiene algún riesgo (con suerte pequeño) de contener errores críticos y vulnerabilidades de seguridad que podrían salir a la luz solo después de que haya empezado a confiar en ella. Si no puede implementar un parche para Heartbleed (http://heartbleed.com) o atenuar problemas especulativos de ejecución, como Meltdown y Spectre (https://meltdownattack.com), porque ha asumido (o prometido) que nada cambiará nunca, esa es una apuesta considerable.
Las mejoras en la eficiencia complican aún más el panorama. Queremos equipar nuestros centros de datos con equipos informáticos rentables, especialmente para mejorar la eficiencia de las unidades centrales de procesamiento (CPU). Sin embargo, los algoritmos y las estructuras de datos de los primeros tiempos de Google son, sencillamente, menos eficientes en los equipos modernos: una lista enlazada o un árbol de búsqueda binario seguirán funcionando bien, pero la brecha cada vez mayor entre los ciclos de CPU y la latencia de la memoria impacta en la imagen de lo que sería el código «eficiente». Con el tiempo, el beneficio de la actualización a un hardware nuevo puede disminuir si no se producen cambios de diseño en el software. La compatibilidad con versiones anteriores asegura que los sistemas antiguos sigan funcionando, pero eso no garantiza que las antiguas optimizaciones sigan siendo útiles. Si no queremos o no podemos aprovechar tales oportunidades, corremos el riesgo de incurrir en importantes costes. Problemas de eficiencia como este son particularmente sutiles: el diseño original podría haber sido perfectamente lógico y seguir las mejores y razonables prácticas. Solo después de una evolución de cambios compatibles con versiones anteriores, una opción nueva y más eficiente se convierte en una necesidad importante. No se cometieron errores, pero fue el paso del tiempo lo que hizo que el cambio fuera beneficioso.
Preocupaciones como las que acabamos de mencionar explican por qué existen grandes riesgos para los proyectos a largo plazo en los que no se ha invertido en sostenibilidad. Debemos ser capaces de responder a este tipo de problemas y aprovechar estas oportunidades, independientemente de si nos afectan directamente o se manifiestan solo en el cierre por transición de la tecnología que utilizamos como base. El cambio no es inherentemente bueno. No deberíamos cambiar solo por cambiar. Pero necesitamos ser capaces de cambiar. Si admitimos esa eventual necesidad, también deberíamos considerar la posibilidad de invertir para hacer que esa capacidad sea barata. Como todo administrador de sistemas sabe, una cosa es saber, en la teoría, qué se puede recuperar de la cinta y otra es saber, en la práctica, exactamente cómo hacerlo y cuánto costará cuando sea necesario. La práctica y la experiencia son grandes impulsores de la eficiencia y la fiabilidad.
Como se indica en el libro Site Reliability Engineering (SRE)11, el sistema de producción de Google en su conjunto se encuentra entre las máquinas más complejas creadas por la humanidad. La complejidad inherente a la construcción de una máquina de este tipo, así como a la capacidad de mantenerla funcionando sin problemas, ha requerido innumerables horas de pensamiento, discusión y rediseño por parte de expertos en toda nuestra organización y en todo el mundo; así que ya hemos escrito un libro sobre la complejidad de mantener esa máquina funcionando a esa escala.
Gran parte de este libro se centra en la complejidad de la escala de la organización que produce dicha máquina y los procesos que usamos para mantenerla en funcionamiento a lo largo del tiempo. Consideremos nuevamente el concepto de sostenibilidad de la base de código: «La base de código de su organización es sostenible cuando puede cambiar todas las cosas que debe cambiar, de forma segura, y puede hacerlo durante todo el tiempo de vida útil de la base de código». En el debate sobre la capacidad se esconde uno de los costes: si cambiar algo supone un coste excesivo, es probable que se posponga. Si los costes crecen con el tiempo por encima de una función lineal, es evidente que la operación no es escalable12. A la larga, el tiempo se impondrá y surgirá algo inesperado que será necesario cambiar. Cuando su proyecto duplique su alcance y necesite realizar esa tarea de nuevo, ¿supondrá el doble de mano de obra? ¿Tendrá los recursos humanos necesarios para abordar el problema?
Los costes en recursos humanos no son el único medio limitado que necesita escalar. Así como el software en sí necesita escalar adecuadamente con recursos tradicionales como computación, memoria, almacenamiento y ancho de banda, el desarrollo de ese software también debe escalar, tanto en términos de participación de tiempo de los recursos humanos como de los recursos de computación, que impulsan el flujo de trabajo de desarrollo. Si el coste de cómputo para su cluster de prueba crece por encima de una función lineal, consumiendo más recursos de cómputo por persona cada trimestre, está en un camino insostenible y necesita hacer cambios rápidamente.
Por último, el activo más valioso de una organización de software, la base de código en sí, también debe escalar. Si su sistema de compilación o un sistema de control de versiones escala con el tiempo por encima de una función lineal, tal vez como resultado del crecimiento y de un historial de registros de cambios cada vez mayor, podría llegar a un punto en el que, sencillamente, no pueda continuar. Preguntas como «¿cuánto tiempo se tarda en hacer una compilación completa?», «¿cuánto tiempo se tarda en obtener una copia nueva del repositorio?» o «¿cuánto costará actualizar a una nueva versión de lenguaje?» no se supervisan de forma activa y cambian a un ritmo lento. Puede ocurrir como en la metáfora de la rana hervida (https://oreil.ly/clqZN). Es demasiado fácil que los problemas empeoren lentamente y nunca se manifiesten como un momento singular de crisis. Solo con la concienciación y el compromiso de toda la organización en cuanto al escalado es probable que se mantenga al tanto de estas cuestiones.
Todo aquello en lo que su organización confía para crear y mantener el código debe ser escalable en términos de coste global y consumo de recursos. En particular, todo lo que su organización debe hacer repetidamente ha de ser escalable en términos de esfuerzo humano. Muchas políticas comunes no parecen ser escalables en este sentido.
Con un poco de práctica, resulta más fácil detectar las políticas con malas propiedades de escalado. Por lo general, estas se pueden identificar considerando el trabajo encomendado a un solo ingeniero e imaginando que la organización aumentará entre diez y cien veces su tamaño. Cuando seamos 10 veces más grandes, ¿añadiremos 10 veces más trabajo con el que nuestro ingeniero de muestra tenga que seguir el ritmo? ¿Aumenta en función del tamaño de la organización la cantidad de trabajo que debe realizar nuestro ingeniero? ¿Se incrementa el trabajo con el tamaño de la base de código? Si alguna de las respuestas es afirmativa, ¿disponemos de algún mecanismo para automatizar u optimizar ese trabajo? Si no es así, tenemos problemas de escala.
Considere un enfoque tradicional de la depreciación. Discutiremos sobre la depreciación a fondo en el capítulo 15, pero el enfoque común de la depreciación sirve como un gran ejemplo de problemas de escala. Se ha desarrollado un nuevo widget. Se adopta la decisión de que todos deben usar el nuevo y dejar de utilizar el anterior. Para motivar esta decisión, los líderes del proyecto dicen: «Eliminaremos el widget anterior el 15 de agosto; asegúrese de utilizar el nuevo widget».
Este tipo de enfoque puede funcionar en una configuración de software pequeña, pero falla pronto, a medida que aumenta la profundidad y la amplitud del gráfico de dependencias. Los equipos dependen de un número cada vez mayor de widgets, y una sola interrupción de la compilación puede afectar a un porcentaje creciente de los equipos de la empresa. Resolver estos problemas de manera escalable significa cambiar la forma en la que tratamos la depreciación: en lugar de trasladar el trabajo de migración a los usuarios, los equipos lo pueden hacer internamente ellos mismos, con las economías de escala que eso conlleva.
En 2012, intentamos poner fin al problema de la depreciación con reglas que reducen la rotación: los equipos de infraestructura deben hacer el trabajo para que sus usuarios internos se muevan a las nuevas versiones por sí mismos o realizar la actualización in situ, de manera compatible con las versiones anteriores. Esta política, a la que hemos llamado «regla de la rotación», escala mejor: ya no hay que hacer un esfuerzo cada vez mayor para mantener los proyectos dependientes. También hemos aprendido que tener un grupo dedicado de expertos permite ejecutar las escalas de cambio mejor que pedir más esfuerzo de mantenimiento a cada usuario: los expertos dedican algún tiempo a aprender todo el problema en profundidad y, luego, aplican esa experiencia a cada parte del problema. Obligar a los usuarios a responder a la rotación significa que cada equipo afectado hace un peor trabajo de puesta en marcha, resuelven el problema inmediato y luego desechan ese conocimiento que ya les resulta inútil. La experiencia escala mejor.
El uso tradicional de divisiones de desarrollo es otro ejemplo de políticas que lleva incorporados problemas de escala. Una organización podría identificar que la fusión de grandes características en el tronco ha desestabilizado el producto y concluir: «Necesitamos controles más estrictos sobre cuándo se fusionan las cosas. Deberíamos hacer las fusiones con menos frecuencia». Esto conduce, inmediatamente, a que cada equipo o cada función tengan divisiones de desarrollo separadas. Siempre que se decide que una división está «completa», se prueba y se fusiona con el tronco, lo que genera un trabajo potencialmente costoso para otros ingenieros que aún trabajan en su división de desarrollo, manifestándose en forma de resincronización y pruebas. Este tipo de gestión de ramas puede funcionar para una pequeña organización que tenga entre cinco y diez ramas de esta clase. Como el tamaño de una organización (y el número de divisiones) aumenta, rápidamente se hace evidente que estamos incurriendo en una cantidad cada vez mayor de gastos generales para realizar la misma tarea. Necesitaremos un enfoque diferente a medida que escalamos; lo discutimos en el capítulo 16.
¿Qué tipo de políticas mejoran los costes a medida que crece la organización? O, mejor aún, ¿qué tipo de políticas podemos implementar que proporcionen valor por encima de una función lineal a medida que la organización crece?