Naves estelares no motor de combustão interna. Sobreviver à batalha com dívidas técnicas



Como sobreviver à batalha com dívidas técnicas? O que fazer se você tiver um legado de um estágio grave? No artigo, usando o exemplo de três casos, proponho descobrir como criar o processo de trabalhar com dívida técnica e quais abordagens de engenharia usar para isso.

Meu nome é Denis, sou o líder da equipe de back-end na Wrike. Sou responsável pela entrega em minha equipe e pelo crescimento de desenvolvedores em várias equipes. Aconteceu que quase toda a minha experiência está trabalhando na fintech. Eu trabalhei para dois grandes bancos e agora trabalho no Wrike.

Nos bancos, você aprende a trabalhar com sistemas em que a confiabilidade e a tolerância a falhas são importantes. E há muito legado lá, e eu experimentei tudo isso como desenvolvedor e ajudei outros a se sentirem como equipes líderes.

Wrike é uma solução de colaboração em equipe SaaS que vendemos para nossos clientes. Fazemos o Wrike usando o Wrike para organizar o desenvolvimento.

Wrike desenvolve trinta equipes de scrum. O serviço está disponível 24/7, portanto, as decisões que tomamos devem ser confiáveis. Se algo der errado, isso afetará o trabalho de quase todas as equipes.

Por que naves estelares?


Starship é uma metáfora que eu uso para descrever com o que os programadores estão trabalhando. O aplicativo inicia com vários módulos. Então começa a crescer: uma grande carga surge, aparecem microsserviços, comunicação entre eles, integrações, APIs externas, clientes. Acontece que um ecossistema grande e conectado. Quanto maior e mais antiga a aplicação, mais conectado o ecossistema se torna. E o mais importante é manter seus nós significativos atualizados e em um estado confiável. Fica muito triste quando os mais importantes deles não atendem aos requisitos atuais ou falham.
Sua nave não entrará no warp se funcionar no AI-95. O sistema não navegará pelas quatro galáxias se o computador central for o Intel Celeron.

O que é dívida técnica?


Imagine que você tem um recurso no qual os erros aparecem constantemente. Um testador chega até você e diz: "Nesta semana, encontramos quatro novos bugs lá". É uma dívida técnica ou não? E se a presença desse recurso bloquear outras histórias que precisam ser feitas? E se for difícil trabalhar com a solução e você a refatorar toda vez? Se os usuários reclamarem de um recurso? E se ela não atender aos requisitos impostos hoje, ou ofender os sentimentos dos desenvolvedores que buscam a excelência?

No artigo, por dívida técnica, entenderei os recursos, soluções ou abordagens que interferem no desenvolvimento futuro do produto. Todos os problemas descritos acima são resultado de dívida técnica. Estas são razões específicas: por que ele é e por que você precisa trabalhar com ele.

Algoritmo Técnico da Dívida


Para trabalhar efetivamente com a dívida técnica, você precisa fazer três perguntas básicas.

  1. Pelo que? Por que nos comprometemos a fazer algo com ele? Por que precisamos desse trabalho? Por que gastar dinheiro da empresa, horas de desenvolvimento, tempo dos engenheiros? Deve haver uma compreensão clara de que benefício obteremos ao resolver um problema específico. A partir da definição, fica claro que a dívida técnica bloqueia o desenvolvimento do produto. Trabalhando com isso, obteremos novos recursos e capacidades.
  2. O que? Devemos entender claramente onde a dívida técnica começa, onde termina, como fica e quanto trabalho terá que ser feito para eliminá-la.
  3. Quão? Ações que precisam ser executadas com dívida técnica para se livrar dela.

Para responder à última pergunta, existe um algoritmo iterativo simples de quatro etapas. O primeiro passo é destacar a dívida técnica e entender seus limites. O próximo passo é separá-lo de todo o resto: encapsular, entrar em um contrato de trabalho entre a solução que você alocou e o restante do sistema. Depois disso, você pode criar uma nova solução próxima, substituí-la e, assim, parte da dívida técnica deixará o aplicativo. Repetindo essa iteração várias vezes, você obterá uma solução pronta.



Agora vamos ver como o algoritmo funciona na realidade usando vários casos como exemplo.

Primeiro caso


