Skip to main content

Introdução

O branching é considerado o feature matador do Git, permitindo que milhares de desenvolvedores trabalhem em paralelo sobre uma mesma base de código. No entanto, apesar do Git facilitar muito,o processo básico continua sendo o mesmo: cada desenvolvedor trabalha em uma cópia privada da base de código. Agora os desenvolvedores podem facilmente trabalhar em seus próprios features, mas surge um problema: como juntar as cópias novamenteao final do trabalho?

Os desafios com esse processo são os conflitos de merge. Estudo de Brindescu et al. (Brindescu et al. 2020) mostra que os conflitos de merge são prevalentes: cerca de 20% de todos os merges acabam em um conflito de merge.

Os conflitos de merge podem ser classificados em três tipos conforme a forma de sua detecção, por ordem crescente de dificuldade:

1.   O conflito textual ocorre quando os desenvolvedores modificamos mesmos arquivos de código em paralelo. Este é o conflito que é detectado automaticamente pelo Git, mas requer intervenção humana para sua resolução;

git
Figure 1: Branchinge merge

1.   O conflito semântico estático ocorre quando as falhas aparecem na análise do programa estática, no build ou IDE;

2.  O conflito semântico dinâmico é o mais insidioso e dificultoso, ocorre quando as falhas aparecem somente em tempo execução.Esse conflito pode ser detectado por um teste que reproduza as condições da falha.

Os conflitos de merge têm impacto na qualidade do código,são perturbadores para o fluxo de trabalho de desenvolvimento. Para resolver um conflito de merge, um desenvolvedor tem que parar o que está fazendo e focar na resolução.

Resolver um conflito requer que o desenvolvedor entenda as mudanças conflitantes, crie uma solução de consenso que satisfaça ambos os conjuntos de requisitos que impulsionaram as mudanças. Não há comocriar um algoritmo para resolver conflitos automaticamente. Muitos times gastam uma quantidade excessiva de tempo lidando com seu emaranhado de branches.

Esses fatores podem levar os desenvolvedores a adiar a resolução do conflito ou “empurrar o problema com a barriga”, especialmente no caso do conflito semântico dinâmico.

De fato, um estudo deNelson et al. (Nelson et al. 2019) descobriu que 56.0% dos desenvolvedores adiaram pelomenos uma vez resolver um conflitode merge.

No entanto, quanto mais tarde um conflito for resolvido, maisdifícil é recordar a lógica das mudanças, o que torna o processo de resolução muito mais difícil (Fowler 2006). Como apropriadamente colocado por um participante do estudo de Nelson et al.:

Adiar um conflito de merge é simplesmente empurrar o problema com a barriga (para um precipício). Geralmente, a resolução do conflito só fica mais difícil com o passar do tempo.

O estudo de Brindescu et al. (Brindescu et al. 2020)descobriu os principais fatores que influenciam a dificuldade dos conflitos de merge:

•  Complexidade das linhas de código em conflito: quando mais complexo maior a dificuldade dos conflitos de merge;

•  Modularidade: se um sistema tem bons módulos, então, na maioria das vezes, os desenvolvedores estarão trabalhando em partes bem separadas da base de código, suas mudanças não causarão conflitos;

•  Tamanho dos branches (número de linhas de código modificadas ou adicionadas): quando do bramoso tamanho dos branches, o valor esperadoe a incerteza da dificuldade dos conflitos de merge (pessoa-horas) quadruplica aproximadamente.

Neste artigo apresentamos quatro workflows que suportam o desenvolvimento em paralelo propiciado pelo Git, mas buscando minimizar a dificuldade dos conflitos de merge:

• Git-flow

•  OneFlow

• GitHub Flow

•  Trunk-based Development

A ideia chave é o conceito de deslocamento à esquerda (Forsgrenet al. 2016). Quando deslocamos paraesquerda, menos coisas quebram na produção, porque quaisquer problemas são detectados e resolvidos mais cedo.

Pense no processo de entrega de software como uma linha demontagem de fabricação. A extremidade esquerda é o laptop do desenvolvedor onde o código se origina, e a extremidade direita é o ambiente de produção onde esse código será implantado.

Quando deslocamos para esquerda, em vez detestar a qualidade apenas no final, temos vários loops de feedback aolongo do caminho para termos problemas menores de resolução mais fácil.

Git-flow

Git-flow tornou-se um dos workflows mais populares. Foi escrito por Vicent Driessen em 2010 (Driessen 2010),aparecendo quando o Git estava ficando popular.Nos dias anteriores ao Git, branching era frequentemente visto como um tópico avançado. Em comparação com o desajeitado Subversion, o Git tinha branching “leve”.

Isto facilitou considerar vários branches como ativos (em paralelo)e fazer merge mais tarde. O mecanismo de merge do Git era muito bom e tinha rastreamento desde o início.

Era mais eficaz do que outras tecnologias de merge anteriores para processar silenciosamente a complexidade.

Para ilustrar o Git-flow apresentamos na figura a seguir o diagrama de branches de um projeto exemplo. O diagrama de branches mostra os commits na ordem cronológica descendente.

git flow

Os BranchesPrincipais

