Uma jornada ao Trunk-Based Development

Felipe Martins
iFood Tech
Published in
7 min readMar 1, 2021

O uso de Pull Requests (PRs) com Code Reviews (PRCR daqui pra frente) virou o padrão de facto na indústria de software, ponto. Pode-se encontrar um incontável número de materiais por aí propondo maneiras de criar PRs melhores e realizar revisões assíncronas de código mais eficazes, além do fato de que quase toda ferramenta agora parece ter sido projetada para um fluxo em que novas porções de código sempre passem primeiro por uma feature branch e só depois sejam integradas a um branch estável como o main ou master no git (é de você mesmo que eu estou falando, Gitlab).

Essa estratégia está tão naturalizada que propor alternativas a essa forma de desenvolver software em times chega a gerar reações bastante passionais, como pode-se constatar na thread abaixo:

Não era de se surpreender, portanto, que tenhamos começado a trabalhar em meu time usando PRCR: novas funcionalidades eram desenvolvidas em branches isoladas e submetidas como pull-requests, sendo então integradas ao branch principal após revisão de código por outras membras do time. Esse fluxo se propõe a facilitar o trabalho de desenvolvedoras individuais através do isolamento de trabalho incompleto, controle de qualidade via revisão assíncrona de código por outras pessoas e compartilhamento de conhecimento através de feedback nos PRs.

Por outro lado, não pude deixar de notar outros efeitos, esses menos desejáveis, ao adotarmos essa prática como longos lead times¹, imposição de maior carga cognitiva ao time pela frequente troca de contexto entre tarefas (devido à mistura de atividades em progresso e outras sob revisão ao mesmo tempo), insegurança para se fazer deployments para produção e uma alta complexidade de conflitos ao se fazer merges para o branch principal.(*)

Por já ter trabalhado anteriormente em dois projetos mais complexos usando Trunk-Based Development (TBD) e lastreado em referências como o relatório DORA’s State of DevOps, eu sabia que poderíamos fazer algo melhor com as condições que tínhamos e, ao mesmo tempo, preservar o máximo possível os benefícios trazidos pelo PRCR — e por "melhor" aqui refiro-me a poder desenvolver novas funcionalidades com rapidez, adaptar-se a novos cenários e manter a qualidade de nossos entregáveis: em resumo, atingir uma alta-performance de acordo com as métricas fundamentais descobertas pelas pesquisas do DORA. Dado que o formávamos um time com quatro desenvolvedoras programando micro-serviços em tecnologias com as quais já estávamos bastante confortáveis (Java, Quarkus, PostgreSQL, AWS, Gitlab…), decidimos que havia espaço para fazermos um experimento com a adoção de TBD em novas funcionalidades e ver em que aspectos teríamos que nos adaptar.

*Verdade seja dita, a existência desses possíveis efeitos colaterais trazidos pela adoção do PRCR é bastante conhecida na literatura e pode-se encontrar vários materiais com recomendações sobre como mitiga-los ao mesmo tempo em que se mantém o uso de PRs. Meu ponto de vista, porém, é de que a partir do momento em que se criaram as condições para preveni-los, talvez você não precise mais estar usando PRCR.

O que é Trunk-Based Development

TBD é um processo de alta maturidade onde commits submetidos por desenvolvedoras são frequentemente integrados a um branch principal que deve estar sempre pronto para deploy em produção. Detalhemos cada um desses aspectos:

  • Alta maturidade: assim como micro-serviços, a adoção de trunk-based development não é para qualquer cenário e time. Ele exige práticas maduras de desenvolvimento como integração contínua, pipelines de build, deployments automatizados, confiança no controle de qualidade, etc — ou, em resumo, não confunda TBD com XGH;
  • Commits integrados frequentemente: as modificações de código submetidas são frequentemente integradas e tornadas visíveis para o restante do time — isto é, elas não precisam ser aprovadas formalmente nem ser revisadas previamente de forma assíncrona. Note que isso não impede, por exemplo, que pessoas trabalhem temporariamente usando branches separados em alguns casos desde que as modificações feitas lá sejam submetidas e sincronizadas com o time diariamente (por exemplo);
  • Branch principal: primariamente, o único branch usado pelo time e que representa o estado atual do projeto é o branch principal. Salvo certas exceções², não se usa feature branches, branches de hotfix, etc;
  • Sempre pronto para deploy em produção: commits submetidos ao branch principal devem estar sempre compiláveis (no caso de linguagens compiladas), com testes e controles automáticos de qualidade aprovados e prontos para serem colocados em produção. Note que estar pronto para ser colocado em produção não significa fazer deploys em produção a cada commit (ao menos não necessariamente), mas sim poder decidir fazê-lo sempre que for necessário³.

O que adaptamos, na prática?

