DEV Community

Tio Dani
Tio Dani

Posted on

Codando sem medo na 'main'

Uma confusão típica de branches de um repositório usando GitFlow

For non-Portuguese speakers, there is an English version of this article here.

Não. Isso não é bait. Prometo.

Talvez você já faça isso e, se for esse o caso, espero que a leitura ainda possa ser útil. Mas vou falar com aqueles que ainda não estão aproveitando do cobertor aconchegante da Integração Contínua. Minha equipe não está (embora seja um cenário específico, e irei explorá-lo mais tarde, mas ainda acho que deveriam). E se por acaso você achar que uma boa forma de pensar nisso seria "Depende!", eu arrisco e digo: "Tá, mas provavelmente não!".

Vamos admitir, por que você não está fazendo suas alterações diretamente na trunk (ou na main, ou como quer que você a chame)? Presumo que a maioria de vocês responderia de uma das seguintes formas:

  • Foi assim que me ensinaram! Eu nem pensei nisso desde então.
  • Você está louco? As pessoas ficariam bravas se eu quebrasse alguma coisa.
  • Estou ciente de onde isso vai dar, e li que seria o caso de trabalhar em branches quando o código é mantido por uma equipe grande, que é o meu cenário.

Se seus motivos forem diferentes desses, gostaria de saber. Considere gastar um minuto para deixar um comentário.

O terceiro caso é provavelmente o melhor cenário para o "Depende!", embora a definição do que seria uma "equipe grande" seja relativa e muito questionável. Mas eu não quero encher o saco de ninguém com isso, então vamos indo. É um tema quente cercado por equívocos e mal-entendidos, apesar de existir há décadas.

(Re)Descobrindo a Integração Contínua (CI)

Ah, tio! Você não pode estar falando sério. Eu já uso _____ há anos.

- Talvez você (preenchendo o espaço em branco com Jenkins, Github Actions, TravisCI, CircleCI ou qualquer outra ferramenta como essas).

Se você já estiver usando alguma ferramenta de automação de compilação para fazer algumas validações conforme o novo código é enviado para o controle de versão, isso é ótimo. Acredite em mim, ainda sei de equipes que não usam. Ainda assim, mesmo que seu pipeline de build esteja montado, isso não é suficiente para dizer que você está usando a Integração Contínua.

Integração Contínua refere-se à prática de frequentemente integrar alterações de código em um repositório compartilhado. Envolve automatizar o processo de merge, validação e build das alterações de código, garantindo que a integração seja contínua e confiável. A CI permite que as equipes de desenvolvimento trabalhem simultaneamente, colaborem com eficiência e detectem problemas de integração no início do ciclo de desenvolvimento.

Se você não confia que seu pipeline está fazendo um bom trabalho, garantindo que está tudo bem e pode simplesmente ser colocado em produção, ainda não está se beneficiando de CI. E se for seguro assumir que garante, deveria te permitir que você codifique na trunk sem medo.

"E por que isso importa?" você pode perguntar. Você se lembra do último post? Uma das Quatro métricas do Accelerate é Lead time, ou seja, o tempo que leva para uma mudança de código ser implementada e implantada na produção. O tempo necessário para implementar a mudança depende totalmente das pessoas engenheiras de software, mas, uma vez concluída a tarefa, quanto mais tempo demorar para entrar em produção, sentada em uma fila, esperando para ser implantada, maior será o Lead Time. Portanto, este indicador nos dá uma pista de quanto tempo acumulando poeira digital um commit deve esperar antes de ser implantado: Horas? Dias? Talvez semanas... (Já vi cenários onde um único deploy poderia levar até seis meses para ser feito, com centenas de commits).

E essa é uma preocupação central do DevOps: fazer com que o código chegue à produção de maneira suave e rápida. Daí a importância do indicador Lead Time, e o quão importante é depender do tempo que leva para o código ser implementado, e fazer com que o tempo de implantação seja cada vez menor, o máximo que pudermos.

Mas, se agilizar o deployment é uma das maiores preocupações, existe uma segunda, tão importante quanto a primeira, que é torná-lo confiável.