Branch Principal ou Tronco O branch principal ou tronco é um branch especial que reúne o trabalho entregue pelo time. Sempre que quisermos começar um novo trabalho, baixamos o branch principal para nosso repositório local para começar a trabalhar.

Sempre que quisermos compartilhar nosso trabalho com o resto do time, atualizamos esse branch principal com o nosso trabalho, idealmente usando o processo de integraçãoao branch principal que discutiremos em breve.

Times diferentes usam nomes diferentes para o branch principal, muitas vezes encorajados pelas convenções dos sistemas de controle de versão usados. O Git-flow chama-o de “develop”. Os usuários do Gitmuitas vezes chamá-lo-ão de “master”ou “main”, usuários do Subversion geralmente chama-o de “trunk”.

Devemos salientar aqui que obranch principal é um branch único, compartilhado. Quando as pessoas falam sobre “develop” no Git-flow, podem querer dizer várias coisas diferentes, já que cada clone de repositório tem seu próprio “develop” local.

Os times têm um repositório central: um repositório compartilhado que atua como o único ponto deregistro e é a origem dos clones, geralmente chamado de “origin”.

Começar um novo trabalho do zero significa clonar este repositório central. Se já temos um clone, começamos baixando o “origin/develop” para atualizá-lo. Nesse caso, o branch principal é o “origin/develop”.

Branch de Produção O branch de produção é um branch cujo“head” sempre marca a última versão pronta para produção. De forma confusa,o Git-flow chama este branch de “master”, o que leva algumas pessoas a pensarem equivocadamente que este é o branch principal a que referimos na seção anterior.

Quando o código-fonte atinge um ponto estávele está pronto para ser liberado para produção, todas as mudanças são copiadas para o branch de produção e tagueadas com um número de versão. Consideramos isso como uma cópia em vez deum merge, pois queremos que o código de produção seja o mesmo que foi testado anteriormente.

Um processo automatizado pode implantar uma versão em produção sempre que um commit é feito no branch de produção.

Uma alternativa à utilização de branch de produção é aplicar um esquema de tagueamento do número de versão. Podemos então obter um histórico de versões observando os commits tagueados. A automação tambémpode ser baseadaem atribuições de tags.

Branches de Apoio

Além dos branches principais (principale de produção), o Git-flow usa uma variedade de branches de apoio: para ajudar o desenvolvimento em paralelo entre os membros do time, facilitar o rastreamento de features, preparar releases em produção e ajudar a corrigir rapidamente problemasde produção em vôo.

Ao contrário dos branches principais, estes branches têm sempre um tempo de vida limitado,visto que serão removidos após o uso.

Os diferentes branches que o Git-flow usa são os seguintes:

• Branches de feature;

•  Branches de release;

•  Branches de hotfix.

Cada um desses branches tem um propósito específico e está sujeito a regras estritas sobre quais branches podem ser sua branch de origem e quais branches que devem ser seu destino.

Branches de Feature

Pode ter como origem: o branch principal.

Deve ter como destino:o branch principal.

Convenção de nomenclatura: qualquer coisa exceto master, develop, release-* ou hotfix-*

Abrimos uma branch separada para cada feature quando começamos a trabalhar nele e continuamos trabalhando nesse branch.

Enquanto estamos trabalhando num branch de feature, outros commits estão pousando no branch principal. Então, devemos baixar regularmente asmudanças cometidas no branchprincipal para nossa branch de feature para detectar se há quaisquer conflitos. Isso deve ser feito obrigatoriamente antes da integração, de forma a garantir que não haverá conflitos na integração.

Note que este “merge ao contrário” não é a integração propriamente dita, visto que não subimos a feature de volta para o branch principal.

Quando terminarmos de trabalhar no feature, executaremos a integração ao branch principal: uma cópia do branch do feature ao principal, para incorporar o feature ao produto.

Figure 3: Branch de feature

O Git-flow prescreve que seja usado o comando“merge” com a opção sem fast- forward, ao invés de “rebase”. A opção sem fast-forward faz com que o merge crie sempre um novo commit de merge, mesmo que omerge possa ser realizado com um fast-forward.

O Git-flow não diz nada sobre a duração dos branches de features, portanto, nem a frequênciade integração esperada. Também não sesabe se o branch principal deve ser um branch saudável e, em caso afirmativo, que nível de saúde é necessário. A presença de branch de produção implica que o branch principal não é pronto para release.

O escopo do branch deve ser definido antes de sua criação e nunca realizar tarefas fora do escopo pré-definido. Caso seja necessário fazer outro trabalho, deve ser criado um branch separado para cada um.

Branches de Release

Pode ter como origem: obranch principal.

Deve ter como destino: o branch principal e o branch de produção.Convenção de nomenclatura: release-<número da versão de produção> Um branch de release típico copia do branch principal, mas não permite que

novos features sejam adicionados a ele. O time de desenvolvimento principal

continua a adicionar tais features ao branch principal, eestes serão pegos em uma release futura. Os desenvolvedores que trabalhamna release se concentram exclusivamente em remover quaisquer defeitosque impeçam a release de estar prontapara produção.

