Mergeando uma feature com 200 PRs

Como e porquê nunca fazer isso

Giovani Pereira
iFood Tech
11 min readDec 5, 2019

--

Você e seu time foram chamados para um projeto novo. A demanda é enorme: vocês vão refazer uma grande parte de um código com o intuito de melhorar o desempenho e a flexibilidade para adição de novas features. Além disso, estão mexendo numa parte crítica da aplicação, ela precisa funcionar lindamente e com o mínimo de bugs.

O projeto é incrível, você e seu time têm liberdade para escrever uma solução do zero, aplicar uma arquitetura nova, bastante tempo para dedicar ao projeto, e a equipe… bom, a equipe é sensacional — o céu de todo arquiteto de software.

Vocês começam o desenvolvimento, muita coisa é POC, então naturalmente é descartada. Várias branches são criadas com os dias contados. Cada um apresenta uma POC, uma experimentação, uma ideia, um exemplo. Até que chegam num consenso, parece simples o suficiente para ser escalável, robusto o suficiente pra aguentar as necessidades de produto e dos usuários, vários pontos de arquitetura legais… em resumo, time satisfeito.

Pegam aquela branch feature-teste-arquitetura-3 e começam a desenvolver a partir dela a famosa feature branch.

Até o momento, tudo certo, nada novo sob o sol. As POCs e experimentações são importantes, a discussão é importante, todas as falhas foram importantes.

O começo do caos

Vamos supor que, no seu projeto, vocês usem uma branch develop, carinhosamente conhecida como dev. Ela representa toda a sua verdade, novas coisas entram lá e novas versões saem de lá. Ou seja, é uma branch extremamente dinâmica e deve sempre estar funcional.

O problema começa em mexer numa branch que já está desatualizada em relação à dev (mantenha suas branches atualizadas!), e focar todo o esforço de desenvolvimento apenas nela.

O seu projeto é longo, já sabemos disso, e o time todo desenvolve nessa feature branch paralela. Como é de se esperar, para fazer uma coisa legal, alguns arquivos serão movidos, alguns assets adicionados, novos módulos criados, alguns imports aqui e ali…

Mas num passo ainda mais frenético que seu time desenvolve, a develop cresce, e a distância entre sua feature branch e a verdade fica cada vez maior.

Você nem viu, mas já estamos em estado de caos

O problema surge quando vocês impreterivelmente precisam atualizar sua feature branch com a develop. Um novo framework foi adicionado ou mudaram algo no processo de build, então tudo precisa ser atualizado, caso contrário não será possível continuar trabalhando.

Um bravo programador, equipado do mais novo Git assume a tarefa, e já começa a ver o tamanho do problema na forma mais assustadora possível:

É… assustador. Dezenas de conflitos espalhados pelo projeto todo.

Mas conflitos fazem parte do dia-a-dia de todo programador que se preze. Aprenda a aceitá-los em sua vida e tudo será mais leve.

O problema é quando todo merge gera uma infinidade de conflitos. Todos os assets, arquivos, módulos alterados e adicionados conflitam com a dev. E pior, outras pessoas continuaram desenvolvendo considerando que nada disso foi movido ou alterado, então mesmo resolvendo os conflitos, você vai ter que corrigir mais um monte de imports e partes quebradas.

O que parecia um simples merge, toma uma boa parte do seu dia. Frustração toma conta do seu ser. Coisas não compilam, precisam ser editadas, mas uma hora tudo funciona.

E podemos continuar desenvolvendo nossa feature branch em paz.

Agora não dá pra fugir, é o caos instaurado mesmo

Você de certa forma acostuma com os merge-backs demorados, já fica rotineiro. Mas pra cada merge que algo precisa ser mudado, são coisas a mais entrando na sua feature branch e não da dev.

Até que um belo dia, algo acontece e o time começa a pensar no lançamento da feature. Ainda estamos no meio do desenvolvimento, mas é comum pensar no lançamento e se programar.

E alguém faz o famoso teste: “E se abríssemos o PR (pull-request) da nossa feature agora?”

