Métodos para tratar con código heredado usando GitLab como ejemplo

Puede engañar sin cesar si GitLab es un buen producto. Es mejor mirar los números: de acuerdo con los resultados de la ronda de inversión, la valoración de GitLab fue de $ 2.7 mil millones, mientras que la valuación anterior fue de $ 1.100 millones. Esto significa un rápido crecimiento y la compañía contratará más y más desarrolladores front-end.

Esta es la historia de la aparición de la interfaz en GitLab.



Este es un gráfico del número de proveedores de front-end en GitLab, comenzando en 2016, cuando no estaban allí, y terminando en 2019, cuando ya había unas pocas docenas de ellos. Pero el propio GitLab ha existido durante 7 años. Entonces, hasta 2017, el código principal en la interfaz fue escrito por desarrolladores de back-end, peor aún, los desarrolladores de back-end en Ruby on Rails (en ningún caso queremos ofender a nadie y explicar a continuación de qué estamos hablando).

Durante 7 años, cualquier proyecto, le guste o no, se está volviendo obsoleto. En algún momento, la refactorización se vuelve imposible de posponer. Y comienza el viaje, lleno de aventuras, cuyo punto final nunca llega. Sobre cómo sucede esto en GitLab, dijo Ilya Klimov.


Sobre el orador: Ilya Klimov (xanf) ingeniero frontend senior en GitLab. Antes de eso, trabajó en startups y outsourcing, dirigió una pequeña empresa de outsourcing. Luego me di cuenta de que todavía no había tenido tiempo de resolver el producto y vine a GitLab.

El artículo se basa en un informe de Ilya en FrontendConf , por lo tanto, no estructura tanto la información como refleja la experiencia del orador. Puede parecer demasiado conversacional, pero no menos interesante desde el punto de vista del trabajo con legado.

En GitLab, como en muchos otros proyectos, están migrando gradualmente de tecnologías antiguas a algo más relevante:

  • CoffeeScript en JavaScript. Los desarrolladores de Ruby on Rails, cuando comenzaron el proyecto, no pudieron evitar prestar atención a CoffeeScript, que se parece mucho a Ruby.
  • JQuery Vue. , JQuery. GitLab SPA. server-side rendering progressive enhancement, Vue-. , : Vue-, .
  • Karma Jest. Jest - . Karma , , .
  • REST GraphQL , , Vuex Apollo. Vuex, Redux Vue, Apollo local state , . GraphQL , .

Al mismo tiempo, los reemplazos tienen lugar en varias direcciones a la vez, en el proyecto hay simultáneamente un código heredado en diferentes etapas.

Ahora imagine que viene a un proyecto que está en medio de todas estas migraciones. El estándar y el punto de referencia para mí fue la situación cuando abres la edición de un proyecto, presionas el botón Guardar, ¿y qué crees que vendrá? Si pensabas que somos tan viejos fagos que llega HTML, entonces no. JavaScript viene en que necesita "evolucionar" para mostrar una ventana emergente. Este es el punto inferior de mi imagen heredada.

Más arriba: clases autoescritas en JQuery, componentes Vue y, como punto más alto, nuevas características modernas escritas con Vuex, Apollo, Jest, etc.

Así es como se ve mi gráfico de contribución en GitLab.



En él, y esto es muy importante para comprender la esencia de la historia y todos mis dolores, se pueden distinguir varios segmentos:

  • Incorporación en la zona de abril. "Luna de miel" cuando recién comencé a trabajar en GitLab. En este momento, los principiantes reciben tareas más fáciles.
  • Desde finales de abril hasta mediados de mayo solo hay unos pocos compromisos, un período de negación : "¡No, no puede ser que todo se haga de esa manera!" Traté de entender dónde no entiendo algo, por lo que hay muy pocas confirmaciones.
  • La segunda mitad de mayo es enojo : "Me importa un comino todo: necesito mover la producción, compartir características, intentar hacer algo al respecto".
  • El comienzo de junio (cero compromisos) es una depresión . No fueron vacaciones, vi y comprendí que me estaban cayendo las manos, no sé qué hacer con ellas.
  • Después de eso, estuve de acuerdo conmigo mismo , decidí que me contrataron como profesional y puedo mejorar GitLab. En junio y julio, ofrecí una gran cantidad de mejoras. No todos resonaron por razones de las que hablaremos.
  • Ahora estoy en la etapa de adopción y entiendo claramente: cómo, dónde, por qué y qué hacer con todo esto.