Existe um aplicativo que trabalha com pedidos de clientes - sistema de gerenciamento de pedidos. O aplicativo foi escrito há muito tempo, em 2010, e foi desenvolvido com as mais recentes tecnologias da época. O aplicativo opera com sucesso na produção nos últimos 9 anos, mas hoje a empresa entende que é necessário capturar novos mercados e desenvolver ainda mais o sistema. Ao mesmo tempo, é importante salvar dados e aumentar novas funcionalidades no sistema.



Acontece que existem tecnologias há muito tempo mortas, mas também há dados que não podem ser perdidos. Nem todos os recursos podem ser implementados em um aplicativo usando tecnologias antigas. Portanto, para ser absolutamente honesto, a situação se parece com isso:



O problema aqui não está nas estruturas antigas, mas na situação que temos: o aplicativo não é suportado, é quase impossível encontrar desenvolvedores para estruturas de uma década atrás. Temos que fazer algo sobre isso.

Vamos executar o algoritmo. É possível distinguir várias partes da dívida técnica e abordar iterativamente esse processo. Primeiro, vamos lidar com o Frontend. Podemos iniciar o novo Frontend usando o antigo Backend. Seremos capazes de expandir o novo Frontend, adaptá-lo às tecnologias modernas, atender aos nossos objetivos. Podemos confiar inteiramente no antigo back-end, ou ele terá que ser ligeiramente modificado para funcionar com o novo front-end. O próximo passo é o encapsulamento. Com o encapsulamento, a arquitetura nos ajuda aqui. O ponto de encapsulamento, neste caso, será um contrato com o Backend. Depois que lançamos o novo Frontend, podemos remover a parte antiga do Frontend. Agora, toda a nossa aplicação ficará mais e mais verde.



O próximo passo é trabalhar com o Backend. Aqui, o ponto de encapsulamento já será a camada do banco de dados. Acontece que a arquitetura fará novamente esse encapsulamento para nós. E podemos criar uma nova solução nas proximidades, trabalhando com os mesmos dados e transferir o Frontend para ele. Agora abandonamos completamente a solução antiga e podemos descartá-la. Isso nos permite alcançar a meta que estabelecemos para este projeto.



Segundo caso


Tome o caso mais complicado. Existe um aplicativo, que possui um recurso específico responsável por salvar pares de moedas no banco de dados. Por exemplo, rublo-dólar, dólar-iene e assim por diante. As informações são armazenadas em um banco de dados em uma tabela. E para torná-lo um pouco mais divertido, adicione algumas dependências: existe um consumidor que recebe dados diretamente do banco de dados e um provedor de dados que pode fornecê-los, novamente, diretamente ao banco de dados.



Não estamos satisfeitos com o formato dos dados e com a maneira como esses dados entram no banco de dados. Mas você precisa corrigi-lo com cuidado, existem muitas dependências.

Para fazer isso, selecione um dado de peça específico. Você precisa encapsulá-los. Para fazer isso, insira o intercalar. A tarefa deles é garantir que o consumidor e o fornecedor não notem nenhuma alteração. Este é o significado de encapsulamento. Agora podemos alterar a estrutura de armazenamento, porque isso não afetará dependências externas. Depois podemos construir uma nova solução que registra os dados em um novo formato. E o último passo é transferir os dados antigos para um novo formato e obter o que queríamos em nosso projeto: os dados em um novo formato e a lógica antiga podem ser removidos do aplicativo.


Nesse processo, os consumidores de dados não perceberão as alterações, o que significa que fizemos isso de forma totalmente segura, mantendo a compatibilidade com versões anteriores. Em seguida, se necessário para o projeto, você pode trabalhar com o consumidor e o provedor de dados para que eles também usem o novo formato.

Terceiro caso


Para aumentar a escala e entender como ele funciona em grandes projetos, imagine que exista um grande projeto, uma grande base de código e algum tipo de funcionalidade essencial que brote para absolutamente todos os pontos do aplicativo. Outras partes do back-end o utilizam, ele tem acesso à API pública, ou seja, os dados estão vazando em algum lugar. O recurso é usado no Frontend, em sistemas externos e até no apêndice, indo diretamente do banco de dados para a analítica. Para tornar o exemplo mais divertido, adicione uma pitada de Legado aqui. Bem, um pouco.



Dois fatos legados divertidos:

  1. Definitivamente funciona.
  2. Ninguém sabe como isso funciona. É por isso que Legacy.