Então, como podemos tornar nosso pipeline de construção confiável o suficiente para nos dar o benefício do CI?

Use o controle de versão

Você provavelmente usa git. Eu sei que existem outros (em minha carreira eu usei CVS, Visual Source Safe, Subversion e até Perforce por um curto período), mas já tem algum tempo que o Git foi bem estabelecido como um padrão para programadores em um sentido geral. Mas usar uma ferramenta de gerenciamento de controle de código é apenas o meio para um fim, e lidar com o controle de versão adiciona uma nova camada à maneira como você o usa.

Por exemplo, a maioria das pessoas desenvolvedoras apenas usa o SCM para fazer o commit do código da sua aplicação, mas ignora que eles devem criar versões do código para configuração do sistema, configuração da aplicação e scripts para automatizar a construção e configuração dela. Portanto, se sua aplicação depende de um banco de dados, você deve implementar alguma automação para manter seu banco de dados alinhado com sua versão de código (manipulação de migração de schema, dados que precisam estar disponíveis quando a aplicação for executada etc.). Além disso, a configuração deve ser igualmente versionada. Considerando o caso da dependência do banco de dados, deveria ser fácil configurar as credenciais do banco de dados e injetá-las em tempo de execução para que a mesma versão possa ser implantada em ambientes produtivos e não produtivos (para testes, controle de qualidade ou preparação).

O livro Accelerate descreve uma pesquisa que foi realizada para o relatório "The State of DevOps" do Puppet, e eles descobriram que:

O mais interessante foi que manter a configuração do sistema e da aplicação no controle de versão estava mais correlacionado com o desempenho da entrega de software do que manter o código da aplicação em si no controle de versão. A configuração é normalmente considerada uma preocupação secundária para o código no gerenciamento de configuração, mas nossa pesquisa mostra que isso é um equívoco.

Portanto, usar o Controle de Versão e gerenciar a configuração do Sistema e Aplicação como parte de sua base de código, bem como scripts de automação, não é apenas uma etapa necessária para a Integração Contínua, mas também melhora o desempenho da equipe e torna a pipeline mais confiável.

Automação de teste

A essa altura, pode não ser uma surpresa para você que estou falando sobre coisas que já são discutidas há muito tempo: CI, Controle de versão e, agora, Automação de teste. Você provavelmente já ouviu falar deles e provavelmente sabe o que é TDD... talvez até tenha lido o livro eXtreme Programming, assistido a alguma palestra sobre isso em uma conferência ou ouvido falar sobre isso em algum post de blog ou vídeo do Youtube. E talvez você também tenha visto outras pessoas rejeitando-o (às vezes, completamente).

Sou um grande fã de TDD, mas independentemente do que você possa pensar sobre isso como uma ferramenta de design, o fato é que, para aumentar a confiabilidade do pipeline de build, você precisa usar automação de teste. E, para o bem da minha sanidade, aproveito para dar um conselho: considere usá-la fazendo TDD (prometo que não vou falar mais sobre isso).

Sim, a Automação de Testes é obrigatória. Se você estava pensando em parar de ler quando falei sobre versionar a configuração do sistema porque pensou que seria difícil, provavelmente está se arrependendo de continuar a leitura. Testar é difícil. Especialmente ao fazê-lo tardiamente no workflow de desenvolvimento (manterei minha promessa). Mais ainda, já que não estamos falando apenas de testes que nos garantem que nosso código está funcionando bem, mas também de tornar o pipeline de construção confiável e agora temos o código de configuração sendo versionado com o código da aplicação e os scripts de automação junto também. E tudo deve estar bem integrado (Integração Contínua, lembra?).

Hora da história:

Em 2013, eu estava trabalhando em um projeto usando .Net, e optamos por usar o recurso de Migrations do Entity Framework para lidar com o versionamento do schema do Banco de Dados. Um dia, quando estávamos tentando construir uma nova versão do aplicativo a pipeline tentou gerar o script para as alterações do banco de dados, recebemos um erro de migração e tivemos que adiar a implantação e investigar o problema.