Te contaré con más detalle lo que hice de agosto a octubre. Honestamente, en una pequeña empresa de outsourcing o en una startup, me habrían despedido cinco veces con tal productividad en estos tres meses.

Entonces, en tres meses hice:

  • Control segmentado: tres botones.
  • La cadena de búsqueda que almacena el historial local es un componente un poco más complejo.
  • Hilandero. Y este componente aún no está congelado.



A continuación, paso a paso, analizaremos por qué sucedió esto y cómo vivir con ello. Si le parece que estoy exagerando, aquí hay una captura de pantalla de algunas de las tareas que me corresponden en GitLab (puede mirar directamente a GitLab, está abierto).



Ver: perdido 12.1, perdido 12.2, perdido 12.3. Sprint dura un mes y control segmentado: 3 sprints. Spinner aún no está, él será nuestro personaje principal.

El problema de la refactorización y la filosofía de la refactorización han enfrentado a la humanidad durante mucho tiempo, durante milenios. Ahora demostraré:
« ; , , ; ; , .

, , , : ».

La Biblia nos dice cómo combinar funcionalidad antigua y nueva. La segunda parte de la cita es valiosa desde el punto de vista de la administración: no importa cómo salga con las iniciativas, encontrará una gran resistencia.

En la fase de depresión, vi muchos informes sobre la refactorización de grandes proyectos, pero alrededor del 70% de ellos me recordaron una broma.

Charla javista:
- ¿Cómo aceleramos nuestra aplicación Java?
- ¡Oh, así que tuve un informe al respecto! Quieres contar
- ¡A decir y puedo, aceleraría!

Si todavía decide embarcarse en el camino peligroso y tembloroso de la refactorización, tengo algunas recetas simples que he desarrollado para mí y que funcionan en condiciones cercanas a la realidad.

1. Aislamiento


Para acelerar las cosas, mejorar, refactorizar, debes cortar el elefante en filetes, es decir, dividir la tarea en partes. GitLab es muy grande, tenemos un canal Slack "¿Es esto conocido?", Donde la gente hace preguntas como "¿Es esto un error o una característica, quién puede explicarlo?" - Y la respuesta no siempre se encuentra.

Un ejemplo simple: capturas de pantalla del mismo lugar en GitLab, tomadas con la diferencia de un día.



Estaba muy molesto, porque estaba trabajando en este botón, y todo esto es un problema u otro con el botón.

¿Que pasó? Es simple: desarrollamos un sistema de diseño y, como parte de una herramienta de libro de cuentos separada para probar un sistema de diseño, deshabilitamos el CSS Global GitLab para verificar cómo los componentes CSS están aislados del CSS global.

Resumiendo: CSS ya no es guardaral menos en GitLab.

He estado trabajando con JavaScript durante 14 años y nunca he visto un proyecto que al menos uno o dos años conserve CSS totalmente administrado. Por cierto, HTML tampoco se puede guardar (en GitLab seguro).

GitLab ha sido desarrollado durante mucho tiempo y backendov. Tomaron una decisión controvertida de usar Bootstrap porque Bootstrap ofrecía un sistema de diseño amigable con el backend.

Pero, ¿qué es Bootstrap en términos de filosofía de aislamiento de componentes? Se trata de unas 600-700 clases globales (de hecho, cada clase CSS es global) que impregnan toda la aplicación. En términos de manejabilidad, nada bueno saldrá de ello.

La siguiente acción (no lo llamemos un error): GitLab tomó Vue.js. La elección fue razonable, debido a los tres marcos, es Vue el que le permite reescribir algo de la manera más fluida. No necesita tirar inmediatamente y cortar una aplicación de página única grande, pero puede reescribir nodos pequeños individuales. Ahora se puede hacer en Angular, pero hace 3-4 años, cuando apareció Angular 2, no podía coexistir en una página en más de una instancia. Ahora también es posible reaccionar, pero toda esta magia con la falta de un paso de construcción y así sucesivamente inclinó la balanza hacia Vue.

