Naves espaciales en el motor de combustión interna. Sobrevive a la batalla con deudas técnicas



¿Cómo sobrevivir a la batalla con la deuda técnica? ¿Qué hacer si tienes un legado de una etapa severa? En el artículo, usando el ejemplo de tres casos, propongo descubrir cómo construir el proceso de trabajar con deuda técnica y qué enfoques de ingeniería usar para esto.

Mi nombre es Denis, soy el líder del equipo de backend en Wrike. Soy responsable de la entrega en mi equipo y del crecimiento de los desarrolladores en varios equipos. Dio la casualidad de que casi toda mi experiencia está trabajando en fintech. He trabajado para dos grandes bancos, y ahora trabajo en Wrike.

En los bancos, aprende a trabajar con sistemas donde la confiabilidad y la tolerancia a fallas son importantes. Y hay mucho Legado allí, y experimenté todo esto yo mismo como desarrollador y ayudé a otros a experimentarme como equipos líderes.

Wrike es una solución de colaboración en equipo SaaS que vendemos a nuestros clientes. Hacemos Wrike usando Wrike para organizar el desarrollo.

Wrike desarrolla treinta equipos de scrum. El servicio está disponible 24/7, por lo que las decisiones que tomamos deben ser confiables. Si algo sale mal, esto afectará el trabajo de casi todos los equipos.

¿Por qué naves espaciales?


Starship es una metáfora que uso para describir con qué estamos trabajando los programadores. La aplicación comienza con varios módulos. Luego comienza a crecer: aparece una gran carga, aparecen microservicios, comunicación entre ellos, integraciones, API externas, clientes. Resulta un ecosistema grande y conectado. Cuanto más grande y antigua es la aplicación, más conectado se vuelve el ecosistema. Y lo más importante es mantener sus nodos significativos actualizados y en un estado confiable. Se vuelve muy triste cuando los más importantes no cumplen con los requisitos de hoy o fallan.
Tu nave no se lanzará a la urdimbre si funciona en AI-95. El sistema no navegará a través de cuatro galaxias si la computadora central es Intel Celeron.

¿Qué es la deuda técnica?


Imagine que tiene una función en la que aparecen errores constantemente. Un probador viene a usted y le dice: "Esta semana encontramos cuatro nuevos errores allí". ¿Es una deuda técnica o no? ¿Y si la presencia de esta función bloquea otras historias que deben hacerse? ¿Y si es difícil trabajar con la solución y la refactoriza cada vez? Si los usuarios se quejan de una función? ¿Y si no cumple con los requisitos que se le imponen hoy o si ofende los sentimientos de los desarrolladores que luchan por la excelencia?

En el artículo, por deuda técnica, entenderé aquellas características, soluciones o enfoques que interfieren con el desarrollo posterior del producto. Todos los problemas descritos anteriormente son el resultado de una deuda técnica. Estas son razones específicas: por qué es él y por qué necesita trabajar con él.

Algoritmo Técnico de Deuda


Para trabajar eficazmente con deudas técnicas, debe hacer tres preguntas básicas.

  1. ¿Para qué? ¿Por qué nos comprometemos a hacer algo con él? ¿Por qué necesitamos este trabajo? ¿Por qué gastar dinero de la empresa, horas de desarrollo, tiempo de ingenieros? Debe haber una comprensión clara de qué beneficio obtendremos al resolver un problema en particular. De la definición queda claro que la deuda técnica bloquea el desarrollo del producto. Trabajando con él, obtendremos nuevas características y capacidades.
  2. ¿Qué? Debemos entender claramente dónde comienza la deuda técnica, dónde termina, cómo se ve y cuánto trabajo habrá que hacer para eliminarla.
  3. ¿Cómo? Acciones que deben hacerse con deuda técnica para deshacerse de ella.

Para responder la última pregunta, hay un algoritmo iterativo simple de cuatro pasos. El primer paso es resaltar la deuda técnica y comprender sus límites. El siguiente paso es separarlo de todo lo demás: encapsular, ingresar un contrato de trabajo entre la solución que ha asignado y el resto del sistema. Después de eso, puede crear una nueva solución cercana, reemplazarla y, por lo tanto, parte de la deuda técnica abandonará la aplicación. Repitiendo esta iteración varias veces, obtendrá una solución preparada.



Ahora veamos cómo funciona el algoritmo en realidad usando varios casos como ejemplo.

Primer caso