Ao trabalhar com um caso desse tipo, no qual existem muitos pontos de contato e muitas incógnitas, vale a pena entender: com o que, de fato, estamos trabalhando, como é essa solução e quais são suas capacidades. É importante entender como a solução que queremos retrabalhar ou nos livrar de interage com o restante do aplicativo. Precisamos encontrar todos os pontos em comum: entender como eles funcionam, entender seus contratos para poder oferecer outra coisa.

Existem várias abordagens aqui que podem ajudar, especialmente se a escala do desastre for grande o suficiente na quantidade de código:

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

Se encontrarmos um ponto em comum no código, você poderá usá-los e agrupar a solução com pontos de encapsulamento. Introduzimos novos contratos para a interação do aplicativo com nosso recurso.



Aqui a quantidade de legado foi reduzida, porque neste exato momento já estamos começando a inserir o que queremos no aplicativo.

Em seguida, você precisa dar o primeiro passo em direção a uma nova solução - testes. Eles encerrarão o fato muito engraçado sobre o Legacy No. 2, que mencionei anteriormente. Os testes mostrarão como sua solução funciona. Além de verificar o fluxo de chaves, você precisa garantir que ele também caia exatamente onde você espera dele e exatamente da maneira que você espera dele.

Muitas vezes acontece que uma solução criada uma vez para fins comerciais foi usada em 100% e hoje ela se cruza com as metas atuais apenas em 30% e em 70% - não. Nas realidades de hoje, esses 70% não são mais importantes. Os testes que você escreveu destacarão os 30%. Depois de executar o teste revestido, você pode entender qual código não é usado, excluí-lo e reduzir a conectividade e a complexidade da sua solução.

Se, depois de escrevermos os testes e entendermos como funciona, começaremos a introduzir uma nova solução na área encapsulada, gradualmente substituiremos tudo o que não precisamos, removeremos o legado e substituiremos a solução por uma nova que atenda às nossas necessidades.



A nova solução deve ser simples, compreensível e deve resolver especificamente seu problema, especificamente hoje e para objetivos imediatos específicos. Não há necessidade de aprofundar a superengenharia, porque estamos fechando o objetivo final de hoje.

E este é o lugar onde você deve parar, aguardar um momento e pensar: "Por que estamos fazendo isso?" Nesta fase, você terá muitas informações sobre como a solução funciona, o que está nela e o que não está. Você escreveu testes, marcou o código, fez um diagrama e agora entende mais uma ordem de magnitude. Talvez seja este o ponto em que vale a pena parar e entender: é possível resolver um problema muito maior? E foi isso que uma vez salvou a ordem do trimestre de desenvolvimento para o nosso projeto, porque escolhemos a direção certa para o desenvolvimento do projeto.

Como organizar o trabalho


Adotamos uma abordagem na qual tentamos dividir a nova solução em iterações. Essas são as partes específicas que podem ser implementadas na produção e que mudarão algo nela.

Para entender o que é a iteração e o conjunto de tarefas que ela contém, pegamos emprestado o conceito de Definição de Concluído do Scrum. Esse é um conjunto de critérios que devem ser atendidos para que uma história seja considerada cumprida.

Aqui eu uso esse conceito de uma forma um pouco diferente. Por Definição de Concluído, entendo a descrição do que será alterado no aplicativo quando uma iteração específica entrar em produção.

Vamos lembrar o primeiro exemplo com o sistema de gerenciamento de pedidos. Nele, o usuário pode criar um novo pedido usando a nova interface do usuário e o antigo back-end. Esse é o tipo de iteração - uma parte específica da funcionalidade que podemos lançar na produção e funcionará. Ou o usuário pode efetuar login com o novo modelo de direitos de acesso - isso também é uma alteração qualitativa. Assim, você pode descrever o que exatamente cada iteração oferece em sua cruzada na luta contra a dívida técnica.

Quando você divide a solução em iterações, pode haver muitos problemas. Haverá uma dependência entre eles, temos um gráfico inteiro. Nesta coluna, haverá várias maneiras de alcançar o resultado final. Você pode usar o planejamento reverso - uma ferramenta que ajudará a reduzir o tempo de trabalho. Começamos a partir do ponto final do projeto e fazemos a pergunta: “O que precisa ser feito para alcançar esse objetivo?”. Entendemos que, para atingir esse objetivo, precisamos dar o passo anterior. Então, passamos do final para o início e, a cada passo intermediário, respondemos a essa pergunta, passando pelo caminho crítico. Uma vez que essa abordagem nos salvou no trimestre de desenvolvimento.

