Métodos para lidar com código legado usando o GitLab como exemplo

Você pode enganar sem parar se o GitLab é um bom produto. É melhor observar os números: de acordo com os resultados da rodada de investimentos, a avaliação do GitLab foi de US $ 2,7 bilhões, enquanto a avaliação anterior foi de US $ 1,1 bilhão. Isso significa crescimento rápido e a empresa contratará cada vez mais desenvolvedores front-end.

Esta é a história da aparência do frontend no GitLab.



Este é um gráfico do número de fornecedores front-end no GitLab, começando em 2016, quando eles não estavam lá e terminando em 2019, quando já havia algumas dezenas deles. Mas o GitLab em si existe há 7 anos. Portanto, até 2017, o código principal do front-end foi escrito pelos desenvolvedores de back-end, pior, os desenvolvedores de back-end no Ruby on Rails (em nenhum caso, queremos ofender alguém e explicar abaixo do que estamos falando).

Por 7 anos, qualquer projeto, goste ou não, está se tornando obsoleto. Em algum momento, a refatoração se torna impossível de adiar. E a jornada começa, cheia de aventura, cujo ponto final nunca chega. Sobre como isso acontece no GitLab, disse Ilya Klimov.


Sobre o orador: Ilya Klimov (xanf) engenheiro front-end sênior do GitLab. Antes disso, ele trabalhou em startups e terceirização, liderou uma pequena empresa de terceirização. Então percebi que ainda não tinha tido tempo de elaborar o produto e vim para o GitLab.

O artigo é baseado em um relatório de Ilya no FrontendConf , portanto, não estrutura tanto as informações como reflete a experiência do palestrante. Pode parecer excessivamente conversacional, mas não menos interessante do ponto de vista de trabalhar com legado.

No GitLab, como em muitos outros projetos, eles estão migrando gradualmente das tecnologias antigas para algo mais relevante:

  • CoffeeScript em JavaScript. Os desenvolvedores do Ruby on Rails, quando começaram o projeto, não podiam deixar de prestar atenção no CoffeeScript, que é muito semelhante ao 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 , .

Ao mesmo tempo, as substituições ocorrem em várias direções ao mesmo tempo; no projeto, há simultaneamente um código legado em diferentes estágios.

Agora imagine que você está chegando a um projeto que está no meio de todas essas migrações. O ponto padrão e de referência para mim foi a situação em que você abre a edição de um projeto, pressiona o botão Salvar - e o que você acha que está por vir? Se você pensou que somos tão velhos que o HTML vem, então não. O JavaScript aparece e você precisa "evoluir" para exibir uma janela pop-up. Este é o ponto mais baixo da minha imagem herdada.

Mais adiante: aulas auto-escritas em jQuery, componentes Vue e, como ponto mais alto, novos recursos modernos escritos com Vuex, Apollo, Jest, etc.

É assim que fica meu gráfico de contribuições no GitLab.



Nele - e isso é muito importante para entender a essência da história e todas as minhas dores - vários segmentos podem ser distinguidos:

  • Onboarding na área de abril. “Lua de mel” quando comecei a trabalhar no GitLab. Neste momento, os iniciantes recebem tarefas mais fáceis.
  • Desde o final de abril até meados de maio, há apenas alguns commits - um período de negação : "Não, não pode ser que tudo seja feito dessa maneira!" Tentei entender onde não entendo algo, então há tão poucos commits.
  • A segunda quinzena de maio é de raiva : "Não dou a mínima para tudo - preciso mudar a produção, compartilhar recursos, tentar fazer algo a respeito".
  • O início de junho (zero commit) é uma depressão . Não era férias, vi e entendi que minhas mãos estavam caindo, não sei o que fazer com isso.
  • Depois disso, concordei comigo mesmo , decidi que fui contratado como profissional e posso melhorar o GitLab. Em junho e julho, ofereci um grande número de melhorias. Nem todos eles ressoaram por razões sobre as quais falaremos.
  • Agora estou no estágio de adoção e entendo claramente: como, onde, por que e o que fazer com tudo isso.