Como resultado, un mal se combinó con el segundo. Esto es malo, porque los estilos Bootstrap no saben nada sobre el sistema de componentes, y los componentes Vue se escribieron al principio, de todos modos. Por lo tanto, se tomó una decisión decidida de crear su propio sistema de diseño. Lo tenemos llamadoPijamas , pero nadie podía explicarme por qué.

Veo que ahora hay más y más de nuestros propios sistemas de diseño, y esto es bueno.

El sistema de diseño implica aislamiento, pero dado que GitLab ya se escribió en Bootstrap, aproximadamente el 50-60% de nuestro sistema de diseño es un envoltorio sobre los componentes de Bootstrap / Vue con una disminución en su funcionalidad. Esto es necesario para que el sistema de diseño no le permita utilizar el componente incorrectamente. Si hablamos de una biblioteca abstracta, entonces la flexibilidad es importante, por ejemplo, la capacidad de hacer cualquier botón que desee. Si en GitLab los hilanderos pueden tener cuatro tamaños aprobados por los diseñadores, entonces físicamente no debe permitir que otros lo hagan.

Algún día, el bien ganará, y tendremos una herramienta importante con la que, por supuesto, si obtuviste puntaje en el soporte para IE y Edge, puedes refactorizar efectivamente los proyectos front-end: este es Shadow DOM . Shadow DOM resuelve el problema de los estilos globales que fluyen hacia los componentes. No tome Polymer, que incluso Google ya ha enterrado. Utilice lit-element y lit-HTML, y puede crear estilos aislados utilizando su marco favorito.

Puede decir que React tiene módulos CSS, Vue tiene estilos de alcance que hacen lo mismo. Tenga mucho cuidado con ellos: los módulos CSS no proporcionan un aislamiento del 100% porque solo funcionan con clases. Y con los estilos de ámbito en Vue, se puede realizar un escenario muy interesante cuando los estilos del componente superior caen en el elemento raíz del padre, y allí se utilizan atributos de datos que se ralentizan.

A pesar de que regañé a Angular durante tres años, ahora tengo que admitir que en este momento está implementado de la mejor manera. En Angular, para garantizar un buen aislamiento de estilo, es suficiente simplemente cambiar el modo de aislamiento y, si es necesario, usar Shadow DOM, de lo contrario, la emulación normal.

De vuelta a la ruleta. De los tres meses que peleé con él, durante algún tiempo estuve involucrado en un negocio emocionante: la limpieza.



Una clase loading-containeres un detalle de implementación de un spinner, es decir, es una clase dentro de una implementación de spinner. Decidimos, ya que CSS no se debe guardar, en pijamas para crear CSS separado basado en Atomic CSS. Personalmente no me gusta mucho el concepto Atomic CSS, pero tenemos lo que tenemos.

Es decir, me ocupé de limpiar estilos en el código del producto principal que se colgaron en elementos que son detalles de implementación. Todo parece muy simple, porque, por supuesto, hay pruebas en GitLab.

2. Pruebas


Las pruebas en GitLab cubren todo el código , brindan confiabilidad. Y así, la tubería se completa en 98 minutos.



GitLab recolecta el 40% del tiempo de los corredores públicos en GitLab.com porque GitLab recolecta tuberías para cada solicitud de fusión.

Me inspiré mucho: finalmente llegué a un proyecto donde todo está cubierto en pruebas. La cobertura del código de back-end es cercana al 100%, y el código de front-end en el momento de mi llegada estaba cubierto por el 89.3%.

Desafortunadamente, resultó que la mayor parte de esta cobertura es basura porque:

  • se desglosa cuando se realizan cambios que no están relacionados con los componentes;
  • No se rompe cuando se realizan cambios.

Lo explicaré con ejemplos. Tomamos a Jest porque pensamos que nos permitiría en ciertas situaciones no escribir afirmaciones, sino usar instantáneas. El problema es que si no configuró Jest y no agregó el serializador correcto, entonces Vue Test Utils simplemente genera accesorios en HTML. Luego, por ejemplo, resulta props con el nombre de usuario, que tenía props en los parámetros con los datos del nombre, a los que se pasó el objeto objeto. Cualquier cambio en el formato de los datos transmitidos no conduce a una falla de la instantánea.