Quaisquer correções a esses defeitos são criadas no branch de release e mergeada sao branch principal. Assim que não houver mais falhas para lidar, o branch está pronto para arelease em produção e será copiado ao branch de produção.

Figure 4: Branch de release

Embora o escopo de trabalho para correções no branch de release seja menor quede um branch de feature, fica cada vez mais difícil fazer o merge deles de volta ao branch principal com o passar do tempo.

Branches inevitavelmente divergem, então a medida que mais commits modificam o branch principal, fica mais difícil fazer merge do branch de release para o branch principal.

Times que têm apenas uma versão em produção precisarão apenas de um único branch de release,mas alguns produtosterão muitas versõesem uso em produção.

Software que érodado nas dependências do cliente só será atualizado quando essecliente desejar. Muitos clientes relutam em atualizar, a menos quetenham novos features atraentes.

Tais clientes, no entanto, ainda querem correções de bugs, especialmente se envolverem problemas de segurança. Nessasituação, o time de desenvolvimento mantém branches de release abertos para cada versão usada em produção e aplica correções a eles conforme necessário.

Branches de Hotfix

Pode ter como origem: o branch de produção.

Deve ter como destino: o branch de produçãoe o branch principal. Convenção de nomenclatura: hotfix-<número da versão da correção> Se um bug grave apareceem produção, então precisa ser corrigido o mais

rapidamente possível. O trabalho nestebug terá uma prioridade maiordo que

qualquer outro trabalho que a equipe está fazendo,e nenhum outro trabalho poderáretardar a correção deste bug.

O trabalho de hotfix precisa ser feito com controle deversão, para que o time possa registrare colaborar corretamente. Podem fazer isto abrindo um branch de hotfix no head do branch de produção e aplicando nele quaisquer alterações para o hotfix.

Quando terminar o trabalho, o hotfix deve ser copiado aobranch de produção para implantação.

Então o hotfix deve ser aplicado ao branch principal para garantirque não haja uma regressão com a próxima release. Se o tempo entre as releases for longo, então o hotfixserá feito em cima de código que foi alterado,assim será mais difícil fazer o merge. Nestecaso, bons testes que expõem o bug são realmenteúteis.

Quando Usar o Git-flow

Recentemente em2020, num adendo ao seu artigo original, Driessen (Driessen 2010) reconheceu queo Git-flow não é adequado para os times que fazem Integração Contínua (ContinuousIntegration) ou Entrega Contínua (ContinuousDelivery):

Esta não é a classe de softwareque eu tinha em mente quando escrevi o post do blog 10 anos atrás. Seseu time está fazendo Entrega Contínua de software, eu sugeriria adotarum workflow muito mais simples (como o GitHubFlow) em vez de tentarencaixar o Git-flowno seu time.

Realmente, o Git-flow não foifeito para suportar a alta frequência de integração requerida pela Integração Contínua. Mais adiante discutiremos a Integração Contínua.

O Git-flow foi feito para a construção de software maistradicional que é ex- plicitamente versionado, ou precise suportar váriasversões em produção, como softwareinstalado nas dependências do cliente. De fato, ter várias versões em produção é um dos principais requisitosque demandam o uso de branches de releases.

Um branch deprodução pode adicionar alguma conveniência ao workflow, mas muitas organizações consideram que otagueamento funciona perfeitamente bem.

Obranch de produção é mais uma complicação desnecessária, que muitos equiv- ocadamente usam comobranch principal, até pelo nome adotado de “master”. Todos os outros workflows quediscutimos neste artigo dispensa o uso do branch de produção.

Enquanto o Git-flow é muito popular, no sentido de quemuitos dizem que o usam, é comumencontrar pessoas que dizem que estão usando Git-flow, mas estão fazendo algo bem diferente. Muitasvezes sua abordagem real está mais próxima do GitHub Flow.

OneFlow

Este workflow foi originalmenteproposto por Adam Ruka em 2015 (Ruka 2015), em um artigo de crítica ao Git-flow.

Um dos aspectos definidores do OneFlow é o uso de um branch principal único(único perene), chamado aqui de “master”. Isso é conseguido via eliminação do branch de produção, sendoeste substituído por um esquemade tagueamento.

Os branches de apoio (feature, release, hotfix) são temporários,e são usados principalmente como uma conveniência para compartilhar código com outros

desenvolvedores e como uma medida de becape. Destarte, os features são integradosdiretamente (via “rebase”) nobranch principal, de forma a manter umhistórico linear; já as releases ehotfixes são feitas de forma semelhante ao Git-flow.

Na figura abaixo, mostramos como ficaria o diagrama de branch usando OneFlow no projeto exemplo, onde podemos verificar notável simplificação em relação ao Git-flow.

Figure 6: Modelo de OneFlow

Vantagens do Oneflow

A vantagem mais óbvia, do ponto de vistado usuário do Git, é a facilidade de compreensãodo histórico do Git, dada a linearidade obtida com a integração direta no branch principal; assim, asequência dos commits é a narrativa de como o projeto foi feito.

Você nãopublicaria o primeiro rascunho de um livro, então por que mostrar seu trabalho bagunçado?