Vou contar com mais detalhes o que fiz de agosto a outubro. Honestamente, em uma pequena empresa de terceirização ou em uma startup, eu teria sido demitido cinco vezes com tanta produtividade nesses três meses.

Então, em três meses eu fiz:

  • Controle segmentado - três botões.
  • A cadeia de pesquisa que armazena o histórico local é um componente um pouco mais complexo.
  • Spinner. E esse componente ainda não está congelado.



A seguir, passo a passo, analisaremos por que isso aconteceu e como viver com isso. Se parece que estou exagerando, aqui está uma captura de tela de algumas das tarefas que dependem de mim no GitLab (você pode olhar diretamente no GitLab, ele está aberto).



Veja: perdeu 12.1, perdeu 12.2, perdeu 12.3. O sprint dura um mês e o controle segmentado - 3 sprints. Spinner ainda se foi, ele será nosso personagem principal.

O problema da refatoração e a filosofia da refatoração estão enfrentando a humanidade há muito tempo - há milênios. Agora vou provar:
« ; , , ; ; , .

, , , : ».

A Bíblia nos diz como combinar funcionalidades antigas e novas. A segunda parte da citação é valiosa do ponto de vista da gerência: não importa como você saia com as iniciativas, você encontrará uma grande resistência.

Na fase de depressão, assisti a muitos relatórios sobre refatoração de grandes projetos, mas cerca de 70% deles me lembraram uma piada.

Javist talk:
- Como aceleramos nosso aplicativo Java?
- Ah, então eu tinha um relatório sobre isso! Você quer contar?
- Para contar e eu posso, eu aceleraria!

Se você ainda decide seguir o caminho perigoso e instável da refatoração, tenho algumas receitas simples que desenvolvi para mim e que funcionam em condições próximas da realidade.

1. Isolação


Para acelerar as coisas, melhorar, refatorar, você precisa cortar o elefante em bifes, ou seja, dividir a tarefa em partes. O GitLab é muito grande, temos um canal do Slack "Isso é conhecido", onde as pessoas fazem perguntas como "Isso é um bug ou um recurso, quem pode explicar?" - e a resposta nem sempre é encontrada.

Um exemplo simples: capturas de tela do mesmo local no GitLab, tiradas com a diferença de um dia.



Fiquei muito chateado, porque estava trabalhando neste botão, e isso é tudo de uma maneira ou de outra com o botão.

O que aconteceu? É simples: desenvolvemos um sistema de design e, como parte de uma ferramenta de livro de histórias separada para testar um sistema de design, desabilitamos o CSS GitLab global para verificar como os componentes CSS são isolados do CSS global.

Resumindo: CSS não é mais salvopelo menos no GitLab.

Trabalho com JavaScript há 14 anos e nunca vi um projeto com pelo menos um ou dois anos de duração preservando CSS totalmente gerenciado. A propósito, o HTML também não pode ser salvo (no GitLab, com certeza).

GitLab foi desenvolvido por um longo tempo e back-end. Eles tomaram uma decisão controversa de usar o Bootstrap porque o Bootstrap oferecia um sistema de layout amigável ao back-end.

Mas o que é o Bootstrap em termos de filosofia de isolamento de componentes? São cerca de 600-700 classes globais (de fato, cada classe CSS é global) que permeiam todo o aplicativo. Em termos de gerenciabilidade, nada de bom resultará disso.

A próxima ação (não vamos chamá-lo de erro) - o GitLab aceitou o Vue.js. A escolha foi razoável, por causa das três estruturas, é o Vue que permite reescrever algo com mais facilidade. Você não precisa jogar e cortar imediatamente um Aplicativo de Página Única grande, mas pode reescrever nós pequenos individuais. Agora, isso pode ser feito no Angular, mas há 3-4 anos atrás, quando o Angular 2 apareceu, ele não pôde coexistir em uma página em mais de uma instância. A reação também é possível agora, mas toda essa mágica com a falta de uma etapa de construção e assim por diante inclinou a balança em direção a Vue.

