Arqueología de software: un método práctico para leer código heredado
Aprenda un enfoque paso a paso para investigar de forma segura bases de código heredadas sin documentación, desde analizar el historial de commits hasta refactorizarlas sin interrumpir el funcionamiento en producción.
Existe un tipo específico de miedo que todo desarrollador experimenta eventualmente.
Abres un archivo: tiene más de 4,000 líneas. No hay ni un solo comentario a la vista; la mitad de las variables llevan nombres como x2 o tempFinal_REAL, y enterrada en algún punto del medio se encuentra una función llamada doStuff() que, por lo que puedes deducir, es la responsable silenciosa de los cobros en toda la empresa.
Ejecutas git blame y el rastro conduce a alguien que dejó la empresa hace seis años. Una búsqueda en Slack no arroja ninguna información sobre por qué existe todo esto. La única persona que quizás aún lo recuerde está fuera de la oficina, y, honestamente, probablemente también tendría vagas ideas sobre los detalles.
Esto es arqueología de software.
No aparece en ningún organigrama ni en ninguna oferta de empleo, pero si has pasado más de uno o dos años desarrollando software, ya lo has practicado. Has revisado código antiguo de la misma manera que un arqueólogo de campo examina un sitio de excavación: lentamente, con atención, tratando de comprender qué pensaban realmente las personas que lo crearon, aunque ya se hayan ido y no puedan aclararlo.
Lo que sigue es una guía para realizar ese trabajo de manera eficaz, sin perder la cordura ni interrumpir el funcionamiento del sistema.
¿Qué es realmente la arqueología de software?
La arqueología de software consiste en investigar, interpretar y dar sentido al código antiguo o no documentado, generalmente con el fin de poder mantenerlo de forma segura, ampliarlo o reemplazarlo eventualmente.
Es un ejercicio diferente al depuración habitual. La depuración parte de la premisa de que se comprende el sistema y que algo dentro de él ha fallado. La arqueología de software parte de la premisa opuesta: aún no se comprende en absoluto el sistema, y la verdadera primera tarea es adquirir ese conocimiento antes de atreverse a cambiar algo.
Es la diferencia entre un mecánico que da mantenimiento a un modelo de coche en el que ha trabajado durante años y otro que restaura un Peugeot de 1962 al que nunca ha abierto antes. El mismo conjunto de llaves, pero una mentalidad completamente distinta.
La mayoría de los ingenieros no eligen este tipo de trabajo; simplemente terminan en él. Comienzas un nuevo empleo y, seis semanas después, alguien te entrega una tarea relacionada con “el servicio de inventario antiguo”. De repente ya no estás escribiendo código nuevo; estás realizando excavaciones.
Por qué esta habilidad es más importante de lo que la gente admite
Aquí hay un hecho incómodo: la mayoría del software que realmente hace funcionar el mundo no es nuevo. Es antiguo, arreglado poco a poco a lo largo de los años, solo parcialmente comprendido, y constituye en silencio una fuente de ansiedad para quien sea nominalmente responsable de él.
Los bancos siguen utilizando COBOL escrito en la década de 1970. Las aerolíneas gestionan las rutas de vuelo a través de sistemas que son más antiguos que los propios pilotos que operan esas aeronaves. Incluso las startups que avanzan a toda velocidad acumulan código heredado en uno o dos años: código creado bajo presión de plazos por alguien que ya ha cambiado de equipo, para resolver un problema que nunca se documentó en ningún lugar.
Si lo único que sabes hacer es escribir código nuevo, estarás limitado a proyectos nuevos. Pero si realmente puedes leer código antiguo, de la misma manera que un detective analiza una escena del crimen, te conviertes en la persona a quien recurren los equipos de ingeniería cuando hay algo grave que necesita solución. Esa es una ventaja profesional que rara vez se discute abiertamente.
La mentalidad del arqueólogo
Antes de abordar técnicas específicas, hay un cambio de perspectiva que facilita todo lo demás.
Supón que el código tuvo sentido para alguien, en algún momento.
Ese simple cambio de enfoque es más importante que cualquier técnica de esta lista. Al ver código enredado y confuso, es fácil concluir que quien lo escribió no sabía lo que hacía. Casi nunca esa es la realidad. Con mucha más frecuencia, había una fecha límite inminente, una restricción que ya no se ve, un requisito del sistema que desapareció con el tiempo, o una solicitud que en 2016 parecía perfectamente razonable y simplemente nunca se volvió a considerar.
Imagínese entrar en una casa antigua y encontrar una viga de soporte en un lugar extraño y aparentemente arbitrario. Parece no tener ningún propósito, hasta que se entera de que allí solía haber un muro y que fue demolido, dejando esa viga como la única cosa que impide que el techo se derrumbe. Los conjuntos de código heredados están llenos de vigas como esa. Antes de tocar algo, su tarea es averiguar qué es lo que cada una sostiene en silencio.
Adoptar esta mentalidad te aporta dos beneficios: te mantiene humilde respecto a tus propias suposiciones y reemplaza la frustración por la curiosidad. Resulta que la curiosidad es una herramienta mucho más eficaz para depurar problemas que el fastidio.
Técnicas para analizar código heredado
1. Lee el historial de commits como un diario
El historial de Git es lo más cercano que tendrás a una verdadera máquina del tiempo. No te limites a examinar el estado actual de un archivo: sigue rastreando cómo llegó allí.
Ejecutar git log --follow en un archivo específico suele revelar una historia concreta: una función añadida a toda prisa justo antes de una demostración importante para un cliente, una corrección apresurada subida tarde en una noche de viernes, o un comentario que dice “solución temporal, eliminar después del lanzamiento en el tercer trimestre” y que ahora lleva cuatro años sin actualizarse.
Me topé por casualidad con una extraña instrucción if que trataba un caso específico relacionado con un único ID de cliente, sin ninguna explicación adjunta. Al revisar los registros de git blame encontré una nota del autor, en la que explicaba que los datos de producción de un cliente en particular contenían un error tipográfico, y que esa comprobación servía únicamente para mantener todo funcionando hasta que ese cliente corrigiera su parte. Esa solución temporal permaneció en vigor silenciosamente durante cinco años. Comprender los antecedentes cambió la forma en que el equipo finalmente la eliminó: la gestionaron con cuidado, mediante una migración de datos adecuada, en lugar de simplemente borrarla con la esperanza de que nada fallara.
2. Sigue los datos, no solo el código
El código te muestra lo que *podría* suceder. Los datos te muestran lo que *realmente* sucedió.
Vaya a consultar la base de datos directamente. Observe las filas reales. Si una tabla tiene una columna status con valores como 1, 2, 7 y 99, no intente deducir su significado únicamente leyendo el código; obtenga los registros reales para cada valor y trace qué ocurrió con ellos en la práctica.
Considere un campo type en una antigua tabla de pedidos donde la base de código solo tenía en cuenta valores del 1 al 5, pero los datos de producción mostraban miles de filas con type = 0. Resultó que 0 significaba “creado antes de que existiera siquiera el campo type”: un fragmento de la historia del sistema que había desaparecido del código actual pero seguía allí, claramente visible, en los datos.
3. Hable con los fantasmas (también conocidos como las personas que todavía están por ahí)
Incluso cuando la persona que desarrolló una parte del sistema ya no está, por lo general alguien cercano conserva un fragmento de contexto: quien lo incorporó originalmente, el ingeniero de soporte que ha atendido durante años tickets relacionados, o el gerente de producto que aún recuerda “el incidente”.
Haga preguntas específicas y sin presión en lugar de preguntas generales. “¿Qué hace este código?” invita a conjeturas porque es demasiado vago. “¿Recuerda algo inusual acerca de cómo el sistema de facturación manejaba los reembolsos alrededor del 2021?” es lo suficientemente específico como para evocar un recuerdo real.
4. Cree un mapa antes de tocar nada
Los arqueólogos nunca comienzan a cavar al azar: primero mapean el sitio. Aplique la misma disciplina a un código base.
Dibuje, aunque sea de forma aproximada en papel o en un documento, el camino real que siguen los datos al moverse por el sistema: qué componentes activan a otros, qué partes almacenan información y en qué se basan unos componentes sobre otros. No busca un diagrama impecable; solo necesita una idea suficiente para evitar que sigan ocurriendo errores.
Un truco útil: elija una acción real del mundo actual, como “un usuario cancela su suscripción”, y siga su proceso de principio a fin, anotando cada archivo y función por los que pasa. Seguir este hilo conductor suele revelar la mayor parte de lo que necesita saber sobre el sistema en su conjunto.
5. Escriba pruebas antes de refactorizar
Si un código base no cuenta con pruebas en absoluto, lo cual es común en los sistemas heredados, resista la tentación de arreglarlo todo de una sola vez. En su lugar, escriba lo que se conoce como pruebas de caracterización: pruebas que simplemente capturan lo que el código hace actualmente, independientemente de si ese comportamiento es correcto o no.
Esto le brinda dos cosas al mismo tiempo: una red de seguridad para cualquier cambio futuro y claridad forzada, ya que no puede describir con precisión el comportamiento actual a menos que realmente lo entienda. Gran parte del valor aquí proviene del acto de escribir las pruebas, no solo de ejecutarlas.
6. Cambie una cosa a la vez
Una vez que finalmente entienda un sistema complicado, la tentación es reescribirlo todo en una sola solicitud de pull triunfal y exhaustiva. No lo haga.
Los sistemas heredados suelen ser más frágiles de lo que parecen, precisamente porque ninguna sola persona conoce en su totalidad todas las dependencias. Realizar cambios pequeños y reversibles, uno por uno, verificando cada uno antes de pasar al siguiente, es la forma de evitar convertirse en ese próximo commit desconcertante que algún arqueólogo del futuro tendrá que resolver.
7. Documente lo que descubra: para la próxima persona
Todo lo que logre encontrar y comprender merece ser preservado. Escríbalo en algún lugar. Incluso un documento breve, informal e incompleto titulado algo como “Cómo funciona realmente el servicio de facturación heredado” es un regalo para quien herede este código después de usted; y esa persona podría muy bien ser usted mismo, dentro de seis meses, habiendo olvidado todo.
Una historia breve: La función que nadie entendió
En una empresa, una función en particular había ganado el apodo de “la bestia”: 900 líneas ocultas profundamente en el flujo de pago a las que nadie quería tocar. Durante la inducción, se advertía a los nuevos empleados sobre ella, en parte como broma y en parte como una verdadera historia de advertencia, similar a los folclores locales.
Finalmente, alguien decidió analizarla adecuadamente en lugar de evitarla. En vez de intentar reescribirla, rastrearon las transacciones reales a medida que pasaban por esa función, mapearon cada rama lógica y crearon pruebas de caracterización para cubrir cada camino posible. Todo el proceso tomó aproximadamente una semana.
Lo que descubrieron no fue desorden: resultó ser un sistema bastante coherente para manejar cinco proveedores de pago diferentes, cada uno con sus propias peculiaridades y casos límite. Había sido creado por alguien que resolvía problemas reales y concretos bajo restricciones reales, sin la posibilidad de volver después a arreglar todo. Una vez que se mapeó por completo, la función dejó de ser aterradora. Seguía siendo compleja, pero ahora era una complejidad que alguien realmente comprendía.
Eso, en esencia, es toda la disciplina: convertir el miedo en un mapa.
Pensamientos finales
No hay nada glamuroso en la arqueología de software. Nadie incluye “habilidad para descifrar códigos confusos de otros” como habilidad destacada en su currículum. Sin embargo, sigue siendo una de las capacidades más útiles y al mismo tiempo más descuidadas en la ingeniería de software.
El código antiguo no debería descartarse como basura: es un registro de decisiones tomadas bajo presiones y limitaciones que quizás nunca conozcas por completo. Abórdalo con curiosidad en lugar de juicio, y así lograrás realizar cambios más seguros, al tiempo que probablemente descubrirás que el proceso es mucho más gratificante de lo que esperabas.
Así que la próxima vez que algún archivo te haga querer cerrar la tapa del portátil y salir afuera, detente un segundo y respira. Lo que ves no es ruina; es un sitio arqueológico listo para ser comprendido.
Toma un pincel, no una topadora.
Lecturas relacionadas
- Nueve hábitos a nivel de código que hacen más fácil confiar en el trabajo de los ingenieros senior — Examina nueve prácticas concretas de programación, desde cláusulas de protección hasta un modelado estricto de datos, que hacen que el código sea más resistente, legible y fácil de depurar bajo presión.
- Diez hábitos recurrentes de JavaScript que dañan silenciosamente su código — Explica diez errores comunes en JavaScript y TypeScript, desde la igualdad laxa hasta la mutación de estado, y muestra patrones más seguros para reemplazar cada uno.