Hay una aplicación que funciona con pedidos de clientes: sistema de gestión de pedidos. La aplicación se escribió hace mucho tiempo, en 2010, y se basa en las últimas tecnologías de la época. La aplicación ha estado funcionando con éxito en producción durante los últimos 9 años, pero hoy la empresa entiende que es necesario capturar nuevos mercados y desarrollar aún más el sistema. Al mismo tiempo, es importante guardar datos y aumentar la nueva funcionalidad en el sistema.



Resulta que hay tecnologías que llevan mucho tiempo muertas, pero también hay datos que no se pueden perder. No todas las características se pueden implementar en una aplicación que utiliza tecnologías antiguas. Por lo tanto, la situación, para ser sincero, se ve así:



El problema aquí no está en los marcos antiguos, sino en la situación que tenemos: la aplicación no es compatible, es casi imposible encontrar desarrolladores para marcos de hace una década. Tenemos que hacer algo al respecto.

Ejecutemos el algoritmo. Se pueden distinguir varias partes de la deuda técnica y abordar este proceso de forma iterativa. Primero, tratemos con Frontend. Podemos lanzar el nuevo Frontend usando el viejo Backend. Podremos expandir el nuevo Frontend, adaptarlo a las tecnologías modernas, cumplirá nuestros objetivos. Podemos confiar completamente en el viejo Backend, o tendremos que modificarlo un poco para que funcione con el nuevo Frontend. El siguiente paso es la encapsulación. Con la encapsulación, la arquitectura nos ayuda aquí. El punto de encapsulación en este caso será un contrato con Backend. Después de lanzar el nuevo Frontend, podemos eliminar la parte anterior de Frontend. Ahora toda nuestra aplicación se volverá más y más ecológica.



El siguiente paso es trabajar con Backend. Aquí, el punto de encapsulación ya será la capa de la base de datos. Resulta que la arquitectura volverá a hacer esta encapsulación por nosotros. Y podemos hacer una nueva solución cercana, trabajando con los mismos datos, y transferirle Frontend. Ahora hemos abandonado por completo la solución anterior y podemos tirarla a la basura. Esto nos permite lograr el objetivo que nos propusimos para este proyecto.



Segundo caso


Toma el caso más complicado. Hay una aplicación, tiene una característica específica que se encarga de guardar los pares de divisas en la base de datos. Por ejemplo, rublo-dólar, dólar-yen, etc. La información se almacena en una base de datos en una tabla. Y para hacerlo un poco más divertido, agregue un par de dependencias: hay un consumidor que recibe datos directamente de la base de datos y un proveedor de datos que puede suministrarlos, nuevamente, directamente a la base de datos.



No estamos contentos con el formato de datos y la forma en que estos datos ingresan a la base de datos. Pero debe solucionarlo con cuidado, hay muchas dependencias.

Para hacer esto, seleccione una pieza específica - datos. Necesitas encapsularlos. Para hacer esto, ingrese la capa intermedia. Su tarea es asegurarse de que el consumidor y el proveedor no noten ningún cambio. Este es el significado de la encapsulación. Ahora podemos cambiar la estructura de almacenamiento, porque esto no afectará las dependencias externas. Después podemos construir una nueva solución que registre datos en un nuevo formato. Y el último paso es transferir los datos antiguos a un nuevo formato y obtener lo que queríamos de nuestro proyecto: datos en un nuevo formato, y la lógica anterior se puede eliminar de la aplicación.


En este proceso, los consumidores de datos no notarán los cambios, lo que significa que lo hicimos de manera completamente segura, manteniendo la compatibilidad con versiones anteriores. Luego, si es necesario para el proyecto, puede trabajar con el consumidor y el proveedor de datos para que también usen el nuevo formato.

Tercer caso


Para aumentar la escala y comprender cómo funciona en proyectos grandes, imagine que hay un proyecto grande, una base de código grande y algún tipo de funcionalidad clave que brota en absolutamente todos los puntos de la aplicación. Otras partes de Backend lo usan, tiene acceso a la API pública, es decir, los datos se están filtrando en alguna parte. La función se utiliza en Frontend, en sistemas externos e incluso en el apéndice va directamente desde la base de datos a la analítica. Para hacer el ejemplo más divertido, agregue una pizca de Legacy aquí. Bueno un poco.



Dos divertidos hechos heredados:

  1. Definitivamente funciona.
  2. Nadie sabe cómo funciona. Por eso legado.