Como resultado, um mal foi combinado com o segundo. Isso é ruim, porque os estilos do Bootstrap não sabem nada sobre o sistema de componentes e os componentes do Vue foram escritos no início, de qualquer maneira. Portanto, uma forte decisão foi tomada para criar seu próprio sistema de design. Nós chamamosPijama , mas ninguém poderia me explicar o porquê.

Vejo que agora existem cada vez mais nossos próprios sistemas de design, e isso é bom.

O sistema de design envolve isolamento, mas desde que o GitLab já foi escrito no Bootstrap, aproximadamente 50-60% do nosso sistema de design é um invólucro sobre os componentes do Bootstrap / Vue com uma diminuição em sua funcionalidade. Isso é necessário para que o sistema de design não permita o uso incorreto do componente. Se falamos de uma biblioteca abstrata, a flexibilidade é importante por exemplo, a capacidade de criar qualquer botão que você desejar. Se os spinners do GitLab podem ter quatro tamanhos aprovados pelos designers, você não deve deixar fisicamente que outros o façam.

Algum dia, o bom vencerá e teremos uma ferramenta importante com a qual, é claro, se você obteve suporte no IE e Edge, poderá refatorar efetivamente os projetos de front-end - este é o Shadow DOM . O Shadow DOM resolve o problema dos estilos globais que entram nos componentes. Por favor, não tome Polymer, que até o Google já enterrou. Use lit-element e lit-HTML, e você pode criar estilos isolados usando sua estrutura favorita.

Você pode dizer que o React possui módulos CSS, o Vue tem estilos de escopo que fazem o mesmo. Tenha muito cuidado com eles: os módulos CSS não fornecem 100% de isolamento porque eles funcionam apenas com classes. E com os estilos com escopo definido no Vue, um cenário muito interessante pode ser realizado quando os estilos do componente superior se enquadram no elemento raiz do pai e os atributos de dados são usados ​​lá que diminuem a velocidade.

Apesar de ter repreendido a Angular por três anos, agora tenho que admitir que no momento ela é implementada da melhor maneira possível. No Angular, para garantir um bom isolamento de estilo, basta mudar o modo de isolamento e, se necessário, usar o Shadow DOM, caso contrário a emulação normal.

Voltar ao botão rotativo. Dos três meses em que lutei com ele, durante algum tempo estive envolvido em um negócio emocionante: limpeza.



Uma classe loading-containeré um detalhe de implementação de um girador, ou seja, é uma classe dentro de uma implementação de girador. Decidimos, já que o CSS não deve ser salvo, no Pyjamas, para criar CSS separado com base no Atomic CSS. Eu pessoalmente não gosto muito do conceito Atomic CSS, mas temos o que temos.

Ou seja, eu estava envolvido na limpeza de estilos no código do produto principal pendurado em elementos que são detalhes de implementação. Tudo parece muito simples - porque, é claro, existem testes no GitLab.

2. Testes


Os testes no GitLab cobrem todo o código , fornecem confiabilidade. E assim o pipeline é concluído em 98 minutos.



O GitLab coleta 40% do tempo dos corredores públicos no GitLab.com porque o GitLab coleta pipelines para cada solicitação de mesclagem.

Fiquei muito inspirado: finalmente entrei em um projeto onde tudo é coberto em testes! A cobertura do código de back-end é próxima de 100%, e o código de front-end no momento da minha chegada era coberto por 89,3%.

Infelizmente, a maior parte dessa cobertura é lixo porque:

  • quebra quando são feitas alterações que não estão relacionadas aos componentes;
  • Não quebra quando são feitas alterações.

Vou explicar com exemplos. Pegamos Jest porque pensamos que ele nos permitiria, em certas situações, não escrever afirmações, mas usar instantâneos. O problema é que, se você não configurou o Jest e adicionou o serializador correto, o Vue Test Utils simplesmente gera adereços em HTML. Então, por exemplo, resultam adereços com o nome user, que tinha adereços nos parâmetros com os dados do nome, para os quais o objeto objeto foi passado. Quaisquer alterações no formato dos dados transmitidos não levam à falha do instantâneo.