Conjunto de ferramentas para reparo de bicicletas
Novas metodologias, novas ferramentas (Fonte: https://www.flickr.com/photos/bre/552152780)

Podemos dissertar acima sobre como esses aspectos do TBD podem resolver os problemas apontados com o uso do PRCR: lead times tendem a tornar-se menores já que não há o gargalo de revisão de código assíncrono; as pessoas do time lidam com menos carga cognitiva já que podem trabalhar em uma tarefa por vez de seu início ao fim; conflitos de código tornam-se menos frequentes e complexos pois todas estão trabalhando em um mesmo branch sincronizado diariamente; e, finalmente, reduz-se o medo e ansiedade de se fazer deployments em produção já que cultiva-se uma base de código que deve estar sempre validada e preferencialmente deployada com frequência.

Os desafios surgem, no entanto, quando pensamos em como preservar os benefícios que podem ser trazidos pelo PRCR:

Isolamento de trabalho incompleto

O time precisou se reeducar para identificar como decompor suas tarefas em atividades menores e consistentes que interferissem o menos possível com funcionalidades e código existente enquanto a implementam, tendo sido bastante útil aqui um esforço para usar TDD.

Para cenários mais complexos, onde há maior dificuldade em manter-se o isolamento de implementações incompletas, tivemos que nos educar sobre o uso de técnicas como Branch by Abstraction, Expand-Contract/Parallel Change e sobre o uso mais generoso de Feature Flags.

Controle de qualidade

Uma das disciplinas que passamos a adotar durante o refinamento de tarefas foi a criação de critérios de aceitação formalizados no formato Gherkin e, quando possível, ter sua verificação automatizada via testes de aceitação usando bibliotecas e frameworks como o Cucumber ou RestEasy. Nos casos em que não parece viável automatizar a verificação, adicionamos à Definição de Pronto daquela tarefa que a pessoa deveria mostrar ao resto do time como a implementação está obedecendo aos critérios de aceitação.

A escrita de testes unitários e de integração (definidos conforme descrito aqui) é adotada como parte do processo de desenvolvimento das tarefas, tendo sua cobertura mensurada como parte do pipeline de build do projeto. Padrões de estilo, code smells e restrições de arquitetura de código também são verificadas automaticamente no pipeline (e localmente com o suporte da IDE) com ferramentas como Checkstyle, SonarQube e ArchUnit — essas checagens não eram tratadas como falhas de build inicialmente mas, conforme fomos adaptando-as aos consensos do time, elas também passaram a ser consideradas falhas no pipeline quando detectam condições indesejadas. Cultivar uma suíte de testes rápida e confiável também nos permite praticar refatoração continuamente para manter a complexidade do código sob controle (o time ainda está amadurecendo esse hábito, admito).

Exemplo de especificações de arquitetura usando ArchUnit (Fonte no link da imagem)

Finalmente, o ambiente de dev/staging está sempre sincronizado com o conteúdo do branch principal (cada commit nesse branch gera um novo deploy nesse ambiente, assim como aplicação de migrações de banco quando houver), o que torna mais confiáveis as validações feitas lá antes de deploys para produção.

Compartilhamento de conhecimento

Estabelecemos que pareamentos seriam feitos de forma mais frequente, mesmo que pontualmente na forma de checkpoints ao longo da implementação de uma tarefa. Notem que isso não significa parear 8h/dia e tampouco fazer isso de forma indisciplinada — para mais detalhes sobre o que isso significa, recomendo fortemente esse material.

Uma prática que ainda não adotamos formalmente mas para a qual estamos no preparando é a adoção de sessões de "brown bag" semanais para que se possa compartilhar entre o time boas práticas de escrita de código, discutir prós e contras de refatorações feitas no projeto, etc.

Concluindo…

Placa de "obra em construção"
Photo by Mark König on Unsplash

Não tenho métricas formais para alegar que estamos tendo bons resultados em relação ao fluxo anterior além de alguns dados incompletos mostrando uma redução significativa no lead time das tarefas depois de termos adotado essa metodologia. Honestamente, mesmo que tivéssemos, isso não provaria que a adoção de TBD é superior para nosso cenário já que não temos como isolar os fatores envolvidos. A engenharia de software não é uma ciência no sentido Popperiano do termo e não precisamos fingir que o seja — se insistíssemos nisso de forma irredutível, ainda estaríamos programando em Assembly.

Subjetivamente, porém, é bastante sensível e relatado pelo time a melhoria de satisfação quanto ao processo de desenvolvimento. O forte investimento em automação da qualidade, assim como uma maior disciplina na formalização de critérios de aceitação traz mais tranquilidade para as lideranças técnicas e de produto no time, permitindo maior delegação e autonomia ao restante das desenvolvedoras.

Ressalto, também, que foi fundamental para a viabilidade desse experimento o fato de termos bastante controle sobre o processo de construção e deployment do serviço que construímos, além de termos apenas nosso time trabalhando nessa base de código — outros times que precisem fazer modificações nela ainda precisam submeter PRs para revisão já que temos menos controle sobre as boas práticas que julgamos necessárias nesse caso.

Notas

  1. "Lead time" é o tempo que se leva do momento em que se começa a implementar uma tarefa até o momento em que ela é colocada em produção (existem algumas variações dessa definição na literatura mas é essa que estou usando aqui).
  2. Costuma ser aceitável ter branches de curta duração em times e projetos com alta complexidade que queiram os benefícios do TBD.
  3. Esse é, na verdade, o conceito mais preciso de Entrega Contínua (CD ou Continuous Delivery).

Quer receber conteúdos exclusivos criados pelos nossos times de tecnologia? Inscreva-se.

--

--

Felipe Martins
iFood Tech

Posto besteira no Twitter e escrevo software para pagar meus boletos. Ele/Him.