Cuando se trabaja con un caso de este tipo, en el que hay muchos puntos de contacto y muchas incógnitas, vale la pena entender: con qué estamos trabajando, qué aspecto tiene esta solución y cuáles son sus capacidades. Es importante comprender cómo la solución que queremos reelaborar o desechar interactúa con el resto de la aplicación. Necesitamos encontrar todos los puntos en común: entender cómo funcionan, comprender sus contratos para poder ofrecer algo más.

Aquí hay varios enfoques que pueden ayudar, especialmente si la escala del desastre es lo suficientemente grande en la cantidad de código:

  • . Java , , , . , , , , ;
  • . , , , . , , . , ;
  • -.

Si encontramos puntos en común en el código, puede usarlos y envolver la solución con puntos de encapsulación. Introducimos nuevos contratos para la interacción de la aplicación con nuestra función.



Aquí se redujo la cantidad de legado, porque en este mismo momento ya estamos comenzando a ingresar lo que queremos en la aplicación.

A continuación, debe dar el primer paso hacia una nueva solución: las pruebas. Cerrarán el hecho muy divertido sobre Legacy No. 2, que mencioné anteriormente. Las pruebas mostrarán cómo funciona su solución. Además de verificar el flujo de claves, debe asegurarse de que también caiga exactamente donde lo espera de él y exactamente como lo espera de él.

A menudo sucede que una solución creada una vez para fines comerciales se usó al 100%, y hoy se cruza con los objetivos actuales solo al 30% y al 70%, no. En la realidad de hoy, este 70% ya no es importante. Las pruebas que escribiste destacarán el 30%. Después de ejecutar la prueba recubierta, puede comprender qué código no se utiliza en absoluto, eliminarlo y reducir la conectividad y la complejidad de su solución.

Si, después de escribir las pruebas y comprender cómo funciona, comenzamos a introducir una nueva solución en el área encapsulada, suplantaremos gradualmente todo lo que no necesitamos, eliminaremos el legado y reemplazaremos la solución por una nueva que se adapte a nuestras necesidades.



La nueva solución debe ser simple, comprensible y debe resolver específicamente su problema, específicamente hoy y para objetivos inmediatos específicos. No hay necesidad de profundizar en la sobreingeniería, porque estamos cerrando el objetivo final de hoy.

Y este es el lugar donde debe detenerse, quedarse en el momento y pensar: "¿Por qué estamos haciendo esto?" En esta etapa, tendrá mucha información sobre cómo funciona la solución, qué contiene y qué no. Escribiste pruebas, marcaste el código, hiciste un diagrama y ahora entiendes un orden de magnitud más. Quizás este es el punto en el que vale la pena detenerse y comprender: ¿es posible resolver un problema mucho mayor? Y esto es lo que una vez guardó el orden del trimestre de desarrollo para nuestro proyecto, porque elegimos la dirección correcta para el desarrollo del proyecto.

¿Cómo organizar el trabajo?


Tomamos un enfoque en el que intentamos dividir la nueva solución en iteraciones. Estas son sus partes específicas que se pueden implementar en producción y que cambiarán algo en ella.

Para comprender qué es la iteración y qué conjunto de tareas contiene, tomamos prestado el concepto de Definición de Hecho de Scrum. Este es un conjunto de criterios que deben cumplirse para que una historia se considere cumplida.

Aquí uso este concepto en una forma ligeramente diferente. Por definición de hecho, entiendo la descripción de lo que cambiará en la aplicación cuando una iteración particular entre en producción.

Recordemos el primer ejemplo con el sistema de gestión de pedidos. En él, el usuario podría crear un nuevo pedido utilizando la nueva interfaz de usuario y el antiguo backend. Este es el tipo de iteración: una parte específica de la funcionalidad que podemos implementar en producción, y funcionará. O el usuario puede iniciar sesión con el nuevo modelo de derechos de acceso; esto también es un cambio cualitativo. Por lo tanto, puede describir qué aporta exactamente cada iteración en su cruzada en la lucha contra la deuda técnica.

Cuando divide la solución en iteraciones, puede haber muchos problemas. Habrá una dependencia entre ellos, obtenemos un gráfico completo. En esta columna habrá varias formas de lograr el resultado final. Puede usar la planificación inversa, una herramienta que ayudará a reducir el tiempo de trabajo. Comenzamos desde el punto final del proyecto y hacemos la pregunta: "¿Qué hay que hacer para lograr este objetivo?". Entendemos que para lograr este objetivo, debemos dar el paso anterior. Entonces, nos movemos desde el final hasta el principio y en cada paso intermedio contestamos esta pregunta, pasando la ruta crítica. Una vez que este enfoque nos salvó el trimestre de desarrollo.

