O controle de código-fonte é o alicerce do desenvolvimento de software. Sem algum tipo de sistema de controle de versão em vigor, o senhor não pode se considerar um engenheiro de software. Se estiver usando um sistema de controle de código-fonte de qualquer tipo, o senhor está fazendo o controle de versão dos arquivos quase que por definição. O conceito de controle de versão está profundamente incorporado em todos os sistemas de controle de código-fonte. O senhor não pode evitá-lo.
Mas há outro conceito, igualmente fundamental para o controle de fontes, que é usado com muito menos frequência na prática. Esse conceito é ramificação. A documentação do Subversion tem uma boa descrição leiga e decente da ramificação:
Suponha que seu trabalho seja manter um manual para uma determinada divisão de sua empresa. Um dia, uma divisão diferente pede ao senhor o mesmo manual, mas com algumas partes modificadas especificamente para eles, pois fazem as coisas de forma um pouco diferente.
O que o senhor faz nessa situação? O senhor faz a coisa óbvia: faz uma segunda cópia do documento e começa a manter as duas cópias separadamente. À medida que cada departamento lhe pede para fazer pequenas alterações, o senhor as incorpora a uma ou outra cópia. Mas muitas vezes o senhor acaba querendo fazer a mesma alteração nas duas cópias. Por exemplo, se o senhor descobrir um erro de digitação na primeira cópia, é muito provável que o mesmo erro exista na segunda cópia. Afinal, os dois documentos são praticamente os mesmos; eles diferem apenas em aspectos pequenos e específicos.
Esse é o conceito básico de uma ramificação: uma linha de desenvolvimento que existe independentemente de outra linha, mas ainda assim compartilha um histórico comum. Uma ramificação sempre começa como uma cópia de algo e continua a partir daí, gerando seu próprio histórico.
Se o senhor nunca usa o recurso de ramificação do seu sistema de controle de código-fonte, o senhor está realmente tirando o máximo proveito do seu sistema de controle de código-fonte?
Acho que o quase todos os clientes que visito quase não estão usando a ramificação. A ramificação é amplamente mal compreendida e raramente implementada, embora a ramificação, assim como o controle de versão, esteja no centro do controle de código-fonte e, portanto, da engenharia de software.
Talvez a maneira mais acessível de pensar nas ramificações seja como universos paralelos. Eles são lugares onde, por qualquer motivo, a história não aconteceu da mesma forma que no universo do senhor. Desse ponto em diante, esse universo pode ser ligeiramente diferente – ou pode ser radical e totalmente transformado. Como a série de quadrinhos da Marvel What If?, a ramificação permite que o senhor responda a algumas perguntas interessantes e possivelmente até perigosas do tipo “e se” com seu desenvolvimento de software.
Os universos paralelos oferecem possibilidades infinitas. Eles também permitem que o senhor permaneça em segurança no universo particular de sua escolha, completamente isolado de quaisquer eventos em outros universos alternativos. Um universo alternativo em que os nazistas venceram a Segunda Guerra Mundial é uma ideia interessante, desde que não tenhamos que viver nesse universo. É possível que existam milhares desses universos paralelos. Embora a ramificação ofereça o apelo sedutor da possibilidade infinita com muito pouco risco, ela também traz consigo algo muito menos desejável: complexidade infinita.
A série de quadrinhos da DC Crise nas Infinitas Terras é um conto de advertência sobre os problemas que o senhor pode encontrar se começar a criar muitos universos paralelos.
Antes de Crise nas Infinitas Terras, a DC era notória por seus problemas de continuidade. A história de fundo de nenhum personagem, dentro dos quadrinhos, era totalmente autoconsistente e confiável. Por exemplo, o Super-Homem originalmente não podia voar (em vez disso, ele podia saltar mais de um oitavo de milha), e seus poderes vieram do fato de ter evoluído em um planeta com gravidade mais forte do que a da Terra. Com o tempo, ele passou a poder voar, seus poderes foram explicados como provenientes do sol e foi inventada uma história de fundo mais complexa (a história de origem do “último sobrevivente de Krypton”, agora familiar). Mais tarde, ela foi alterada para incluir suas façanhas como Superboy. A história foi alterada ainda mais para incluir a Supergirl, a cidade engarrafada de Kandor e outros sobreviventes de Krypton, diluindo ainda mais a ideia original de que o Super-Homem havia sido o único kryptoniano a sobreviver à destruição de seu mundo. Havia também a questão do envelhecimento dos personagens; por exemplo, o Batman, um ser humano nascido na Terra sem superpoderes, manteve sua juventude e vitalidade até a década de 1960, apesar de ter sido um herói ativo durante a Segunda Guerra Mundial, e seu companheiro Robin nunca pareceu envelhecer além da adolescência em mais de 30 anos.
Essas questões foram abordadas durante a Era de Prata, quando a DC criou mundos paralelos em um multiverso: A Terra-Um era o Universo DC contemporâneo, que havia sido retratado desde o advento da Era de Prata; a Terra-Dois era o mundo paralelo onde ocorreram os eventos da Era de Ouro e onde os heróis que estavam ativos durante esse período envelheceram de forma mais ou menos realista desde então; A Terra-Três era um mundo “oposto”, onde os heróis eram vilões e os eventos históricos aconteciam ao contrário do que acontecia na vida real (como, por exemplo, o presidente John Wilkes Booth sendo assassinado por um rebelde chamado Abraham Lincoln); a Terra-Primeira era ostensivamente o “mundo real”, usado para explicar como os funcionários da DC na vida real (como Julius Schwartz) podiam ocasionalmente aparecer nas histórias em quadrinhos; e assim por diante. Se algo acontecesse fora da continuidade atual (como as chamadas “Histórias Imaginárias”, que eram um elemento básico das publicações da Era de Prata da DC), isso era explicado como se estivesse acontecendo em um mundo paralelo, uma premissa não muito diferente da atual marca “Elseworlds” da empresa.
Comece a fazer malabarismos com muitos universos paralelos de uma só vez e o senhor acabará perdendo alguns. Na maioria dos sistemas de controle de código-fonte, é possível criar centenas de ramificações sem nenhum problema de desempenho; é o sobrecarga mental de manter o controle de todas as ramificações com as quais o senhor realmente precisa se preocupar. Os cérebros dos desenvolvedores não podem ser atualizados exatamente da mesma forma que o servidor de controle de código-fonte, portanto, esse é um problema sério.
Acho que a analogia de universos paralelos ajuda os desenvolvedores a entender o conceito de ramificação, juntamente com seus inevitáveis prós e contras. Mas a partir daí não fica muito mais fácil. A ramificação é um fenômeno complexo. Há dezenas de maneiras de ramificar e ninguém pode realmente dizer se o senhor está fazendo certo ou errado. Aqui estão algumas alguns padrões comuns de ramificação que o senhor talvez reconheça.
Filial por versão
Cada versão é uma nova ramificação; as alterações comuns são mescladas entre as versões. As ramificações são eliminadas somente quando as versões não são mais compatíveis.
Filial por promoção
Cada camada é uma ramificação permanente. À medida que as alterações são concluídas e testadas, elas passam pelo portão de qualidade e são “promovidas” como fusões em camadas sucessivas.
Filial por tarefa
Cada tarefa de desenvolvimento é uma ramificação nova e independente. As tarefas são mescladas na ramificação principal permanente à medida que são concluídas.
Ramificação por componente
Cada componente arquitetônico do sistema é uma ramificação nova e independente. Os componentes são mesclados na ramificação principal à medida que são concluídos.
Filial por tecnologia
Cada plataforma de tecnologia é uma ramificação permanente. Partes comuns da base de código são mescladas entre cada plataforma.
O senhor pode notar alguns temas emergentes nesses padrões de ramificação:
- Todas as ramificações têm um ciclo de vida claramente definido. Ou elas vivem para sempre ou acabam sendo eliminadas.
- Todas as ramificações são criadas com a intenção de serem mescladas em algum lugar. Uma ramificação sem uma mesclagem é inútil.
- À medida que adicionamos ramificações, nosso modelo de desenvolvimento se complica.
Mas essa complicação geralmente é justificada. Quanto mais desenvolvedores o senhor tiver em um projeto, maiores serão as chances de um desses desenvolvedores verificar algo realmente ruim no controle de origem e interromper o trabalho de todos os outros. É uma estatística simples. As pessoas cometem erros. Quanto mais desenvolvedores o senhor tiver, mais erros ocorrerão. E quanto mais desenvolvedores o senhor tiver, maiores serão as consequências quando o trabalho de todos for simultaneamente interrompido por um check-in incorreto. Então, quais são as nossas opções?
- Produtividade máxima
Todos trabalham na mesma área comum. Não há ramificações, apenas uma longa e ininterrupta linha reta de desenvolvimento. Não há nada para entender, portanto, os check-ins são extremamente simples, mas cada check-in pode interromper todo o projeto e fazer com que todo o progresso seja interrompido. - Risco mínimo
Cada pessoa do projeto trabalha em sua própria filial privada. Isso minimiza os riscos; todos trabalham de forma independente e ninguém pode interromper o trabalho dos outros. Mas também acrescenta uma sobrecarga incrível ao processo. A colaboração torna-se quase comicamente difícil – o trabalho de cada pessoa precisa ser meticulosamente mesclado com o trabalho de todos os outros para que se possa ver até mesmo a menor parte do sistema completo.
A resposta geralmente está em algum lugar entre esses dois extremos. Como tudo o mais, a ramificação pode ser usada de forma abusiva. Chris Birmele observa que a ramificação tem seu próprio conjunto de antipadrões o senhor deve estar atento ao:
Merge Paranoia | A fusão é evitada a todo custo, devido ao medo das consequências. |
Merge Mania | A equipe passa uma quantidade excessiva de tempo mesclando ativos de software em vez de desenvolvê-los. |
Big Bang Merge | A mesclagem foi adiada até o final do esforço de desenvolvimento e é feita uma tentativa de mesclar todas as ramificações simultaneamente. |
Mesclagem sem fim | A atividade de mesclagem parece nunca terminar; sempre há mais para mesclar. |
Mesclagem de caminho errado | Um ativo de software é mesclado com um anterior anterior. |
Branch Mania | As ramificações são criadas com frequência e sem motivo aparente. |
Ramificações em cascata | As ramificações nunca são mescladas de volta à linha de desenvolvimento principal. |
Ramificações misteriosas | Ninguém sabe dizer ao senhor para que servem os galhos. |
Ramificações temporárias | A finalidade de uma ramificação está sempre mudando; ela serve efetivamente como um espaço de trabalho “temporário” permanente. |
Filiais voláteis | Uma ramificação instável é compartilhada por outras ramificações ou mesclada em outra ramificação. |
Congelamento do desenvolvimento | Todas as atividades de desenvolvimento são interrompidas durante a ramificação, a fusão e a criação de novas linhas de base |
Muro de Berlim | As ramificações são usadas para dividir os membros da equipe de desenvolvimento, em vez de dividir o trabalho que estão realizando. |
Se o senhor conseguiu ler até aqui, talvez possa entender por que tantas equipes de desenvolvimento de software estão completamente convencidas do controle de versão, mas hesitam em adotar a ramificação e a mesclagem. É um recurso poderoso e fundamental de controle de versão, sem dúvida, mas também é complicado. Se o senhor não for cuidadoso, a estratégia de ramificação errada poderá causar mais danos ao seu projeto do que benefícios.
Ainda assim, peço aos desenvolvedores que façam um esforço para entender a ramificação… realmente e explorar o uso de estratégias de ramificação quando apropriado em seus projetos. Se feito corretamente, o custo mental da taxa de ramificação é insignificante em comparação com o os benefícios do desenvolvimento simultâneo que ele possibilita. Adote a ideia de universos paralelos em seu códigoe o senhor poderá descobrir que pode fazer mais, com menos riscos. Apenas tente evitar uma crise de bases de código infinitas enquanto o senhor estiver fazendo isso.