Imagem de um code review que mostra 1103 arquivos alterados,36 mil linhas adicionadas e 25 mil linhas removidas
Imagens reais de dor e sofrimento

“Ugh!”

Você até engasga.

E a ficha cai: estamos num famoso caso de MERGE HELL! Irreversível (cherry-pick, rebase, copy-paste apenas não vão te salvar), imprevisível (a develop continua mudando) e impreterível (não vai dar pra fugir de ser mergeado).

Bichinho gritando merge Hell!
XABLAU

E agora? Queremos mergear a fetaure branch, mas ela não está pronta ainda, e abrir um review de 1K arquivos é desumano e perderia todo aquele code review lindo que gostamos, ninguém vai realmente olhar TODOS os arquivos num tempo hábil. Um PR desse tamanho seria extremamente mal revisado ou nunca teria todos os approves necessários.

Ninguém quer começar o dia revisando cinquenta mil linhas.

Reuniões são marcadas, como chegamos a este ponto? Discussões são trazidas, como vamos mergear tudo? Perguntas são levantadas, quando vamos lançar? Medidas são tomadas, precisamos de um plano.

O plano

Imagem ilustrativa de um caderno com anotações
Photo by Med Badr Chemmaoui on Unsplash

Não sabemos como fazer ainda, mas sabemos o que queremos:

  • Mergear a feature o quanto antes
  • Code review expressivo em cima de todo o código
  • Dividir conhecimento e domínio da feature com outros times
  • Alinhar expectativas de lançamento

E para tudo isso existe apenas uma solução: mergear pedaços pequenos da sua feature, se-pa-ra-da-men-te.

Um ponto que complica as coisas um pouco é: a feature continua sendo desenvolvida. Mas existem maneiras de contornar esse problema e conseguir um plano de merge executável.

Identificando seções mergeávaeis

Comece seu plano observando a feature em si. Como ela é feita, o que ela adicionou, quais são suas dependências externas…

Algumas coisas podem ser mergeadas diretamente, como assets e arquivos movidos. Dependências externas também.

Abra um Pull Request separado para cada coisa que você quer mergear. Um para os assets, outro para os módulos, um para cada lógica…

Com PRs de tamanho razoável, que podem variar pra cada grupo ou projeto, é muito mais fácil as pessoas olharem e fazerem um code review realmente bom em cima disso tudo.

O problema começa em mergear código novo. Todo código mergeado deve estar compilável (até aí parece tudo bem, sua feature branch compila lindamente), mas uma classe geralmente não existe sozinha, ela depende de outra classe, que depende de um modelo, que depende de um enumerador, que depende de uma biblioteca…

Tente identificar essas dependências e pegar os menores pedaços possíveis que tenham uma lógica clara e que não dependam de mais nada.

Por exemplo, você pode tentar mergear todo o mecanismo de apresentação da sua feature (se ela exibir alguma coisa). Então você estaria mergeando uma tela, toda a lógica de controle, toda a lógica de apresentação, para todas as coisas que ela exibe.

Divida esse cara! Pegue apenas a base da sua tela, depois abra um PR individualmente para cada componente que é exibido. E continue assim para o restante da feature.

Código isolado

Um ponto importante é que: um código que não está sendo chamado pelo restante do projeto não afeta sua execução, então não há problema desse código já estar mergeado.

Esquema de dependências entre features.
Esquema de dependências entre features e bibliotecas. A feature laranja está desconectada das demais, e a feature roxa está conectada ao core e a uma outra biblioteca azul.

Imagine esse esquema como uma representação das dependências das suas features. O que está marcado em laranja, não tem nenhuma dependência de outras features ou bibliotecas, nem está sendo conectado ao core ainda. Ele poderia estar já mergeado na develop sem impactar o restante do seu sistema.

Já o que está marcado em roxo depende de uma outra biblioteca azul da qual o core também depende. Então mergear a feature roxa envolveria mais trabalho e cuidado, dado que já influencia no core.