Os desenvolvedores Ruby estão acostumados a fazer testes, grosso modo, abrangendo todos os métodos.
Quando fazemos testes para os componentes Vue ou React, precisamos testar como a API pública se comporta.
Assim, tivemos grandes testes de propriedades calculadas que não foram usadas em alguns cenários, mas em outros era fisicamente impossível atingir o estado em que esse cálculo seria chamado. Agradecimentos especiais ao Vue, em que os modelos são cadeias de caracteres, para que você não possa calcular a cobertura de teste do modelo. No Vue 3, os Mapas de Origem aparecerão e a capacidade de corrigi-lo, mas não será em breve.

Felizmente, existe uma habilidade simples que permitirá refatorar efetivamente o legado. Essa é a capacidade de escrever o que é chamado de teste de fixação no mundo dos grandes testes.

Teste de fixação


Este é um teste que tenta capturar o comportamento que você está refatorando. Observe que o teste de fixação provavelmente não acabará sendo confirmado no repositório. Ou seja, você, através de todos os tipos de refinamentos, por exemplo, usando o ambiente de temporariedade, escreve para si mesmo um teste que descreve como seu componente é renderizado. Após a refatoração, o teste de fixação deve gerar o mesmo HTML, e isso provavelmente é um bom sinal, ou você deve entender quais alterações ocorreram.

Vou dar um exemplo da vida. Alguns meses atrás, conduzi uma revisão de solicitação de mesclagem com a refatoração de uma lista suspensa. O contexto legado é o seguinte: anteriormente, para separar os ramos de um amigo um do outro por um traço na lista suspensa, a cadeia de texto "divisor" era simplesmente passada. Portanto, se seu ramo foi chamado de divisor, você estará sem sorte. No processo de refatoração, uma pessoa trocou duas classes em um nó HTML, isso entrou em produção e o arruinou. Na justiça, é claro, não exatamente na produção, mas na encenação, mas mesmo assim.

Como resultado, quando começamos a escrever esses testes, descobrimos que, apesar do indicador de cobertura legal, os testes foram gravados incorretamente. Porque, primeiro, fizemos testes para o Karma, ou seja, antigos. Em segundo lugar, quase todos os testes fizeram suposições sobre as partes internas do componente. Ou seja, eles fingiram ser testes de unidade e funcionaram essencialmente como de ponta a ponta, verificando se uma tag específica com uma classe específica estava sendo renderizada, em vez de verificar se um componente específico estava sendo renderizado com acessórios específicos. Entenda a diferença: classes são componentes?

Como resultado, minhas 18 solicitações de mesclagem com testes de refatoração para um total de 8 a 9 mil linhas, o changelog total acabou por ser de cerca de 20 mil, porque 11 mil foram cortados.



Ao mesmo tempo, formalmente, refiz todos esses testes por uma coisa: remover afirmações sobre as classes de spinners e, em vez disso, verificar se o spinner com os adereços corretos é processado lá.

À primeira vista, este é um trabalho ingrato. Mas reescrever testes para a arquitetura certa era muito fácil de vender para os negócios. O GitLab é um produto comercialmente lucrativo. Obviamente, se você informar ao gerente do produto que precisa de três iterações para reescrever 20 testes, adivinhe para onde será enviado. Outra coisa: “Preciso de três iterações para reescrever o teste. Isso nos permitirá introduzir spinners com mais eficiência e acelerar a implementação futura de novos elementos do sistema de design. ” E aqui chegamos ao importante.

3. Resistência


Há outra funcionalidade que muitos dos meus spinners aguardam no sistema de design GitLab - esses são ícones SVG comuns.


Temos ícones desenhados pelo designer que são usados ​​no projeto principal, mas eles não estão no sistema de design, porque o GitLab tem uma infância difícil. Por exemplo, em 2019, o CSS é coletado não pelo Webpack, mas por uma peça chamada Sprockets - esse é o pipeline do Ruby, porque precisamos reutilizar o mesmo CSS no backend e no frontend. Por esse motivo, os ícones devem estar conectados a diferentes projetos de maneiras diferentes. Portanto, alguém refatorou a base de código principal por três meses para que você pudesse conectar os ícones do sistema de design a projetos relacionados.