Quando está trabalhando em umprojeto, pode precisar de um registro de todas suas tentativas e erros, mas, quando for a hora de mostrar seutrabalho para o mundo, pode querer contaruma narrativa direta de como sair de A para B.

As pessoas neste campo usam comandos como “rebase” e“filter-branch” para rescrever seus commits antes de serem mergeados ao branch principal, deforma a contar a narrativa da maneira que for mais compreensível parafuturos leitores. Estes comandos são considerados“avançados”, assim alguns times podem ter certa dificuldade em usá-los.

No contexto mais amplo deste artigo,a facilidade (e conveniência) estimulada pela integração direta ao branchprincipal favorecerá a abordagem aqui estimulada, de menor duraçãodos branches de apoio.

Quando Usar o OneFlow

OneFlow se destina a ser umsubstituto imediato para Git-flow, o que quer dizer ser adequado em todas as situações em que o Git-flow é. De fato,é fácil migrar um projeto que esteja usando Git-flow para OneFlow.

Como foi proposta no artigooriginal, da mesma forma que o Git-flow, o Oneflow não pode ser usado em projetos em quetenham mais do que uma versão em produção.Mas essa deficiência pode ser sanada mantendo vários branches de release ativos, um para cada versão emprodução, como mostramos na seção sobre o Git-flow.

Da mesma forma que o Git-flow, o Oneflow é inadequado paraprojetos que usam Integração Contínua (ou Entrega Contínua). O OneFlow tambémnão foi feito para suportar a altafrequência de integração requerida pela Integração Contínua. Mais adiante discutiremos a Integração Contínua.

GitHub Flow

Apesar dosucesso do Git-flow, a complexidade desnecessária de sua estrutura de branching para aplicações web incentivoumuitas alternativas. Com o aumento da popularidadedo GitHub, não é surpresa que uma abordagem leve de branching usada por seus desenvolvedores se tornasse uma abordagem bem conhecida: o GitHubFlow. A melhor descrição foi feita por Scott Chacon em 2011 (Chacon 2011).

O GitHub Flowfoi conscientemente baseado em, e uma reação contra, o Git-flow. A diferença essencial entre os dois é umtipo diferente de produto, o que significa um contexto diferente, portanto, abordagens diferentes. O Git-flow assumiu um produto com várias versões em produção. O GitHub Flow assume uma única versão em produção com altafrequência de integração em um branch principalpronto para a release.

Com esse contexto, o branch de release não é necessário. Os problemas de produção sãocorrigidos da mesma forma que os features regulares, então não há necessidade debranch de hotfix, no sentido de que um branchde hotfix geralmente representa um desvio do processo normal.Remover esses branches simplifica drasticamente a estruturade branching para a branch principal e branches de feature.

O GitHub Flow chama sua branch principal de “master”. Osdesenvolvedores trabalham em branchesde feature. Chacon indica que os branches de feature podem ser uma única linha de código até durar duas semanas. O processo pretende funcionar da mesmaforma nessa faixa. O mecanismo de Pull Request faz parte da integração com o branch principal e é usadopara a revisão pré- integração.

No GitHub Flow deve-se assegurar que todo código, integradoou não, seja mantido no repositório central. Nesse caso, o desenvolvedor deve cometerno seu branch de featurelocalmente e subir regularmente seu trabalho para o branch com o mesmo nome no servidor. Isso permite que outros membros do time

possam ver no que está trabalhando, mesmoque ainda não esteja integrado ao trabalho de outras pessoas.

Alta Frequência de Integração

Chacon em seu artigo explica queum dos aspectos visados na criação do GitHubFlow é que o GitHub pratica a Entrega Contínua: asentregas para produção acontecem diariamente ou mesmo várias vezes ao dia.

A frequênciacom que fazemos integração tem um efeito extremamente poderoso sobre a forma como umtime trabalha. Estudos do Relatório State of DevOps (Forsgren et al. 2016)indicaram que os times de desenvolvimento de elite integram mais frequentemente do que os de baixo desempenho.

A integraçãofrequente aumenta a frequência de merges, mas reduz sua complex- idade e risco. O problema com grandesmerges não é tanto o trabalho envolvido com eles, é a incerteza desse trabalho. Em cerca de 80% das vezes, atémesmo grandes merges não dão problemas, mas ocasionalmente dão enormesproblemas. Essa dor ocasional acaba sendo pior que uma dor regular.

A integração frequente também alerta ostimes para conflitos com muito mais antecedência.Esta vantagem está conectada com a vantagem anterior, é claro. Mergesdifíceis são geralmente o resultado de um conflito latente no trabalho do time, surgindo apenas quando a integração (e testagem) acontece.

O que muita gente não percebe é que um sistema de controle de versão,como o Git, é uma ferramenta de comunicação. Permite que umdesenvolvedor veja o que outraspessoas do time estão fazendo.

Com integrações frequentes, não é apenasalertado rapidamente quando há conflitos, mas também fica mais ciente do que todos estão fazendo, e comoa base de código está evoluindo. Somos menos indivíduos hackeando independentemente e mais um time trabalhando em conjunto.

Branch Principal Pronto para Release

Para termos um branch prontopara release é essencial manter o branchprin- cipal saudável. Assim o head dobranch principal pode sempre ser colocado diretamente em produção.