Uma maneira de ainda assim mergear features e garantir que elas estejam desligadas é com o auxílio de feature flags, que ficam desligadas até que a feature esteja realmente pronta para ser lançada.

Vai dar trabalho (mesmo), mas não desanime!

É coisa que não acaba mais para ser mergeada. Mas tem vários pontos positivos, dentre elas: um bom code review.

Coisas menores serão revisadas mais rápido, e realmente revisadas. Diversas pessoas podem ter pontos a complementar sua implementação, discussões, evoluções da sua feature que só vão surgir num code review.

Mas alterar coisas no seu PR, significa que vai entrar na develop uma parte da feature diferente do que está na feature branch, e tudo bem. O code review está ali pra isso mesmo, o ponto importante aqui é lembrar de mergear a develop na sua feature branch sempre que uma nova parte da feature for mergeada. Isso vai levar a novos conflitos, mas você vai saber exatamente em quais partes ele vai acontecer, e que a solução é a versão mais atualizada.

Juntamente com isso, você começa a democratizar a sua feature. De repente, não é só mais a sua equipe que domina o que está acontecendo. Diversas pessoas vão começar a interagir com esse código novo, questionando, aprendendo, se tornando futuros colaboradores dela.

Alinhe expectativas

Vai demorar, vai dar trabalho e você vai precisar de tempo.

Sua feature em algum momento vai ser cobrada ou ao menos questionada do seu lançamento, mas não tema. Um bom trabalho realmente demanda tempo e mão de obra, e cabe a você explicitar isso para as partes envolvidas.

Além disso, alinhe com o time em desenvolvimento. Acredito que deva ser comum numa situação dessas que uma pessoa fique responsável pelo merge, enquanto outras continuam trabalhando na feature. Mas você deve alinhar o que vai ser mergeado, quando será e cobrar review dos seus team mates também.

“Ah, mas eu vou mudar isso agora, não mergeia ainda não.”

Não se preocupe com isso, mudanças vão acontecer, se você for esperar tudo ser terminado, ficar 100% estável, não vai terminar esse merge nunca. O que vai acontecer é abrir um novo PR depois com essas novas mudanças.

Dicas de coração ❤️

Eu passei por esse problema, por isso que escrevi esse artigo, e tive algumas lições aprendidas que podem te salvar um bom tempo, se estiver passando pela mesma coisa:

  • Faça uma boa documentação

Queremos code review, queremos que mais pessoas conheçam a feature, e para isso precisamos mostrar para elas como funciona.

Ter uma documentação de algo tão grande é essencial. É código nunca antes explorado pelas outras pessoas, e é importante para que elas possam tirar dúvidas enquanto tudo ainda é desenvolvido.

  • Mantenha a mesma formatação de texto da sua IDE e dos seus colegas de equipe

Eu passei por um problema bem específico, em que meu editor de texto automaticamente apagava todo espaçamento extra e quebra de linhas adicionais.

Quando eu mergeava uma parte da feature, que outro colega tinha feito, sem perceber, espaços em branco e linhas vazias eram removidas. O que é bom, limpar o código fonte, mas quando eu mergeava a dev de volta na feature branch acontecia um efeito incrível:

Print de um editor de texto mostrando um conflito entre duas linhas iguais
Diff de duas linhas em branco 🙄

Um diff de duas linhas em branco.

Não é nada óbvio nem esperado, mas em umas dessas linhas existem espaços, e na outra não. E isso era um problema, porque dependendo do arquivo geravam muitos, mas muitos conflitos desse tipo.

  • Faça um planejamento real de merge

Entenda quais partes dependem de outras partes, o que deve ser mergeado antes para liberar novos merges.

Ter isso realmente anotado, te dá uma noção melhor do que vai ser necessário nas próximas sprints.

  • Garanta tudo com testes

Sério gente, unit tests all the way!

Num cenário ideal, nem teríamos conflitos. Mas lembre-se que estamos longe do ideal. E muitos merge conflicts em uma feature em desenvolvimento é a situação perfeita para resoluções erradas, perder algo sem querer e acabar sumindo com uma parte de código importante.