Uma ferramenta chamada gráfico de Gantt é adequada para visualizar o trabalho. Este é um diagrama que mostra os relacionamentos entre as tarefas, a duração e a aparência do projeto. Na imagem é uma captura de tela do Wrike. Temos essa ferramenta, estamos usando-a ativamente para trabalhar com projetos.



Se você estiver trabalhando em uma solução abrangente, poderá encontrar uma situação em que alguém altere o código que você refatorar, adaptar, orientar em seu processo e que você acabou de pensar. Essas alterações podem dificultar o tratamento de dívidas técnicas.

Você pode se proteger de tais alterações de várias maneiras:

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

Você também pode configurar um sistema de monitoramento externo para o seu código. Na Wrike, usamos PMD. O sistema inicia e verifica a conformidade com certas regras a cada nova linha de código. A imagem é um exemplo do log de compilação. Aqui a regra “resultados públicos de métodos devem ser imutáveis” foi violada, o PMD fala sobre isso e mostra em que linha - aqui está o método “método errado”. Abaixo está uma pista do que você precisa fazer para corrigi-lo. Assim, sempre sabemos onde a regra é violada e como corrigi-la.



Como passar pelo chefe final


Uma coisa importante sobre a qual não falamos é o chefe final, que precisa passar por um trabalho com dívidas técnicas. Empresa, proprietário do produto, cliente - todos têm um nome diferente. Isso pode interferir com o arrastamento de nossas iniciativas de engenharia para a produção.

Todos sabemos sobre casos em que o proprietário do produto não apresentou as melhores soluções do ponto de vista da engenharia. Mas mesmo que o proprietário do produto olhe para a chapa de titânio, você ainda poderá negociar com ele. Trabalhando com dívida técnica, você abre novas oportunidades, e o proprietário do produto precisa delas para desenvolver seu produto.

Você pode concordar com uma cota de tempo. Por exemplo, 10% das vezes os desenvolvedores se dedicam ao trabalho com dívidas técnicas. Essa cota não permitirá livrar-se da dívida técnica, mas permitirá que não aumente.

Obviamente, é difícil falar sobre dívida técnica, se não estiver claro do que exatamente se trata a conversa. Portanto, sua equipe deve ter uma lista técnica pendente, na qual existem tarefas avaliadas e priorizadas pelos desenvolvedores.

No entanto, as ferramentas acima não permitirão lidar com projetos de grande escala. E, nesse caso, é importante, em termos do que foi listado anteriormente (3 perguntas à dívida técnica, o projeto no rastreador, o caminho crítico no gráfico, etc.), ser capaz de explicar os benefícios do projeto para o seu cliente. E quanto mais elaborada a história, mais compreensão você transmitirá a ela. Uma empresa tem objetivos que você ajuda a alcançar. É importante que você fique a par do que está acontecendo não apenas agora, mas também dos planos que se tornarão realidade em quatro partes. Portanto, comunique-se com a empresa, entenda o que ela quer fazer e quais são as formas de atingir esses objetivos e use esse conhecimento para combinar seus objetivos de engenharia e negócios.

Talvez os objetivos não coincidam imediatamente, mas em um quarto ou mesmo em um ano. Mas isso permitirá que você entenda quando a empresa estará pronta para a mudança.

Se você conseguir sincronizar metas, receberá todos os bônus: priorização, recursos e organização de todo o trabalho. O proprietário do produto ajudará você a fazer isso. A principal tarefa é concordar com ele.

Total


Quando falamos sobre mudanças nas principais áreas do produto, um algoritmo simples de quatro etapas se complica:



mais duas etapas são adicionadas. O primeiro é uma compreensão do que estamos trabalhando. O segundo - depois do encapsulamento - uma compreensão de como a solução funciona e como evitar alterações no seu código.

Usando esse algoritmo e minhas recomendações para organizar o processo, você pode trabalhar com qualquer dívida técnica.

O artigo é baseado no meu discurso na reunião, você pode ver a reportagem em vídeo .

All Articles