Para conseguirmos esse feito, de manter a branch principal sempre saudável, é fundamentalque o time trabalhe com código deautoteste.

Esta prática de desenvolvimento quer dizer que, à medida queescrevemos o código de produção, tambémescrevemos um conjunto abrangente de testes automatizados para podermoster confiança de que, se esses testes passarem, o código não conterá bugs.

Há uma tensão em torno do graude testagem para fornecer confiança suficienteda saúde. Muitos testes mais completos requeremtempo demais para serem executados. Os times lidam com isso separando testes em vários estágios em um pipeline de implantação.

O primeiro estágio desses testes deve serexecu- tado rapidamente em menos de dez minutos, mas ainda serrazoavelmente abrangente. Referimos a tal suíte como a suíte de commit, embora seja frequentementereferida como “os testes unitários”, porque a suíte de commit é normalmente a união dos testes unitários.

Teste desoftware é um problema combinatório. Por exemplo, cada elemento de condição requer pelo menos dois testes: um com umresultado “verdadeiro” e um com um resultado“falso”. Como resultado, quase sempre o código de teste acaba ficando maior do que o código de produção.

O códigorodar sem bugs não é o suficiente para dizer que o código seja bom. De modo a manter umritmo constante de entrega, precisamos manter também a qualidade interna do código alta. As técnicas usadas para assegurar isso são: análise de programa estática (feita no build ou pelo IDE) erevisão pré-integração (feita pelo time).

A eficácia dobranch principal pronto para release é governada pela frequência de integração do time. Se o timenormalmente integra uma nova feature apenasuma vez por mês, então provavelmente estará em lugar ruim e uma insistência em um branch principal prontopara release pode ser uma barreira para sua melhoria.

O lugar ruim é que não podem responder tempestivamente às necessidades de mudanças do produto, porque o tempo de cicloda ideia à produção é muito longo. Também são mais suscetíveis de ter merges complexos porque cada feature é grande.

A chave para sair dessa armadilha é aumentar a frequência de integração, mas, em muitos casos, isso pode ser difícil de conseguirmantendo um branch principal prontopara release.

Nesse caso, muitasvezes é melhor desistir do branch principal pronto para release, incentivaruma integração mais frequente e usar branch de release para estabilizar o branch principalpara a produção.

No contexto da integração em alta frequência, um branchprincipal pronto para release tem avantagem óbvia da simplicidade. Não há necessidade de se preocupar com todas ascomplexidades dos vários branches do Git-flow. Mesmo hotfixes podem ser tratados como se fossem features regulares.

Além disso, manter o branch principal pronto para releaseincentiva uma dis- ciplina valiosa. Mantém a prontidão para produção no topodas mentes dos desenvolvedores, garantindo que problemas não entremgradualmente no sistema, seja como bugs ou como problemasde processo que retardam o ciclo do produto.

A disciplina completa de EntregaContínua, com desenvolvedores integrando muitas vezes ao dia no branch principal semquebrá-la, parece assustadoramente difícil para muitos.

No entanto, uma vezalcançada e se tornando um hábito, os times descobrem que isso reduz notavelmente oestresse e é relativamente fácil de continuar.É por isso que é um elemento-chave da fluência de entrega do modelo Agile Fluency (Shore and Larsen 2018).

Pull Requests(PRs)

O modelo dePull Request (PR) introduzido pelo GitHub é o modelo dominante de revisão pré-integração hoje. O conceitoestava disponível a partir do lançamento do GitHub em 2008 e revolucionou o desenvolvimento de software decódigo aberto e empresarial.

Google estava secretamente fazendo a mesma coisadesde 2005, e a apresentação de Guidovan Rossum (Kennedy 2006) do Mondrian em 2006 vazou isso para o mundo.

Chacon em seu artigo cita que oPull Request é utilizada mais como uma visão de conversação do branch do que como uma solicitação de integraçãopropriamente dita.Podem enviar um Pull Request para dizer “Preciso de ajuda ou revisão sobre isto” além de “Por favor, fazer merge disto”.

No GitHub Flow todo o código deve ser revisado em PullRequest antes de ser integrado. Para serem eficazes, essas revisões nãopodem ser muito rápidas. Porém, muitos times que usam revisões pré-integraçãonão as fazem rapidamente o suficiente.

O valioso feedback que podem fornecer, então, chega tarde demais para ser útil. Uma regra prática para as revisões é que ela deve ser começar rapidamente, no máximo duas horas depoisdo Pull Request, e durar cerca de metade do tempo de desenvolvimento da feature.

O branch de feature de curta duração pode ter recebidomuitos commits antes dodesenvolvedor iniciar o Pull Request. Alguns times fazem squash e rebase das mudanças em um commit único ligado diretamente ao branch principal, de forma amanter o histórico linear, à moda do OneFlow, antes de iniciar a revisão pré-integração.

Desenvolvimento BaseadonoTronco

Paul Hammant (Hammant 2017b)escreveu um site detalhado para explicar essaabordagem.

O Desenvolvimento Baseado no Tronco seconcentra em fazer todo trabalho no branchprincipal (chamado de “tronco”), usandobranches de curtaduração (menor que dois dias).