Los desarrolladores de Ruby están acostumbrados a hacer pruebas, en términos generales, que cubren todos los métodos.
Cuando hacemos pruebas para componentes Vue o React, necesitamos probar cómo se comporta la API pública.
Por lo tanto, tuvimos grandes pruebas sobre propiedades calculadas que no se usaron en algunos escenarios, pero en otros era físicamente imposible alcanzar el estado cuando se llamaría a este cálculo. Un agradecimiento especial a Vue, en el que las plantillas son cadenas, por lo que no puede calcular la cobertura de prueba de la plantilla. En Vue 3, aparecerán los mapas de origen y la capacidad de solucionarlo, pero no será pronto.

Afortunadamente, hay una habilidad simple que te permitirá refactorizar efectivamente el legado. Esta es la capacidad de escribir lo que se llama la prueba de fijación en el mundo de las grandes pruebas.

Prueba de fijación


Esta es una prueba que intenta capturar el comportamiento que está refactorizando. Tenga en cuenta que la prueba de fijación probablemente no terminará comprometida con el repositorio. Es decir, usted, a través de todo tipo de refinamientos, por ejemplo, utilizando el entorno de ensayo, escriba usted mismo una prueba que describa cómo se representa su componente. Después de refactorizar, la prueba de fijación debería generar el mismo HTML, y es muy probable que sea una buena señal, o debe comprender qué cambios se han producido.

Daré un ejemplo de la vida. Hace unos meses, realicé una revisión de solicitud de fusión refactorizando una lista desplegable. El contexto heredado es el siguiente: antes, para separar las ramas de un amigo entre sí mediante un guión en la lista desplegable, simplemente se pasó la cadena de texto "divisor". Por lo tanto, si su rama se llamó divisor, entonces no tiene suerte. En el proceso de refactorización, una persona intercambió dos clases en un nodo HTML, esto entró en producción y lo arruinó. Para ser justos, por supuesto, no del todo de producción, sino de puesta en escena, pero no obstante.

Como resultado, cuando comenzamos a escribir tales pruebas, descubrimos que, a pesar del indicador de cobertura de prueba, las pruebas se escribieron incorrectamente. Porque, en primer lugar, teníamos pruebas de Karma, es decir, antiguas. En segundo lugar, casi todas las pruebas hicieron suposiciones sobre los componentes internos del componente. Es decir, pretendieron ser pruebas unitarias, y trabajaron esencialmente de principio a fin, verificando que se mostrara una etiqueta específica con una clase específica, en lugar de verificar que un componente específico se renderizara con accesorios específicos. Comprende la diferencia: ¿las clases son componentes?

Como resultado, mis 18 solicitudes de fusión con pruebas de refactorización para un total de 8-9 mil líneas, el registro de cambios total resultó ser de aproximadamente 20 mil, porque se cortaron 11 mil.



Al mismo tiempo, formalmente, volví a trabajar todas estas pruebas por una sola cosa: eliminar las afirmaciones con respecto a las clases de spinner y, en su lugar, verificar que el spinner con los accesorios correctos se muestra allí.

A primera vista, este es un trabajo ingrato. Pero reescribir las pruebas para la arquitectura correcta fue bastante fácil de vender al negocio. GitLab es un producto comercialmente rentable. Por supuesto, si le dice al gerente de producto que necesita tres iteraciones para reescribir 20 pruebas, adivine a dónde será enviado. Otra cosa: “Necesito tres iteraciones para reescribir la prueba. Esto nos permitirá introducir hilanderos de manera más eficiente y acelerar la implementación futura de nuevos elementos del sistema de diseño ". Y aquí llegamos a lo importante.

3. resistencia


Hay otra funcionalidad que más de mis hiladores esperan en el sistema de diseño GitLab: estos son iconos SVG comunes.