Isso é extremamente mitigado tendo testes para suas features. Testes em tudo. Do tipo que se você perder uma linha o teste quebra.

  • Não duplique código

Nem assets, nem imports, nem nada.

Apesar de muitas vezes ser a solução mas rápida, código duplicado não evolui em conjunto. Como tudo é dinâmico, pode ser que outra pessoa precise editar uma parte de código que você duplicou, mas ela vai alterar apenas a instância original, fazendo com que a sua parte esteja desatualizada e infelizmente fadada ao erro.

Uma boa sugestão é criar módulos que permitam o compartilhamento de código entre sua feature nova e as partes antigas do código. Se ela for alterada, vai afetar diretamente as duas partes envolvidas.

  • Tenha paciência com os reviews

É uma coisa nova que ninguém nunca viu, tenha paciência. Realmente explique o que está sendo feito, converse com as pessoas, mostre como funciona. E colha os feedbacks. É importante aplicar as mudanças boas sugeridas nos reviews.

“Ah, mas vai conflitar com a minha branch depois disso!”

Sim, mas já falamos disso, faz parte da evolução da sua feature, para evitar desgastes maiores no futuro e manter sua solução sempre atualizada.

  • Não deixe chegar nesse estado

É claro que são dicas para resolver esse problema, mas o cenário ideal passaria longe disso.

Pensando novamente em código isolado, feature flags e planejamento de desenvolvimento, você consegue ter controle do acesso à sua feature ainda em desenvolvimento, e ainda assim, conseguir desenvolver diretamente na sua develop evitando chegar em num novo MERGE HELL.

Uma breve ideia de TBD

Já ouviu falar de Trunk Based Development, ou TBD?

Essa é a minha máxima para evitar o MERGE HELL.

Se você não conhece, TBD é uma política de trabalho com Git, onde existe uma grande branch (the Trunk, ou a master) que é sempre estável, sempre podemos tirar uma nova versão de lá, e novas features saem desse Trunk e voltam pra ele rapidamente.

A ideia é manter mudanças pequenas e graduais, que estejam sempre testadas, que não quebrem o Trunk e que cresçam continuamente. Aqui entra bastante a ideia que levantamos antes de código isolado, que não afeta nosso projeto e pode estar mergeado.

Quando uma feature nova for ser conectada ao nosso Core, usamos sempre uma feature flag. O TBD usa e abusa de flags, de forma que podemos ligar/desligar uma feature quando quisermos. Isso permite que features mesmo já conectadas ao core possam estar mergeadas sem impactar de fato o comportamento da aplicação.

Segue aqui o link do Trunk Based Development, eles têm várias ideias legais que podem te ajudar a compreender e/ou adotar esse processo.

Esquema de representação da árvore de commits sem TBD
Modelo de desenvolvimento não-TBD. Uma feature branch perdura por muito tempo e tem várias dependências ao longo do caminho.
Esquema que representa a árvore de commits com TBD
Exemplo de TBD — uma mesma feature dividida em vários merges adicionando pequenas partes ao Trunk.

Resultados

Deu tudo certo no final, depois de um pouco de estresse, bastante (re)trabalho envolvido e umas boas 3 sprints trabalhando nesse cara.

Imagem de um conjunto de tasks do Jira chamado Merge para a dev com 59 subtarefas
O 200 do título do artigo foi um exagero, mas deu pra pegar a ideia

Como dá pra ver na imagem acima, conseguimos mergear uma feature enorme em basicamente 59 Pull Requests. Mas 59 PRs não são mergeados da noite pro dia, não há Allan Turing, Zuckerberg nem fada do código que vão fazer esse milagre.

Você precisa mesmo de paciência, alinhar com todo mundo e fazer um processo muito bom. A melhor coisa é dividir o conhecimento dessa feature enquanto trabalha no merge, manter todos alinhados e evitar atritos durante o processo.

Enfim, tente não deixar chegar num Merge Hell, mas se chegar, divida tudo direitinho. E força, sua feature VAI entrar!😉

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

--

--