Times menores cometem diretamente no branch principal,da mesma forma que o OneFlow.

Times maiores podem usar branches de features de curta duração.

Os times podem usar branch de release (chamadode “branch para release”) ou branchprincipal pronto para release (chamadode “release a partir do tronco”).

Google (Potvinand Levenberg 2016) faz Desenvolvimento Baseado no Tronco e tem mais de 35 mil desenvolvedores nessetronco monorepo único com mais de 2 bilhões de linhas de código.

Figure 7: Desenvolvimento baseado no Tronco para times menores
Figure 8: Desenvolvimento baseado no Tronco para times maiores

Branches para Release

Se um time estiver fazendo implantações em produçãomensalmente, então também terão que subir versões de correções de bugsentre as releases planejadas. Parafacilitar isso, é comum que os times de Desenvolvimento Baseado no Tronco façam um branch de lançamento “just in time”:digamos alguns dias antes da release. Isso se torna um localestável, dado que os desenvolvedores estão mandando seus commits para o branch principal em velocidade máxima.

Os desenvolvedores continuam cometendo na maior frequênciapara o branch principale não desaceleram ou congelamcom a proximidade de uma release.Os desenvolvedores como um grupo não cometem no branch de release.

Um problema com a aplicação dos commits de correção nobranch de release, como é feito noGit-flow, é que é muito fácil deixar de fazer merge deles para o branchprincipal, principalmente porque fica mais difícil devido à divergência. A regressão resultante é muito embaraçosa.

A melhorprática para os times de Desenvolvimento Baseado no Tronco é criar os commits de correção no branch principal, e apenas, quandoestiverem funcionando lá, fazer o “cherry-pick” para o branch de release.

Figure 9: Cherry-picks num branch de release

Um cherry-pick é quando é feito merge de um commit de um branch paraoutro, mas osbranches não são mergeados. Ou seja, apenas um commit é mergeado, não os commits anteriores desde o ponto de ramificação.

No exemplo da figura, se fizéssemos o merge regular de F1 ao branch de release, isso incluiriaM4 e M5. O cherry-pick pode ser conflituoso, pois pode dependerde mudanças feitasem M4 e M5.

A desvantagemde escrever correções de releases no branch principal é que muitos times acham maisdifícil fazê-lo, e é frustrante corrigi-lo de uma maneira no branchprincipal e ter que retrabalhar no branch de release antes que a release possaocorrer. Isto é particularmente verdadeiro quando há pressão para a release sair logo.

O processo de fazer merge de commits do branch principalpara o branch de releaseusando “cherry-pick” é um papel para um único desenvolvedor do time. Mesmoassim, é uma atividade de tempo parcial. Talvez deva ter um rodízio de desenvolvedores para exercer o papel a cada dia.

Integração Contínua

Visto que um time tenha experimentado quea integração em alta frequência é mais eficientee menos estressante, uma pergunta natural é “até que frequência podemos chegar?”. Branchingpor feature implica um limite inferior ao tamanho do branch: não pode ser menor que um feature coesivo.

A IntegraçãoContínua (CI) aplica um gatilho diferente para a integração: integra sempre que fizer um pedaço do featuree seu branch ainda estiversaudável. Não há nenhumaexpectativa de que o feature esteja completo, como no Git-flow, apenas que tenha havido umaquantidade de mudanças de valor na base de código.

A regra de ouro é “todo mundo comete ao branch principal pelo menos uma vez por dia”. Na prática, a maioriados praticantes faz a integração muitas vezes por dia.

Quando se faz Pull Request para revisão pré-integração, seadmite branches de featurescom duração máximade dois dias: um dia para o desenvolvimento isoladomais um dia para o Pull Request.

Os desenvolvedores que usam a CI precisam se acostumar coma ideia de se atingir pontos de integração com um feature parcialmente construído.Precisam considerar como fazerisso sem expor um feature parcialmente construído no sistema em execução.

Branch por abstraçãoé uma técnica (Hammant 2017a) para fazer uma mudançaem grande escala gradualmente, que permite fazer releases da aplicação regularmente enquantoa mudança ainda está em andamento.

Se não houver maneira de ocultar facilmente o featureparcial, podemos usar Flags de Feature. Além de esconder um feature parcialmenteconstruído, tais flags tambémpermitem que o feature seja revelado seletivamente a um subconjunto de usuários: muitasvezes útil para uma release gradual de um novofeature.

A integração de features parcialmenteconstruídos preocupa particularmente aqueles que se preocupam em manter o branchprincipal saudável. Consequente- mente,aqueles que usam a CI tambémprecisam de Código de Autoteste para haver confiança de que terfeatures parcialmente construídos no branch principalnão aumenta a ocorrência de bugs.

Com esta abordagem, os desen- volvedoresescrevem testes para os features parcialmente construídos conforme estão escrevendo esse código dofeature, submetendo juntos tanto o código dofeature quanto os testes no branch principal.

Uma pesquisa feita por Luke e Prince (Luke and Prince 2017) descobriu que a maioria dos participantes não conseguia definir de formacompleta a CI. No entanto, muitos que deram uma definição incompleta, faltando especificamente a necessidade de se integrar ao branchprincipal diariamente, disseram estarem praticandoa CI.