Tenemos iconos dibujados por el diseñador que se utilizan en el proyecto principal, pero no están en el sistema de diseño, porque GitLab tiene una infancia difícil. Por ejemplo, en 2019 CSS se recopila no a través de Webpack, sino por una pieza llamada Sprockets: esta es la tubería Ruby, porque necesitamos reutilizar el mismo CSS en el back-end y la interfaz. Debido a esto, los iconos deben estar conectados a diferentes proyectos de diferentes maneras. Por lo tanto, alguien refactorizó la base del código principal durante tres meses para que pudiera conectar los íconos del sistema de diseño a proyectos relacionados.

Aquí hay un punto importante que inevitablemente encontrarás. La refactorización es un proceso de mejora continua. Pero tarde o temprano tienes que parar.
Es absolutamente normal detenerse, no completar la refactorización, sino obtener mejoras concretas mensurables.
Pero si está trabajando en un proyecto heredado, inevitablemente se encontrará con personas que lo hacen.

Esto significa que escriben a la antigua usanza porque están muy acostumbrados. Por ejemplo, nuestros patrocinadores dicen: "No quiero enseñarles a ustedes su broma. He escrito pruebas para Karma durante tres años, necesito agregar una nueva funcionalidad, y dado que no tomarán la funcionalidad sin pruebas, aquí hay una prueba para Karma ".

Su tarea es resistir esto tanto como sea posible. Esto es relativamente fácil de combatir, pero hay un pecado aún mayor que eso. A veces, en el proceso de refactorización, se encuentra con un problema y, por lo general, se desea ir a un lado.

Es decir, sustituir una nueva muleta simplemente porque, por ciertas razones, no es posible poner fin a la refactorización. Por ejemplo, si tenemos problemas para integrar iconos en la base del código principal, podemos dejar una clase de utilidad que se extraerá del CSS de la aplicación global. Formalmente, la tarea comercial se resolverá, pero en la práctica, como en la historia de la hidra de Lernean: había 8 errores, 4 corregidos, 13 restantes.

Refactorización, como reparar una casa: es imposible terminarla, solo se puede detener.
El primer 80% de la refactorización toma el 20% del tiempo, el 80% restante de la refactorización (así como así) toma otro 80% del tiempo.
Es importante no introducir nuevos hacks durante el proceso de refactorización. Créeme, durante el proceso de desarrollo, ellos mismos aparecerán.

4. herramientas


Afortunadamente, incluso antes de llegar, GitLab se embarcó en el camino correcto de introducir buenas herramientas: Prettier, Vue Test Utils, Jest. Aunque Prettier se implementó torcidamente.

Explicaré lo que está en juego. Mientras descubrí qué y por qué tan históricamente, el 80% de mis búsquedas se encontraron con un compromiso de 37 mil líneas de código de prettify. Era casi imposible usar el historial, y tuve que configurar el complemento para VS Code para que excluyera este commit al buscar el historial de cambios.

Claro, las herramientas son importantes, pero debe elegirlas con cuidado. Por ejemplo, tenemos Vue, y Vue tiene una buena herramienta de prueba: Vue Test Utils. Pero si Vue 2 se lanzó hace 2-3 años, entonces Vue Test Utils aún no ha salido de la versión beta. Además, de acuerdo con información privilegiada, en este momento el único desarrollador de Vue Test Utils no escribe en Vue.

En el proceso de elegir herramientas, juegas a la suerte y realmente intentas ganar.

GitLab tuvo una lesión infantil con CoffeeScript. Es por eso que es imposible impulsar incluso la idea teórica de escribir en TypeScript en GitLab. Todo se descompone en un argumento simple: ¿no será lo mismo que con CoffeeScript cuando el lenguaje que se compila en JavaScript haya muerto?
Al elegir herramientas, intente que la herramienta pueda reemplazarse o, en casos extremos, mantenerse de forma independiente.
En GitLab usamos una cosa genial llamada Danger.

Esta es una captura de pantalla real de su sitio web en 2019. Pero, los colegas dijeron que, de hecho, en 2019 el sitio puede parecer cualquier cosa.