Descobrimos que o recurso de migração do Entity Framework dependia de um timestamp para garantir que todas as migrações fossem mantidas em ordem, mas pessoas trabalhando em tarefas diferentes (usando branches diferentes) criaram alterações diferentes no banco de dados, cada uma com timestamps diferentes, mas a maneira como eles fizeram o merge do código posteriormente não estava na mesma ordem e as entradas não estavam seguindo a ordem cronológica correta, fazendo com que o Entity Framework entrasse em pânico. Então, a partir daquele momento, decidimos que deveríamos sempre verificar a ordem das migrações ao mergear o código na trunk (a gente usava GitFlow -- eu sei, você não precisa se lembrar de mim disso).

Mas isso não foi o suficiente. O objetivo é tornar a pipeline de build confiável e não podemos confiar que as pessoas se lembrem de verificar a linha do tempo das migrações para enviar seu código. Para ser confiável, a pipeline precisa verificar tudo novamente. É a proteção definitiva que evita que erros humanos causem problemas no fluxo de entrega.

Então começamos a criar um novo tipo de teste automatizado. As pessoas chamam de Testes de Infraestrutura (para diferenciá-los de outros, como testes de unidade ou testes de integração). E o primeiro caso de teste foi criar um novo banco de dados do zero usando a base de código (o que o tornaria compatível com a versão mais recente do código) e, em seguida, executar cada migração voltando para a primeira versão do banco de dados (testando as etapas de downgrade), e por fim rodando todas elas novamente, agora subindo para a última versão (testando as etapas de upgrade) e verificando se tudo foi feito ok.

Tivemos que gastar algum tempo descobrindo como hackear o Entity Framework para funcionar bem com o banco de dados SQLite na memória para que não demorasse muito para executar todos os testes. Assim, a compilação não seria afetada pelo tempo que levaria para executá-los.

Isso mostra como é valioso tornar o pipeline de construção confiável. Toda vez que rodava, a pipeline nos garantia que quaisquer problemas relacionados à ordem das migrações seriam detectados, para que pudéssemos apenas fazer nosso trabalho e confiar nela.

Certifique-se de que sua pipeline teste tudo que possa comprometer a Integração de sua aplicação. Não negligencie o valor que isso traz para o seu fluxo de entrega. Considere não apenas criar testes de unidade e obter uma boa cobertura de código deles, mas também fazer testes de integração, testes de infraestrutura e quaisquer outros testes que aumentem a confiabilidade da pipeline.

Aplique Inspeções de Qualidade e Segurança

Então, a aplicação está sendo compilada, os scripts de configuração e automação estão sendo consolidados junto com o código da aplicação, tudo está sendo bem testado e você tem uma boa cobertura de testes no código... olha só! Bom trabalho. E agora?

Agora temos que evitar outros gargalos que possam afetar nosso Lead Time e, consequentemente, sabotar a confiabilidade do pipeline. Primeiro, verificamos se o código está em conformidade com quaisquer padrões de qualidade que devemos seguir (Build quality in). Talvez você e sua equipe gostem de executar algum linter para verificar os padrões de código ou extrair alguns relatórios de métricas de código para verificar a complexidade ciclomática ou outros indicadores. Talvez esse não seja um problema que pare a pipeline, mas anteciparia o feedback de inspeções e revisões posteriores que poderiam fazer com que as alterações esperassem antes de serem implantadas.

O mesmo se aplica à segurança (Shift Left on Security). Talvez a equipe de Infosec da empresa tenha estabelecido conformidade de segurança que todos devem seguir. Você deve fazer sua pipeline verificar essas inspeções também. Talvez você precise executar varreduras de vulnerabilidade para verificar seu código, as dependências da aplicação e sua configuração... quanto mais você colocar em sua pipeline, mais confiável ela será.

Trabalhe em pequenos lotes e envie códigos regularmente

Agora sua pipeline está fazendo tudo o que precisa para garantir que toda a aplicação esteja íntegra e você pode confiar nela.