Há um ponto importante aqui que você encontrará inevitavelmente. A refatoração é um processo de melhoria contínua. Mas mais cedo ou mais tarde você tem que parar.
É absolutamente normal parar, não concluindo a refatoração, mas obtendo melhorias mensuráveis ​​concretas.
Mas se você estiver trabalhando em um projeto herdado, inevitavelmente encontrará pessoas que fazem isso.

Isso significa que eles escrevem da maneira antiga, porque estão muito acostumados. Por exemplo, nossos defensores dizem: "Eu não quero ensinar esse seu gracejo. Escrevi testes para o Karma por três anos, preciso adicionar novas funcionalidades e, como eles não serão aceitos sem testes, aqui está um teste para o Karma.

Sua tarefa é resistir a isso o máximo possível. Isso é relativamente fácil de combater, mas há um pecado ainda maior do que isso. Às vezes, no processo de refatoração, você se depara com um problema e geralmente há um desejo de ir para o lado.

Ou seja, substituir uma nova muleta simplesmente porque, por certas razões, não é possível levar a refatoração até o fim. Por exemplo, se tivermos problemas para integrar ícones na base de código principal, podemos deixar uma classe de utilitário que será extraída do CSS global do aplicativo. Formalmente, a tarefa comercial será resolvida, mas na prática, como na história da hidra de Lernean: restavam 8 bugs, 4 corrigidos e 13 restantes.

Refatorar, como reparar uma casa - é impossível terminar, você só pode pará-lo.
Os primeiros 80% da refatoração levam 20% do tempo, os 80% restantes da refatoração (exatamente assim) levam outros 80% do tempo.
É importante não introduzir novos hacks durante o processo de refatoração. Acredite, durante o processo de desenvolvimento, eles próprios aparecerão.

4. Ferramentas


Felizmente, mesmo antes de eu chegar, o GitLab embarcou no caminho correto da introdução de boas ferramentas: Prettier, Vue Test Utils, Jest. Embora Prettier tenha implementado torto.

Vou explicar o que está em jogo. Enquanto eu descobri o que e por que tão historicamente, 80% das minhas pesquisas se depararam com um commit de 37 mil linhas de código de prettificação. Era quase impossível usar o histórico, e eu tive que configurar o plug-in para o VS Code, de modo a excluir esse commit ao procurar o histórico de alterações.

Claro, as ferramentas são importantes, mas você precisa escolhê-las com cuidado. Por exemplo, temos o Vue, e o Vue tem uma boa ferramenta de teste - o Vue Test Utils. Mas se o Vue 2 foi lançado há 2-3 anos, o Vue Test Utils ainda não saiu da versão beta. Além disso, de acordo com informações privilegiadas, no momento o único desenvolvedor do Vue Test Utils não escreve no Vue.

No processo de escolha das ferramentas, você joga o sorteio e realmente tenta vencer.

O GitLab sofreu uma lesão na infância com o CoffeeScript. É por isso que é impossível empurrar até a idéia teórica de escrever no TypeScript no GitLab. Tudo se divide em um argumento simples: não será o mesmo que no CoffeeScript quando a linguagem que compila o JavaScript tiver morrido.
Ao escolher ferramentas, tente para que a ferramenta possa ser substituída ou, em casos extremos, mantida de forma independente.
Nós do GitLab usamos uma coisa legal chamada Danger.

Esta é uma captura de tela real do site em 2019. Mas, colegas disseram que, em 2019, o site pode parecer qualquer coisa.

O Danger é um bot que ocupa um estado intermediário entre o linter no seu IC e as diretrizes escritas. Este bot pode ser expandido e virá para puxar solicitação ou, como são chamados corretamente conosco, mesclar solicitação e deixar comentários como:
  • "Há um comentário de desativação do ESlint neste arquivo, corrija-o."
  • “Esse arquivo costumava ser governado por essa pessoa. Talvez você precise fazer uma revisão.

