El cliente pregunta: "Si añadir esta funcionalidad en una aplicación nueva llevaría una semana, ¿por qué aquí se necesitan tres?"
Es una muy buena pregunta.
Y a menudo la respuesta no es: "porque los programadores trabajan más despacio".
El problema puede estar mucho más profundo: en la arquitectura del sistema, sus dependencias, la forma de almacenar los datos, la falta de pruebas, decisiones históricas y cambios sucesivos añadidos durante años.
Precisamente por eso el coste del desarrollo de software no es fijo.
La misma funcionalidad puede costar una cantidad completamente distinta en dos sistemas diferentes.
El código no se valora solo por el número de funciones
A primera vista, la tarea puede parecer trivial.
"Añadamos la posibilidad de exportar datos a Excel."
O: "Añadamos un nuevo rol de usuario."
O: "Conectemos el sistema con nuestro CRM."
El problema es que una funcionalidad nunca existe completamente aislada del resto del sistema.
Una nueva funcionalidad puede requerir cambios en:
- la base de datos,
- la API,
- el backend,
- el frontend,
- el sistema de permisos,
- el inicio de sesión,
- la generación de informes,
- las integraciones,
- las pruebas,
- los mecanismos de caché,
- la documentación,
- el proceso de despliegue.
Cuanto más interconectado está el sistema, más elementos hay que analizar antes de hacer un cambio.
El mayor coste puede surgir antes de escribir la primera línea de código
En un sistema maduro, el programador no debería simplemente empezar a escribir.
Primero hay que responder:
- ¿Dónde debería añadirse esta funcionalidad?
- ¿Con qué módulos se comunicará?
- ¿Qué datos utiliza?
- ¿Los mecanismos de permisos existentes la cubren?
- ¿El cambio afectará a otros procesos?
- ¿Qué pruebas hay que actualizar?
- ¿La arquitectura actual permite siquiera hacerlo correctamente?
Todo eso forma parte del coste de desarrollo de la funcionalidad.
Por eso, en un sistema antiguo, una parte importante del trabajo puede no ser la programación en sí, sino reconocer las dependencias y limitaciones de la solución existente.
La deuda técnica funciona como intereses
Una buena manera de entender la deuda técnica es justamente el coste de los cambios sucesivos.
Si una solución se hizo deprisa en su momento, puede haber estado totalmente justificada.
El problema aparece cuando esa solución temporal se convierte en un elemento permanente del sistema.
Aparece una nueva funcionalidad.
Luego otra.
Surge una excepción.
Luego otra excepción.
A eso se suma una integración, un workaround, un proceso manual y una regla adicional.
Después de unos años, nadie recuerda ya por qué el sistema funciona precisamente así.
Pero cada cambio sucesivo tiene que tener en cuenta todas esas decisiones históricas.
Martin Fowler describe la deuda técnica como el esfuerzo adicional asumido al cambiar un sistema debido a problemas con su calidad interna.
Así que podemos decir: la deuda técnica no tiene por qué detener el desarrollo de inmediato. Primero hace que cada cambio sucesivo sea más caro.
Señal uno: "de paso hay que corregir otras cinco cosas"
Es uno de los síntomas más característicos.
El cliente pide una funcionalidad.
Durante el análisis resulta que, para implementarla, hay que:
- corregir la estructura de la tabla,
- cambiar la forma de autorización,
- actualizar la biblioteca,
- arreglar una API antigua,
- reescribir un fragmento del frontend.
De repente, una pequeña funcionalidad deja de ser pequeña. No porque el requisito sea complicado. Sino porque el sistema ya no tiene las fronteras arquitectónicas adecuadas.
Señal dos: un cambio requiere probar todo el sistema
Si una pequeña modificación exige una regresión manual completa, la organización está pagando por la falta de automatización.
A medida que el sistema crece, el número de combinaciones posibles aumenta.
Sin un conjunto adecuado de pruebas, cada vez es más difícil tener la certeza de que la nueva funcionalidad no ha roto la anterior.
Eso, a su vez, genera cautela.
Los despliegues son menos frecuentes.
Los cambios son más grandes.
El riesgo aumenta.
Y los despliegues más grandes son más difíciles de diagnosticar en caso de problemas.
Se crea un círculo vicioso.
Señal tres: "mejor no tocar ese módulo"
Esa frase debería encender una luz de advertencia.
Si un módulo concreto se ha convertido en una zona que el equipo evita porque su comportamiento es impredecible, el sistema tiene un problema importante de mantenimiento.
Peor aún si solo una persona conoce su funcionamiento. Entonces la empresa no solo tiene deuda técnica. También tiene riesgo de conocimiento.
La salida de un empleado puede significar la pérdida del conocimiento necesario para seguir desarrollando el sistema con seguridad.
Señal cuatro: cada funcionalidad requiere excepciones
Un sistema bien diseñado debería tener reglas previsibles.
Si cada nueva funcionalidad exige añadir una excepción especial, una condición adicional o una ruta individual, es probable que la arquitectura esté empezando a frenar el desarrollo.
Eso a menudo conduce a un código que ya no se puede prever fácilmente.
Y la falta de previsibilidad significa un mayor coste de análisis, pruebas y mantenimiento.
¿Hay que reescribirlo todo?
No.
Y aquí llegamos a una distinción muy importante.
La deuda técnica no implica automáticamente la necesidad de un rewrite.
Las posibles soluciones incluyen:
Refactorización
Es decir, mejorar la estructura del código existente sin cambiar su comportamiento de negocio.
Es una buena dirección cuando el sistema sigue teniendo una arquitectura razonable, pero ciertos fragmentos son difíciles de mantener.
Modernización de componentes seleccionados
No hace falta sustituir toda la aplicación.
Se puede empezar por el módulo, la integración o la capa que más problemas da.
Migración gradual
Los elementos nuevos pueden funcionar junto al sistema antiguo, y las áreas siguientes se van trasladando sucesivamente.
Este enfoque permite limitar el riesgo de una migración única. En la literatura sobre modernización de legacy, a menudo se utiliza precisamente la extracción gradual de funcionalidades y la sustitución de partes sucesivas del sistema.
Rewrite
Construir un sistema nuevo tiene sentido cuando la arquitectura actual es tan limitante que seguir modernizándola ya no ofrece un retorno justificado.
Pero el rewrite debería ser una decisión derivada del análisis, no una reacción a la frustración del equipo.
¿Cuándo no merece la pena invertir todavía en modernización?
La deuda técnica en sí misma no es motivo para detener el desarrollo. Todo sistema tiene cierto nivel de deuda técnica. A veces pagarla no tiene sentido económico.
Si la aplicación:
- funciona de forma estable,
- es segura,
- tiene un número reducido de cambios,
- da soporte a un proceso que no va a crecer de forma significativa,
- no genera problemas operativos,
puede ser razonable dejarla en su estado actual.
No se trata de que cada sistema sea tecnológicamente perfecto.
Se trata de que el nivel de deuda sea una decisión consciente.
¿Cuándo el coste de la deuda se convierte en un problema de negocio?
Cuando empieza a afectar a los resultados de la empresa.
Por ejemplo:
Una nueva funcionalidad debía salir al mercado en un mes, pero necesita tres.
La integración con un nuevo socio se retrasa porque la API del sistema antiguo no permite manejar fácilmente nuevos datos.
Una persona clave del equipo tiene que participar en el trabajo cada vez, porque solo ella conoce el módulo antiguo.
Cada despliegue importante requiere horas de regresión.
La competencia introduce nuevas funciones más rápido, porque su plataforma permite experimentar con mayor rapidez.
En este momento, la deuda técnica deja de ser un problema del departamento de TI.
Se convierte en un problema de negocio.
¿Cómo medir si la situación está empeorando?
No hace falta crear un sistema complejo de KPI.
Conviene observar algunos indicadores sencillos:
Lead time - cuánto tiempo transcurre desde el inicio del trabajo en un cambio hasta su despliegue.
Frecuencia de despliegues - con qué frecuencia puede el equipo entregar cambios de forma segura.
Tasa de fallos de cambio - con qué frecuencia los despliegues causan problemas.
Tiempo de recuperación - con qué rapidez se puede volver a un funcionamiento estable tras una incidencia.
Tiempo de realización de una funcionalidad - si tareas similares requieren cada vez más esfuerzo.
Además, conviene analizar el número de operaciones manuales, la cobertura de pruebas, la actualidad de las dependencias y el tiempo necesario para incorporar a un nuevo programador al proyecto.
Estos datos permiten ver si el problema es realmente técnico o si puede deberse al proceso, a los requisitos o a la forma de organizar el trabajo.
La peor solución es "una corrección rápida más"
Si el equipo sabe que la arquitectura requiere cambios, pero cada vez pospone el tema, el sistema puede entrar en una espiral.
"Hagamos ahora un workaround."
"La refactorización la haremos después."
"Por ahora basta."
"En el próximo release."
El problema es que el siguiente release trae nuevos requisitos.
Y cada workaround adicional aumenta el coste del siguiente cambio.
Por eso la decisión de pagar la deuda técnica debería formar parte de la estrategia de desarrollo del producto, y no ser una reacción casual ante una crisis.
Una buena aplicación no es aquella que nunca envejece
Todo sistema cambiará.
Las tecnologías cambiarán.
Los clientes tendrán nuevas necesidades.
Aparecerán nuevas integraciones.
Cambiará la forma de trabajar de la empresa.
Por eso, el objetivo no debería ser crear una aplicación que nunca haya que modernizar. El objetivo debería ser crear una arquitectura, en la que la modernización sea posible sin detener el negocio. Es una diferencia enorme.
Porque el mejor sistema no es el que parece más moderno el día del lanzamiento. Es el que también, después de varios años, permite a la empresa reaccionar rápidamente a los cambios.
Y si cada nueva funcionalidad cuesta cada vez más, no siempre significa que la funcionalidad sea difícil.
Quizá lo que ya se ha vuelto difícil es el propio sistema.