A melhor coisa que você deve fazer agora para realmente se beneficiar da Integração Contínua é evitar que seu código gaste muito tempo antes de ser enviado para a trunk. E a melhor maneira de fazer isso é codificar direto nela. Para evitar branches de vida longa, você deve evitar fazer seu trabalho em grandes lotes de código sendo enviados de uma só vez e trabalhar em etapas menores, valiosas e entregáveis, enviando pequenos lotes de código regularmente para a pipeline e observando seu fluxo de entrega enquanto ela roda para executar a integração de forma iterativa e incremental.

Além disso, você nunca deve esquecer que as coisas evoluem, e não existe algo como "definitivo", como uma pipeline final, Uma pipeline para governar todas. Você provavelmente enfrentará situações em que a confiabilidade de sua pipeline de build será desafiada e encontrará espaço para melhorias. Tome conta dela. Não a negligencie! Ela vai te ajudar a dormir melhor.

Conselhos Adicionais

Algumas pessoas (inclusive eu) gostam de manter uma versão menor da pipeline para executar enquanto enviam o código para o repositório de origem como uma forma de garantir, no último momento, que tudo está bem antes que o código chegue lá e quebre a trunk no GitHub (ou similar). Isso é útil, embora você provavelmente tenha que ter cuidado com o quão curto ela é em comparação com a original.

Outra opção seria rodar tudo localmente, no seu ambiente de desenvolvimento (embora isso nem sempre seja possível, mas se for, também é ótimo).

Muitas pessoas tendem a ficar aborrecidas se o build as fizer esperar algum tempo. Especialmente quando elas precisam executá-lo antes do envio. Muitas acham que deveriam gastar esse tempo codificando outras coisas. E é engraçado imaginar o quão relativo é dizer que demora muito, pois faz sentido se a coisa toda demorar mais de 10 minutos para terminar, mas você encontrará pessoas que provavelmente reclamariam mesmo que levasse apenas 2.

Conclusão

Eu sei... o artigo ficou longo... muito.

Mas cobrimos muito em uma das melhores práticas que temos para reduzir o Lead Time, que é uma das Quatro Métricas Chaves, Integração Contínua. Conversamos sobre como codificar na trunk não é uma maneira sem noção de trabalhar e, por meio da confiabilidade da pipeline, podemos ter certeza de fazê-lo sem hesitação.

No próximo post vou falar sobre a Entrega Contínua, que é outra ótima prática a ser utilizada nesse sentido.

Espero que você ache este post útil e estou ansioso para ver o que vocês pensam sobre isso.

Obrigado.

Top comments (2)

Collapse
 
felruivo profile image
Felipe Piacsek

Muito obrigado pelo texto! (precisamos de mais conteúdos assim, principalmente em português)

Gostaria de saber se você tem algum conselho sobre como implantar integração contínua num projeto/empresa que nunca teve isso como filosofia.

Collapse
 
dmyoko profile image
Tio Dani

Opa, tô quase um ano atrasado e nem sei se você ainda precisa da minha resposta.

O melhor a fazer é sempre demonstrar o custo que a falta de automação causa no time. Quanto tempo o time perde tentando colocar uma versão nova no ar? É algo que se pode fazer a qualquer momento? Se, por algum motivo, precisarmos fazer o deploy agora, que tipo de mobilização será necessária? E quanto tempo irá levar? E quanto tempo depois de pronto a gente vai ficar confiante de que está estável o suficiente para voltarmos para nossa rotina de trabalho normal?

E eu sei que esse cenário hipotético pode ser interpretado como uma forçação de barra. Algumas pessoas irão dizer "Nossos deploys são sempre programados com uma margem tranquila de tempo para nos organizarmos da forma adequada. Não existe isso de 'vamos publicar agora'." E talvez seja verdade, mas isso não vai mudar o fato de que um deploy possui um custo operacional: se as pessoas precisam parar o que estão fazendo pra alinhar a nova versão, se se perde horas e horas de validação e homologação, se tem gente que precisa acordar de madrugada pra fazer o trabalho durante um período em que o sistema não está sendo utilizado... tudo isso gera um custo.

Seja o custo pelas horas que são dedicadas a isto, seja o custo do stress e a preocupação das pessoas a respeito do risco de outage, ou de novos bugs.

Leve isto em conta quando tiver que argumentar sobre.