Com exceção de pouco mais de 10%, todos que usavam uma ferramentade CI/CD disseram estarem praticando CI. Parece que o uso de uma ferramenta de CI/CD é bastantesinônimo de fazer CI. Um nome melhor para essas ferramentas seria ferramentas de build contínuo.

Dos workflows discutidos neste artigo, somenteo GitHub Flow e o Desenvolvi- mentoBaseado no Tronco suportam a alta frequência de integração exigida pela CI.

Branchingpor Feature × Integração Contínua Martin Fowler (Fowler 2020) fez uma comparação muitointeressante entre o Branching por Feature completo e a IntegraçãoContínua.

O Branching por Feature completo, como usado no Git-flow, parece ser a estratégia de branching maiscomum na indústria no momento, mas há um grupovocal de praticantes que argumentam que a Integração Contínua égeralmente uma abordagem superior. A principal vantagem que a IntegraçãoContínua fornece é que suporta maior frequência de integração, muitas vezes muito maior.

Como já indicamos, a maior frequência de integração leva auma integração menos complicada emenos medo de integração. Isso é muitas vezes uma coisa difícil de se comunicar. Em um mundo de integração a cadapoucas semanas ou meses, a integraçãoprovavelmente será uma atividade muito preocupante. Pode ser difícil acreditar ser algo que possa ser feita muitasvezes por dia.

Mas a integração é uma daquelas coisas em que a frequência reduz a dificuldade. É uma noção aparentemente masoquista: “se dói, faça-ocom mais frequência”. Mas, quanto menoresas integrações, menosprovável é que se transforme num merge monstruosode miséria e desespero. Com branchingpor features, isso requer featuresmenores: de dias, não de semanas.

A IntegraçãoContínua permite que um time obtenha os benefícios da integração em alta frequência, enquanto desacopla ocomprimento do feature da frequência deintegração. Se um time prefere features de uma ou duas semanas, a Integração Contínua permite quefaça isso, obtendo assim todos os benefícios da maior frequência de integração. Osmerges são menores, exigindo menos trabalho para resolver os conflitos.

Mais importante ainda, comoexplicamos na seção sobre GitHubFlow, fazer merges com mais frequência reduz o risco de um merge problemático,tanto elimina as surpresas ruins que isso traz, como reduz o medo geral de fazer merge. Se surgirem conflitos no código, aintegração em alta frequência os descobrirá rapidamente, antes mesmoque levem a esses problemas de integrações dificultosas.

A desvantagem clara da Integração Contínua é que falta o senso de fechamento dessaintegração climática com o branch principal. Não é só uma comemoração perdida, é um riscose o time não for bom o bastante para manter um branch saudável.

Manter todosos commits de um feature juntos torna possível decidir mais tarde sobre a inclusão de um featureem uma próxima versão. Os times que querem fazer Integração Contínua devemdesenvolver um regime de testes fortepara poderem ter certeza de que o branch principal permanece saudável, mesmo com muitas integrações por dia.

Alguns times acham essa habilidade difícil de imaginar, mas outras acham possível elibertadora. Esse pré-requisito quer dizer que o branching por feature émelhor para times que não forçam um branch saudável e requerem branches derelease para estabilizar o código antes da release.

Enquanto o tamanho e a incerteza dos merges são osproblemas mais óbvios com o branching por feature, o maior problema com isto pode ser dele deter arefatoração. A refatoração é mais eficaz quando é feita regularmente e com poucoatrito.

A refatoração reduzirá conflitos,se esses conflitos não forem detectadose resolvidos rapidamente, o merge fica preocupante. A refatoração funciona melhor com uma alta frequência deintegração, então não é surpresa que se tornou popularcomo parte da Extreme Programming (Beck 2000), que tambémtem a Integração Contínua como uma de suas práticas originais.

O branching por featuretambém desencoraja os desenvolvedores de fazer mudançasque não são vistas como parte do feature que está sendoconstruído, o que prejudica acapacidade de refatoração para melhorar de forma constante uma base de código.

Quando nos deparamos com estudos científicos de práticas de DevOps, geralmente não ficamos convencidos devido a sérios problemas com suametodologia.

Uma exceção é o State of DevOps Report (Forsgren et al. 2016), que desenvolveu uma métrica de desempenho de entrega desoftware, que se correlacionou a uma medida mais ampla de desempenho organizacional,que se correlaciona com métricasde negócio como retorno do investimento e rentabilidade.

Em 2016, avaliarampela primeira vez o Desenvolvimento Baseado no Tronco e descobriram que contribui para um maior desempenho de desenvolvimento de software:

A ideia que osdesenvolvedores devem trabalhar em pequenos lotes a partir do mestre ou tronco, em vez de embranches de features de longa duração é ainda uma das ideiasmais controversas do cânone ágil,apesar do fato que é a normaem organizações de alto desempenho, como o Google (Potvin and Levenberg 2016).