Danger es un bot que ocupa un estado intermedio entre el linter en su CI y las pautas escritas. Este bot se puede expandir y vendrá para extraer la solicitud o, como se llama correctamente con nosotros, fusionar la solicitud y dejar comentarios como:
  • "Hay un comentario de desactivación de ESlint en este archivo, corríjalo".
  • “Este archivo solía ser gobernado por esta persona. Quizás necesites poner un comentario al respecto.

En mi opinión, este es un marco muy bueno, importante y extensible para monitorear el estado de la base del código.

5. Abstracción


Comenzaré con un ejemplo. Hace unos meses, vi la noticia: “GitHub se deshizo de jQuery. Hemos recorrido un largo camino difícil y ya no estamos usando jQuery ". Naturalmente, pensé que también necesitamos deshacernos de jQuery en GitLab.



Una búsqueda rápida mostró que jQuery se usa en 300 archivos. Parece aterrador, pero nada: los ojos tienen miedo, las manos están haciendo. ¡Pero no! jQuery es un pegamento integral en la base de código de GitLab, porque tenemos Bootstrap.

Bootstrap fue originalmente escrito en jQuery. Esto significa que si necesita, por ejemplo, capturar el evento de apertura desplegable en Bootstrap, este es un evento jQuery. No puedes interceptarlo de forma nativa.

Esto es lo primero que debe hacer al trabajar con código heredado. Si tiene jQuery que no puede tirar, escriba su propio Emisor de eventos, que se ocultará dentro del trabajo con eventos jQuery.

Cuando llegue un futuro brillante, podemos eliminar jQuery, pero por ahora, lo siento, debes concentrar el govnokod. En un proyecto heredado normal, se distribuye uniformemente en todo el código. Recójalo en cuellos de botella marcados con las banderas "No entre sin un traje de protección química".

6. Métricas


No puedes hacer algo cuyo resultado no pueda medirse. En GitLab, medimos todo lo que hacemos para saber objetivamente que el código funciona mejor.



Por ejemplo, tenemos un cronograma de migración de pruebas de Karma (azul) a Jest (verde):

ve que hay un progreso gradual. Y tenemos muchos horarios así. Pero es importante entender que no siempre todo termina bien.

Daré un ejemplo más (la demostración en el informe comienza a partir de este momento).



Aquí está la interfaz de solicitud de fusión habitual en la producción de GitLab. Obviamente, podemos colapsar archivos, hacer clic en el encabezado y el archivo comenzará a colapsarse.

¿Qué crees, cuánto tiempo lleva colapsar un archivo de 50 líneas, mientras que la máquina con la Core i7 de octava generación está torcida para obtener el máximo rendimiento? ¿Cuánto tiempo lleva la implementación?

El tiempo que tarda el archivo en colapsarse varía de 7 a 15 segundos. El despliegue ocurre instantáneamente. Antes de refactorizar, ambos trabajaron igualmente rápido.

Por eso es muy importante tener métricas.

Te diré lo que está pasando aquí. Este es Vue, su sistema de reactividad realiza un seguimiento del valor: si cambia, se llaman todas las dependencias que dependen de este valor. Cada línea es un componente Vue, que consta de muchas cosas, porque puede poner comentarios en una línea, los comentarios se pueden cargar dinámicamente desde el servidor, etc. Todo esto está suscrito a la tienda Vue, que también es un componente Vue.

Cuando cierra la solicitud de fusión, todas, digamos, 20 mil suscripciones de tienda deben actualizarse cuando se actualiza la tienda. Si se elimina la fila, debe eliminarse de las dependencias. Y luego matemática simple: debe mirar una matriz de 20 mil elementos para encontrar aquellos que deben eliminarse. Digamos que hay 500 de esas líneas, y cada línea tiene varios componentes. El resultado es una operación matemática O (N), es decir, O (20,000) * 500. JavaScript ha estado ejecutándose todo este tiempo.

La implementación ocurre instantáneamente, porque agregar una dependencia es solo un empujón a la matriz, es decir operación matemática O (1).

A veces, mejorar la calidad del código degrada el rendimiento y otras métricas. Es muy importante medirlos.

En resumen: aísle el código incorrecto y realice un seguimiento de las métricas.

legacy — . , – TechLead Conf — , . – , legacy Python, PHP.

, ++, , FrontendConf. .

All Articles