Na minha opinião, essa é uma estrutura muito boa, importante e extensível para monitorar o estado da base de código.

5. Abstração


Vou começar com um exemplo. Alguns meses atrás, vi a notícia: “O GitHub se livrou do jQuery. Percorremos um longo caminho difícil e não usamos mais o jQuery. ” Naturalmente, pensei que também precisamos nos livrar do jQuery no GitLab.



Uma pesquisa rápida mostrou que o jQuery é usado em 300 arquivos. Parece assustador, mas nada - os olhos estão com medo, as mãos estão fazendo. Mas não! O jQuery é uma cola integral na base de código do GitLab, porque temos o Bootstrap.

O Bootstrap foi originalmente escrito em jQuery. Isso significa que, se você precisar, por exemplo, capturar o evento de abertura suspensa no Bootstrap, esse é um evento jQuery. Você não pode interceptá-lo nativamente.

Essa é a primeira coisa que você deve abstrair ao trabalhar com código legado. Se você tiver o jQuery que não pode ser descartado, escreva seu próprio Emissor de Eventos, que ficará oculto dentro do trabalho com os eventos do jQuery.

Quando um futuro brilhante chegar, podemos remover o jQuery, mas, por enquanto, desculpe, você precisa concentrar o govnokod. Em um projeto legado regular, ele é distribuído igualmente por todo o código. Colete-o em gargalos marcados com as bandeiras “Não entre sem traje de proteção química”.

6. Métricas


Você não pode fazer algo cujo resultado não possa ser medido. No GitLab, medimos tudo o que fazemos para saber objetivamente que o código está funcionando melhor.



Por exemplo, temos uma programação de migração dos testes de Karma (azul) para Jest (verde):

você vê que há um progresso gradual. E nós temos muitos desses horários. Mas é importante entender que nem sempre tudo termina bem.

Vou dar mais um exemplo (a demonstração no relatório começa a partir deste momento).



Aqui está a interface de solicitação de mesclagem usual na produção do GitLab. Obviamente, podemos recolher arquivos, clicar no cabeçalho e o arquivo começará a recolher.

O que você acha, quanto tempo leva para recolher um arquivo de 50 linhas, enquanto a máquina com a oitava geração Core i7 é torcida para obter o máximo desempenho? Quanto tempo leva a implantação?

O tempo necessário para o recolhimento do arquivo varia de 7 a 15 segundos. A implantação ocorre instantaneamente. Antes da refatoração, ambos trabalhavam igualmente rápido.

É por isso que é muito importante ter métricas.

Vou lhe contar o que está acontecendo aqui. Este é o Vue, seu sistema de reatividade controla o valor: se ele mudar, todas as dependências que dependem desse valor serão chamadas. Cada linha é um componente do Vue que consiste em várias coisas, porque você pode colocar comentários em uma linha, os comentários podem ser carregados dinamicamente no servidor etc. Tudo isso é assinado na loja Vue, que também é um componente do Vue.

Quando você fecha a solicitação de mesclagem, todas as, digamos, 20 mil assinaturas de loja precisam ser atualizadas quando a loja é atualizada. Se a linha for excluída, ela deverá ser removida das dependências. E depois a matemática simples: você precisa examinar uma matriz de 20 mil elementos para encontrar aqueles que precisam ser removidos. Digamos que existem 500 dessas linhas, e cada linha possui vários componentes. O resultado é uma operação matemática O (N), ou seja, O (20.000) * 500. O JavaScript está sendo executado todo esse tempo.

A implantação acontece instantaneamente, porque adicionar uma dependência é apenas um empurrão para a matriz, ou seja, operação matemática O (1).

Às vezes, melhorar a qualidade do código prejudica o desempenho e outras métricas. É muito importante medi-los.

Em resumo: isole o código incorreto e acompanhe as métricas.

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

, ++, , FrontendConf. .

All Articles