Una herramienta llamada diagrama de Gantt es muy adecuada para visualizar el trabajo. Este es un diagrama que muestra las relaciones entre las tareas, su duración y el aspecto visual del proyecto. En la imagen hay una captura de pantalla de Wrike. Tenemos esta herramienta, la estamos usando activamente para trabajar con proyectos.



Si está trabajando en una solución extensa, puede encontrarse con una situación en la que alguien cambia el código que refactoriza, adapta, guía en su proceso y que acaba de pensar. Estos cambios pueden dificultarle lidiar con deudas técnicas.

Puede protegerse de tales cambios de varias maneras:

  • — . , , - , .
  • , . git hook, git commit git push. unit test, , - , , , : .

También puede configurar un sistema de monitoreo externo para su código. En Wrike usamos PMD. El sistema se inicia y verifica el cumplimiento de ciertas reglas en cada nueva línea de código. La imagen es un ejemplo del registro de compilación. Aquí se violó la regla “los resultados del método público deben ser inmutables”, PMD habla sobre ello y muestra en qué línea, aquí es el método del “método incorrecto”. A continuación hay una pista de lo que debe hacer para solucionarlo. Por lo tanto, siempre sabemos dónde se viola la regla y cómo solucionarla.



Cómo superar al jefe final


Una cosa importante de la que no hemos hablado es el jefe final, que tiene que pasar mientras trabaja con deudas técnicas. Empresa, propietario del producto, cliente: todos tienen un nombre diferente. Puede interferir con el avance de nuestras iniciativas de ingeniería hacia la producción.

Todos conocemos casos en los que el propietario del producto no impulsó las soluciones más óptimas desde el punto de vista de la ingeniería. Pero incluso si el propietario de su producto mira la hoja de titanio, aún puede negociar con él. Al trabajar con deudas técnicas, abre nuevas oportunidades y el propietario del producto las necesita para desarrollar su producto.

Puede acordar una cuota de tiempo. Por ejemplo, el 10% del tiempo que los desarrolladores dedicarán a trabajar en deuda técnica. Tal cuota no permitirá deshacerse de la deuda técnica, pero permitirá que no se hinche.

Obviamente, es difícil hablar de deuda técnica, si no está claro de qué se trata exactamente la conversación. Por lo tanto, su equipo debe tener una acumulación técnica, en la que hay tareas que los desarrolladores evalúan y priorizan.

Sin embargo, las herramientas anteriores no permitirán tratar proyectos a gran escala. Y en este caso, es importante, en términos de lo que se ha enumerado anteriormente (3 preguntas sobre la deuda técnica, el proyecto en el rastreador, la ruta crítica en el gráfico, etc.), poder explicar los beneficios del proyecto a su cliente. Y cuanto más elaborada sea la historia, más comprensión le transmitirá. Una empresa tiene objetivos que le ayudan a alcanzar. Es importante que se mantenga al tanto de lo que está sucediendo no solo ahora, sino también de los planes que se harán realidad en trimestres. Por lo tanto, comuníquese con el negocio, comprenda lo que quiere hacer y cuáles son las formas de lograr estos objetivos, y use este conocimiento para combinar sus objetivos de ingeniería y objetivos comerciales.

Quizás los objetivos no coincidan de inmediato, sino en un trimestre o incluso en un año. Pero esto le permitirá comprender cuándo el negocio estará listo para el cambio.

Si logra sincronizar los objetivos, recibirá todas las bonificaciones: priorización, recursos y organización de todo el trabajo. El propietario del producto lo ayudará a hacer esto. La tarea principal es estar de acuerdo con él.

Total


Cuando hablamos de cambios en áreas clave del producto, un algoritmo simple de cuatro pasos se complica:



se agregan dos pasos más. El primero es comprender con qué estamos trabajando. El segundo, después de la encapsulación, es comprender cómo funciona la solución y cómo evitar cambios en su código.

Usando este algoritmo y mis recomendaciones para organizar el proceso, puede trabajar con cualquier deuda técnica.

El artículo se basa en mi discurso en la reunión, puede ver el informe en video .

All Articles