Descobrimos que ter branches comduração muito curtas (menos que um dia) antes de serem mergeadosao tronco e menos de três branchesativos no total, são aspectos importantes daEntrega Contínua, e todos contribuem para um desempenho mais elevado. Assim como fazer merge ao tronco ou mestre diariamente.

Conclusão

Neste artigo, investigamos diferentes aspectos que podemimpactar o nível de dificuldade em resolverconflitos de merge. Avaliamos as práticas que são usadas em quatro workflows de Git nessequesito.

Branching é uma técnica poderosa. Poderosa, mas fácil de usar em excesso, muitas vezes se torna uma armadilha para os incautos einexperientes. Os sistemas de controle de versão podemajudar a controlar branches, rastreando cuidadosamenteas mudanças, mas no final só podem agir como testemunhas dos problemas.

Existemproblemas cotidianos, como vários desenvolvedores contribuindo para uma única base decódigo, onde o uso criterioso de branching é essencial. Mas devemos sempre ter cuidado com isso e lembrar a observação de Paracelso que “a diferençaentre o remédio e o veneno está na dose”.

Então a primeira recomendação é: sempre antes de abrir um branch defina precisamente seu escopo,certifique-se que é o menor possível e não realize tarefas fora deste escopo nele. Quanto maior for o escopo da branch, maior será o risco de ter um merge problemático.

Daí a próxima diretriz:diminua o número de branches ativos, se possível a menos de três.Existe uma maneira de resolver seu problema diminuindo a complexidade ou a melhorando amodularidade? Pode melhorar seu pipeline de implantação? Uma tag é suficiente?

Que mudançasem seu processo tornariam esse branch desnecessário? É bem provável que obranch seja, de fato, a melhor rota aseguir agora, mas é um fedor que está o alertando para um problema mais profundo que deve ser resolvido nos próximos meses. Livrar-seda necessidade de um branch é geralmente uma coisa boa.

Tente dobrar sua frequência de integração. Há obviamente um limite aqui, mas não estará pertodele a menos que esteja na zona de Integração Contínua. Haverá barreiras para a integração com mais frequência, mas são muitasvezes exatamente as que precisam ser eliminadas, de modo a melhorar o seu processode desenvolvimento.

Como o merge é a parte maisdifícil do branching, preste atenção noque está dificultando o merge. Às vezes é um problema de processo, às vezes é uma falha do design de alto nível.

Qualquerproblema de merge, especialmente um que causa uma crise, é um sinal para melhorar aeficácia de um time. Lembre-se que os erros só são valiososquando se aprendecom eles.

Referências
Beck, Kent. 2000. ExtremeProgramming Explained: Embrace Change. Addison- Wesley professional.
Brindescu, Caius, IftekharAhmed, Rafael Leano, and Anita Sarma. 2020. “Planning forUntangling: Predicting the Difficulty of Merge Conflicts.” In 2020 IEEE/ACM 42nd International Conference onSoftware Engineering (ICSE), 801–11. IEEE.
Chacon, Scott.2011. “GitHub Flow.” Scottchacon.com.http://scottchacon.co m/2011/08/31/github-flow.html.
Driessen, Vincent. 2010. “A Successful Git Branching Model.” Nvie.com. https:
//nvie.com/posts/a-successful-git-branching-model/.
Forsgren, Nicole, Jez Humble, Gene Kim, Alana Brown, and Nigel Kirsten.2016. “2016 State of DevOps Report.” https://services.google.com/fh/files/misc/state-of-devops-2016.pdf.
Fowler, Martin.2006. “Continuous Integration.” Martinfowler.com. https:
//martinfowler.com/articles/continuousIntegration.html.
———. 2020. “Patterns for Managing Source Code Branches.” Martinfowler.com. https://martinfowler.com/articles/branching-patterns.html.
Hammant, Paul. 2017a.“Branch by Abstraction.” Trunk Based Development. https://trunkbaseddevelopment.com/branch-by-abstraction/.
———. 2017b.“Trunk Based Development.” Trunkbaseddevelopment.com. https:
//trunkbaseddevelopment.com/.
Kennedy,Niall. 2006. “Google Mondrian: Web-Based Code Review and Storage.” NiallKennedy. Niall Kennedy. https://www.niallkennedy.com/blog/2006/11/google-mondrian.html.
Luke, Emily, and Suzie Prince. 2017. “No One Agrees How to Define CI or CD.” GoCD Blog.https://www.gocd.org/2017/05/09/continuous-integration- devops-research/.
Nelson,Nicholas, Caius Brindescu, Shane McKee, Anita Sarma, and Danny Dig. 2019. “The Life-Cycle of Merge Conflicts: Processes, Barriers, and Strategies.” Empirical SoftwareEngineering 24 (5): 2863–2906.
Potvin, Rachel, and Josh Levenberg. 2016. “Why Google Stores Billions of Lines of Code in a Single Repository.” Communications of the ACM 59 (7): 78–87. Ruka, Adam. 2015. “GitFlow Considered Harmful.” Endoflineblog.com. https:
//www.endoflineblog.com/gitflow-considered-harmful.
Shore, James, and Diana Larsen. 2018.“The Agile Fluency Model.” Martin- fowler.com. https://www.martinfowler.com/articles/agileFluency.html.

Leave a Reply