Páginas

SyntaxHighlighter

terça-feira, 9 de novembro de 2010

A arte de desenvolver sem branches

Ultimamente eu e meu time estamos evitando ao máximo a criação de branches no SVN. Nosso objetivo é comitar apenas no trunk e tentar manter a aplicação sempre releaseable. Acreditamos que isso ajuda a manter o fluxo de entregas constante e o caminho aberto para o sonhado continuous deployment e delivery.

Nossas últimas experiências com feature branch foram desagradáveis. Além dos (muito) chatos, às vezes longos, e arriscados merges envolvidos na criação de um novo branch, o que mais incomoda é a falta de integração contínua e o "tumulto" causado quando o branch é reintegrado ao trunk - dependendo do tamanho e o grau de intrusão é necessária uma longa fase de estabilização.

Para evitar esse transtorno, uma prática que começamos a adotar é, ao invés de criar branches no controlador de versão, fazer as ramificações no próprio código fonte - branch by abstraction. Basicamente o que fazemos é criar flags em arquivos de propriedades da aplicação e utilizá-las para esconder/desabilitar uma feature incompleta (GUI) ou para chavear entre uma implementação ou outra.

Características do design, por exemplo utilização de patterns como  Dependency InjectionStrategy e Factory, facilitam bastante o trabalho. Mais que isso, já vi casos de refactorings para suportar esse tipo de chaveamento, melhorar o design através da redução do acoplamento, e nascimento de novos componentes!

É uma estratégia bem simples de adotar e que traz alguns benefícios. Como continuamos a comitar no mainline, o código segue sendo continuamente integrado pelo Hudson juntamente com as demais features. Se bem planejado e estruturado, é possível entregar a feature aos poucos, em pequenas partes, reduzindo o risco das implantações. A cada release pouquíssimo código novo é implantado e consequentemente a identificação de eventuais bugs ou efeitos colaterais indesejados é antecipada, reduzindo o custo de investigação e correção.

Para ilustrar: suponha que temos uma nova demanda que alterará drasticamente o cerne da aplicação. Pior que isso, a estimativa para terminá-la é de alguns dias, podendo chegar até uma semana. Em paralelo várias outras melhorias e correções de bugs menores irão acontecer. O objetivo é entregar o quanto antes tudo o que for ficando pronto e evitar ao máximo o "represamento" de código.

Enxergo três opções: A primeira, e pior de todas, é sair implementando tudo no trunk deixando a aplicação unreleaseable por um longo período de tempo. A segunda é criar uma feature branch para a alteração maior e desenvolver as demandas menores no trunk, dessa forma conseguimos manter o ritmo de entregas, mas o código da alteração maior é "estocado". A última é criar um code flip, implementar todas as demandas no trunk só que deixando a grande alteração dormente e escondida. Apesar de incompleto e invisível para o usuário o código faz parte dos binários entregues, passou pela integração contínua e por QA - já está sendo aceito, está implantado!

Alguns pontos de atenção caso opte pela feature flag:
  • Aposentar os code flips e garantir a remoção do código obsoleto;
  • Manter ambos os caminhos (code paths) enquanto as duas versões coexistirem.
Volto a mencionar que esse tipo de decisão precisa ser bem pensada. Já tivemos períodos tensos com bloqueamento de implantações - tanto com branches normais como abstratas. Se seu cliente estiver "mal acostumado" com várias entregas por semana, por exemplo, e você começar a faze-lás quinzenalmente, pode ter certeza que ele vai reclamar. Além do que, a sensação de receber software concreto quase que diariamente, é mil vezes melhor que um "burndown na pinta".

É importante ressaltar que apesar do título, não estou dizendo que nunca criarei branches ou que é sempre melhor evitá-los. Sei que existem casos onde não há outra saída. O importante é saber tomar esse tipo de decisão - focar nas entregas mais frequentes.

Para se aprofundar melhor: