Book cover

Página Principal | Modo Dark

Fundamentos de Manutenção de Software

Marco Tulio Valente

9 Processos

Agile processes harness change for the customer’s competitive advantage. ― Agile Manifesto, 2001

Neste capítulo, vamos tratar de como manutenção de software é contemplada em processos modernos de desenvolvimento de software. Primeiro, na Seção 9.1, vamos comentar sobre a visão que métodos ágeis possuem sobre mudanças de software e sobre a importância de ter times autônomos, capazes de desenvolver, manter e evoluir seus sistemas. Em seguida, na Seção 9.2, vamos apresentar algumas práticas — relacionadas diretamente com manutenção e evolução de software — que esses times costumam realizar com frequência, incluindo revisão de código, slacks, post mortems, congelamento do código (code freeze) e recompensas por descoberta de bugs (bug bounties). Depois, na Seção 9.3, vamos comentar sobre métricas que os times podem usar para acompanhar o trabalho que está sendo realizado, incluindo métricas de fluxo, qualidade, código e negócios. Por fim, vamos comentar sobre dois temas mais operacionais, relacionados com a forma de uso de sistemas de controle de versão. Especificamente, na Seção 9.4, vamos tratar de modelos de branches, incluindo Git-flow, GitHub Flow e Desenvolvimento Baseado no Trunk (TBD). Para concluir o capítulo, na Seção 9.5, vamos apresentar um padrão para escrita de mensagens de commits, chamado de conventional commits.

9.1 Introdução

Atualmente, os processos de desenvolvimento de software mais comuns são ágeis, tais como Scrum, Kanban ou XP. Uma parte do sucesso desses processos se explica pela compreensão que eles tiveram sobre o papel de mudanças em desenvolvimento de software. Ou seja, software, como o próprio nome sugere, é flexível e pode ser adaptado de forma mais simples e barata do que a maioria dos produtos físicos.

Imagine um edifício residencial: é quase impossível realizar mudanças significativas nele depois de pronto. Normalmente, não dá para construir um novo andar ou aumentar o tamanho dos apartamentos. Porém, mudanças e evoluções em software são mais viáveis, pelo menos em linhas gerais. Por isso, na maioria dos sistemas, não vale a pena investir tempo demais em um levantamento de requisitos completo e em um projeto detalhado (como ocorre no caso de um edifício residencial). Em vez disso, com software, é mais importante começar, até para entender, na prática, o problema e as necessidades dos clientes. Como consta de um dos doze princípios do Manifesto Ágil, que usamos na abertura deste capítulo, não devemos ter medo de mudanças, mas sim tirar proveito delas para gerar valor para os nossos clientes.

Porém, mesmo com processos ágeis, não é possível mudar de ideia a qualquer momento. Nenhum time de desenvolvimento consegue trabalhar se hoje a prioridade é uma funcionalidade X, mas amanhã ela muda para uma funcionalidade Y. Por isso mesmo, sprints devem possuir um objetivo claro, normalmente implementar um conjunto de histórias de usuários. Com isso, mudanças e adaptações podem acontecer, mas apenas entre sprints, como ilustrado na próxima figura. Durante um sprint, os times devem estar blindados contra mudanças. Por exemplo, o Guia do Scrum afirma categoricamente que durante um sprint nenhuma mudança pode comprometer seu objetivo.

Sprints são blindados contra mudanças; elas somente podem ocorrer entre sprints

Outra novidade importante que veio com métodos ágeis diz respeito à organização dos times de desenvolvimento e manutenção. Anteriormente, era comum ter times somente de desenvolvimento e times somente de manutenção (também chamados, às vezes, de times de sustentação). Ou seja, um time implementava e outro time mantinha. Essa dicotomia, no entanto, gera um problema grave: ela impede que o próprio time tenha que lidar com as consequências de um desenvolvimento mal feito. Ou seja, o time de desenvolvimento pode não se preocupar tanto com questões de código limpo, comentários, código flexível a mudanças, logging, dívida técnica, etc., tal como vimos nos capítulos anteriores. O motivo é que ele vai desenvolver, mas depois outro time será responsável pela manutenção.

Por isso, atualmente, o modelo mais comum de organização de times é baseado em squads. Uma das principais características desses times é sua responsabilidade fim a fim sobre o produto, desde a concepção, passando pela implementação, bem como pela sua manutenção e evolução. Com isso, favorece-se um estilo de trabalho que põe a própria pele em risco (skin in the game). Por exemplo, se o time criar muita dívida técnica, depois caberá também a ele pagar essa dívida.

9.2 Práticas para Manutenção de Software

Nesta seção, vamos comentar sobre práticas que podem ajudar na manutenção de software, incluindo revisão de código, slacks, post mortems, congelamento do código (code freeze) e recompensas por descoberta de bugs (bug bounties).

Outras práticas, mais tradicionais, também desempenham um papel importante em atividades de manutenção, como os quatro eventos típicos de Scrum (Planejamento, Reuniões Diárias, Revisão e Retrospectiva). No entanto, não vamos descrever tais eventos, pois eles já foram abordados no nosso livro anterior (Engenharia de Software Moderna, link).

9.2.1 Revisão de Código

Revisão de código é uma das práticas mais importantes para garantir a saúde a médio e longo prazo da base de código de um sistema. Ela é adotada por várias empresas que desenvolvem software. Por exemplo, o Stack Overflow Survey de 2019 incluiu uma pergunta sobre a adoção de revisão de código. Dentre os mais de 70 mil desenvolvedores que responderam a essa pergunta, apenas 23% não usavam revisão de código no seu trabalho.

A ideia de revisão de código é a seguinte: todo código desenvolvido por um desenvolvedor tem que ser, em seguida, analisado por pelo menos um outro desenvolvedor, chamado de revisor. O revisor pode adicionar comentários no código sob revisão, procurando esclarecer dúvidas, sugerindo melhorias, indicando bugs, etc.

Assim, estabelece-se um diálogo — na forma de uma troca de comentários — entre o autor do código e o seu revisor. Como resultado, o autor pode alterar a sua implementação para atender a alguma sugestão do revisor que ele achou procedente. Ou então, ele pode replicar e justificar que ela não faz sentido. Espera-se que após esse diálogo o código seja aprovado pelo revisor e possa ser integrado ao repositório do projeto. No entanto, o revisor pode recomendar também a rejeição definitiva da implementação proposta.

Normalmente, revisão de código é uma das funcionalidades oferecidas por plataformas de gerenciamento de código. Por exemplo, no GitHub, temos o conceito de Pull Requests (PRs), que permite que desenvolvedores submetam código para ser revisado e integrado no repositório principal de um projeto.

Literatura Científica: Em 2013, Alberto Bacchelli e Christian Bird realizaram um estudo sobre revisão de código com 873 desenvolvedores e testadores da Microsoft (link). As principais motivações para a realização de revisões de código, na opinião dos participantes desse estudo, foram as seguintes:

O último benefício acima pode ocorrer nos dois sentidos, isto é, o autor pode aprender com os comentários do revisor e vice-versa. Na verdade, revisão de código tem um efeito colateral importante que é evitar a formação de ilhas de conhecimento em um projeto. Em vez disso, ela ajuda a socializar o conhecimento sobre o código e a criar uma cultura de colaboração e troca de ideias entre os membros do time.

O que Revisar? Segue uma lista de questões que devem ser consideradas em revisões de código (indicamos também o capítulo no qual tratamos de cada item; quando o tratamento ocorreu no nosso livro anterior, Engenharia de Software Moderna, usamos a sigla ESM):

Recomendações para Revisores de Código: De forma geral, revisores devem focar em identificar problemas claros no código analisado, evitando sugestões que não possuem benefícios evidentes. Também é importante evitar comentários subjetivos ou baseados em preferências pessoais, como comentários sobre nomes de variáveis. A comunicação deve ser sempre educada e profissional, restrita ao código analisado, sem envolver questões pessoais.

Além disso, é importante justificar todas as sugestões, principalmente ao interagir com desenvolvedores menos experientes. Deve-se também adotar uma linguagem colaborativa, usando expressões como nós ou a gente. Por fim, em situações de forte divergência ou quando a discussão não evoluir de forma satisfatória, pode-se recorrer a uma conversa síncrona para buscar consenso, embora isso deva ser exceção.

Recomendações para Autores de PRs: Os autores também devem ser profissionais e educados em suas respostas. Eles devem entender que revisão de código não é um exame de proficiência. Ou seja, como autor, não leve a revisão para o lado pessoal e nunca imagine que o revisor está julgando a sua competência.

Outro ponto importante é que os autores devem submeter PRs pequenos, caso queiram obter uma resposta rápida e mais proveitosa dos revisores. Por exemplo, os autores do livro Software Engineering at Google recomendam que um PR deve ter no máximo 200 linhas de código.

Automatizando a Revisão: Antes de concluir, gostaríamos de comentar que diversos problemas tratados em uma revisão de código podem ser detectados de forma automática por meio de ferramentas de análise estática. Por exemplo, questões sobre convenções de nomes de identificadores (camelCase, snake_case, etc.), organização de código e estilo de indentação (tabs ou espaços, por exemplo) podem ser padronizadas por meio de linters. Com isso, evita-se que um revisor tenha que perder tempo com tais questões. Com os avanços de Modelos de Linguagens, diversas soluções baseadas em IA estão sendo propostas para automatizar atividades de revisão de código.

9.2.2 Slacks

Desenvolvimento de software, como qualquer projeto de engenharia, está sujeito a incertezas e riscos que são difíceis de prever inicialmente. Por isso, métodos como XP recomendam incluir uma folga (slack) em cada sprint. Por exemplo, pode-se reservar 20% da duração de cada sprint para realizar atividades como as seguintes:

Uma alternativa a slacks é reservar um dia a cada mês (ou a cada n meses) para a realização das duas últimas atividades descritas acima. Nesses dias, normalmente chamados de Hack Days, todos os membros do time focam em uma pauta de trabalho que não visa a implementação de novas funcionalidades.

Slacks ou Hack Days são então práticas que uma organização pode adotar para sinalizar a importância de cuidar da qualidade interna de seus produtos de software. Para isso, a organização entende ser importante reservar um tempo exclusivamente para essa finalidade.

Mundo Real: Shape Up é um processo de desenvolvimento de software proposto pela empresa 37signals, que desenvolve um sistema de gerenciamento de projetos chamado Basecamp. Shape Up possui duas trilhas de trabalho: uma trilha de descoberta de requisitos (discovery) e uma trilha de entrega (delivery) e implementação. A trilha de entrega é organizada em ciclos, que possuem tipicamente seis semanas. No entanto, quando um ciclo termina, não se começa imediatamente um novo ciclo. Em vez disso, ele é seguido de um período de cool-down, que dura duas semanas. Segundo os autores do processo Shape Up, o objetivo desse período é o seguinte:

Durante o período de cool-down, programadores e designers ficam livres para trabalhar no que quiserem. Após se dedicarem intensamente para entregar seus projetos de seis semanas, eles apreciam ter um tempo sob seu próprio controle. Eles usam esse tempo para corrigir bugs, explorar novas ideias ou experimentar novas possibilidades técnicas. (Shape Up: Stop Running in Circles and Ship Work that Matters, Capítulo 8)

Portanto, o período de cool-down é também uma forma de slack, na verdade, com uma duração até maior do que aquela proposta em XP.

9.2.3 Post Mortems

Post mortem é um documento que descreve, de forma detalhada, um incidente grave que impactou as operações e serviços de uma organização. No contexto específico de Engenharia de Software, esse incidente está então relacionado com falhas de software. No entanto, também é possível elaborar um post mortem após a morte de um projeto, incluindo projetos de desenvolvimento de software.

Em um post mortem, descrevem-se as causas do incidente (ou do cancelamento do projeto), o que funcionou bem e, principalmente, como o problema pode ser evitado no futuro, isto é, quais lições foram aprendidas. Portanto, o objetivo não é apontar culpados, mas sim evitar que os mesmos erros se repitam no futuro.

Normalmente, um post mortem possui as seguintes seções: linha do tempo (que descreve os acontecimentos relacionados ao incidente, incluindo data e hora); impacto do incidente; causas raiz; mitigação (isto é, como o problema foi resolvido); e as providências que serão tomadas para evitar que o incidente se repita.

Mundo Real: No dia 18 de novembro de 2025, um incidente grave ocorreu com a Cloudflare, um importante provedor de infraestrutura e serviços básicos de Internet. Como resultado, diversos sites que são hospedados ou que usam serviços desse provedor ficaram fora do ar, como ChatGPT, X e Spotify. No Brasil, segundo informações da Agência Brasil, alguns sistemas do Gov.br também foram afetados. De forma interessante, no mesmo dia, a empresa publicou um post mortem detalhado no seu site, explicando o incidente. Inclusive, esse post mortem foi assinado pelo co-fundador da empresa. A seguir, mostramos um resumo desse post mortem:

9.2.4 Congelamento do Código

Congelamento de código (code freeze ou, melhor ainda, code deployment freeze) é uma prática que proíbe mudanças na versão em produção de um sistema por um certo período de tempo. Especificamente, existem dois tipos de congelamento de código:

Essa prática costuma ser adotada por grandes empresas de software antes de períodos com grandes volumes de transações, como Black Friday e Natal. Pode ser usada também quando a maior parte da equipe está de férias, como no final do ano. Por fim, também pode ser útil quando, após uma implantação, um sistema passa a apresentar muitas instabilidades, sendo prudente suspender a implantação de novas funcionalidades até que a situação seja estabilizada.

9.2.5 Recompensas por Descobertas de Bugs

Programas de recompensas pela descoberta de bugs (bug bounties) são usados por grandes empresas de tecnologia para recompensar aqueles que identificarem bugs críticos, principalmente de segurança ou privacidade, em seus sistemas. Normalmente, esses programas são abertos a pesquisadores e desenvolvedores externos, algumas vezes chamados de hackers éticos. Quando encontram um problema, os participantes do programa devem reportá-lo de forma responsável, isto é, comunicando a falha diretamente para a empresa antes de qualquer divulgação pública. As recompensas variam de acordo com a gravidade do bug e podem chegar a valores bastante elevados em casos críticos. Em geral, as empresas definem claramente o escopo do programa, especificando quais sistemas podem ser testados e quais tipos de testes são permitidos.

9.3 Métricas

Tom DeMarco — um conhecido engenheiro e consultor de software — possui uma frase clássica sobre métricas: Você não pode controlar aquilo que não consegue medir. De fato, não conseguimos controlar e também não conseguimos entender e melhorar um processo de software quando as decisões são subjetivas, isto é, não são baseadas em dados. Por isso, é importante coletar métricas, mesmo que de forma mínima, sobre diversos aspectos de um projeto de desenvolvimento e manutenção de software. O objetivo é entender os problemas que estão acontecendo, digamos assim, no chão da fábrica, de forma que possamos atuar para eliminá-los ou pelo menos atenuar os seus impactos negativos. Nesta seção, vamos abordar quatro categorias importantes de métricas: métricas de fluxo, métricas de qualidade, métricas de código fonte e métricas de negócio.

9.3.1 Métricas de Fluxo

Essas métricas têm como objetivo acompanhar, de forma quantitativa, o fluxo de trabalho dos times de desenvolvimento e manutenção de software. Especificamente, podemos analisar o ritmo de implementação de novas funcionalidades por meio de duas métricas principais:

Essas duas métricas capturam dimensões complementares de um processo de desenvolvimento e evolução de software. O lead time mede quanto tempo um cliente precisa esperar para usar uma funcionalidade, enquanto o throughput mede quantas funcionalidades a equipe consegue entregar em um determinado período.

Para entender melhor as métricas, podemos fazer uma analogia com um restaurante. Um restaurante não pode ter um lead time alto, pois os clientes não querem esperar horas para serem servidos. Porém, também não adianta atender a um único cliente de forma rápida enquanto vários outros permanecem esperando. O objetivo é reduzir o tempo de espera dos clientes (lead time) e, ao mesmo tempo, manter uma boa vazão de atendimento (throughput). Nessa analogia, o restaurante são os times de desenvolvimento e os pratos correspondem às histórias de usuário que esses times devem implementar.

Existe uma lei em Teoria de Filas que permite calcular o número médio de histórias de usuário que estão sob a responsabilidade de um time. Esse valor é chamado de Work in Progress (WIP). A lei, chamada Lei de Little, define o seguinte:

WIP = Throughput x Lead Time

Ou seja, conhecendo o throughput e o lead time médios de um time, medidos durante um certo período, conseguimos calcular o número médio de histórias que estiveram sob a sua responsabilidade durante esse período (WIP). Voltando à analogia do restaurante, o WIP seria o número médio de pratos que a cozinha do restaurante ficou preparando durante o período da análise.

Por exemplo, suponha que um time entregue em média 3 histórias por semana (throughput) e que as histórias levam em média duas semanas para ficarem prontas (lead time). Logo, esse time, em média, está sempre com seis histórias sob sua responsabilidade (WIP).

Para calcular o lead time e o throughput é importante que exista uma baixa variabilidade no tamanho das histórias de usuário, pois ambas as métricas são calculadas como sendo uma média. Se as histórias tiverem tamanhos muito diferentes, podemos unir histórias pequenas ou quebrar aquelas que são muito grandes. Uma outra alternativa é calcular o lead time e throughput para classes de histórias. Por exemplo, para histórias pequenas, médias e grandes.

Aprofundamento: Existem outras interpretações do conceito de lead time, além daquela apresentada anteriormente. Por exemplo, o lead time do cliente é o tempo entre o recebimento de uma solicitação do cliente e sua entrega. Essa definição de lead time é mais adequada quando um sistema é desenvolvido de forma customizada para um cliente específico. Por outro lado, esse não é o caso de sistemas desenvolvidos para um mercado formado por milhares ou milhões de clientes (quando é natural que uma solicitação tenha que ser entendida, validada e priorizada, antes de sua implementação ser alocada para um time).

9.3.2 Métricas de Qualidade

Apesar de as métricas de fluxo serem importantes, não basta entregar novas histórias de usuário com velocidade e com vazão. Também precisamos garantir que essas entregas tenham qualidade. Para isso, é importante considerar métricas como as seguintes:

9.3.3 Métricas de Código Fonte

É interessante acompanhar também propriedades do código fonte, como tamanho e complexidade. No caso de tamanho, podemos usar métricas como número de linhas de código (LOC), número de classes, número de pacotes, etc. No caso de complexidade, uma métrica muito comum é a complexidade ciclomática (CC). Essa métrica mede a complexidade de uma função com base no número de caminhos possíveis de execução. Na prática, ela pode ser calculada somando 1 ao número de comandos de decisão no código (como if, while, for e case). Quanto maior o valor de CC, mais difícil tende a ser entender, manter e testar a função. Por exemplo, valores de CC acima de 10 indicam funções excessivamente complexas.

9.3.4 Métricas de Negócio

No contexto de desenvolvimento e manutenção modernos de software é importante que os times consigam, proativamente, contribuir com os resultados de uma organização. Por isso, é importante que acompanhem também métricas de negócio. No caso de software por assinatura (Software as a Service ou SaaS), as métricas mais comuns são:

Para um SaaS ser viável, LTV > CAC. Na verdade, recomenda-se que LTV / CAC > 3.

9.3.5 Recomendações para Uso de Métricas

Para encerrar esta seção, seguem algumas recomendações para uso de métricas:

1. Métricas são indicadores para ajudar no entendimento e diagnóstico de um problema. Elas nunca podem ser o objetivo final. Apenas para ilustrar, se a produtividade de desenvolvedores for medida em linhas de código ou número de commits, eles serão incentivados a gerar código redundante, duplicado, que não agrega valor, etc.

2. Deve-se sempre entender a distribuição estatística que descreve os valores das métricas. Por exemplo, métricas de código fonte podem ter uma distribuição no formato de uma cauda longa, o que compromete o uso de valores médios. Para ilustrar, a maioria das funções de um sistema podem ser pequenas, mas podem existir também algumas funções muito grandes, que vão distorcer o tamanho médio da população de funções.

3. Além de entender a distribuição dos valores de uma métrica, devemos entender a evolução desses valores ao longo do tempo. Por exemplo, uma sazonalidade pode aumentar o número de bugs de um sistema em um certo mês. No entanto, mais importante do que esse aumento em um único mês, devemos acompanhar a evolução da métrica ao longo de mais meses, para não tomar decisões precipitadas.

4. Não devemos usar uma única métrica. Por exemplo, no caso das métricas de processo, recomendamos anteriormente usar tanto métricas de fluxo como de qualidade. Assim, se os desenvolvedores somente privilegiarem a velocidade de entregas, eles poderão ser penalizados nas métricas de qualidade. Por outro lado, se eles ficarem focados em alcançar níveis de qualidade que não são necessários, eles serão penalizados pelas métricas de fluxo.

5. Por fim, não devemos usar um número excessivo de métricas, pois podemos ficar perdidos ao tentar entender uma grande quantidade de dados. Além disso, é comum ter correlações entre métricas. Por exemplo, tende a existir uma correlação alta entre o tamanho de classes e o número de métodos por classe. Logo, basta considerar uma dessas duas métricas.

9.4 Modelos de Branches

Já caminhando para o final do capítulo, vamos apresentar três modelos que padronizam os branches que devem ser criados por times de desenvolvimento e manutenção de software. São eles: Git-flow, GitHub Flow e Desenvolvimento Baseado no Trunk (TBD). Esse é um assunto mais técnico e operacional, por isso o deixamos para o final do capítulo. Porém, a escolha do modelo de branches é muito relevante em processos de manutenção de software, pois eles disciplinam o fluxo de trabalho usado pelos times para implementar novas funcionalidades ou para corrigir bugs, por exemplo.

9.4.1 Git-flow

Git-flow é um modelo de gerenciamento de branches muito comum em times que usam Git, tendo sido proposto por Vincent Driessen em 2010. Basicamente, o modelo usa dois branches principais e permanentes:

Git-flow prevê ainda três branches temporários: branches de funcionalidade, branches de release e branches de hotfix. Esses branches serão descritos a seguir. Eles podem ser criados usando os próprios comandos do Git ou então usando plugins com macros que facilitam o uso do Git-flow.

Branches de Funcionalidade: Esses branches nascem de develop e devem ser criados antes de começar a implementação de uma nova funcionalidade. Quando essa funcionalidade ficar pronta, eles são integrados (via um merge) de volta em develop e são removidos. Por isso, eles existem apenas no repositório local de um desenvolvedor.

Na próxima figura, mostramos um branch de funcionalidade. Os círculos cheios representam commits e o círculo vazio representa um merge. Veja que o branch de funcionalidade nasce e volta para develop.

Branch de funcionalidade (último branch da figura) usando Git-flow

Branches de Release: Esses branches também nascem de develop. Eles são usados para preparar uma nova release, a qual deve ser aprovada (ou homologada) pelo cliente final. Quando o cliente dá o sinal verde, os branches de release são integrados no main, pois agora temos uma nova versão do sistema pronta para entrar em produção. Porém, se no processo de aprovação forem realizadas mudanças no código, o branch de release deve ser integrado de volta também em develop.

Para exemplificar, suponha que após implementar a funcionalidade da figura anterior, o líder do time decidiu gerar uma release 1.0 do sistema. Para isso, ele criou um branch de release (veja a próxima figura), que foi usado para mostrar o sistema para o seu cliente final. Após algumas modificações requisitadas pelo cliente, o sistema foi aprovado e entrou em produção, isto é, foi integrado no main, usando-se a tag 1.0. Por fim, as mudanças realizadas no branch de release foram também aplicadas em develop.

Branch de release (último branch da figura) usando Git-flow

Branches de Hotfix: Esses branches são usados para corrigir um bug crítico que foi detectado em produção, ou seja, em um código que está no main. Por isso, eles nascem do main, recebem commits que corrigem o bug crítico e, por último, são novamente integrados no main e também em develop.

Continuando com nosso exemplo, suponha que após lançar a nova release, os usuários reportaram um bug crítico nela. Assim, criou-se um branch para corrigir esse bug crítico (veja na figura a seguir), o qual nasceu do main. Após a correção do bug, o branch foi integrado de volta no main e uma nova release foi gerada, com a tag 1.0.1. Por fim, o branch foi também integrado em develop.

Branch de hotfix (último branch da figura) usando Git-flow

Em resumo, o fluxo mais comum quando se usa Git-flow é o seguinte:

Funcionalidade develop release main

Ou seja, uma funcionalidade é sempre implementada em um branch específico. Em seguida, esse branch é integrado em develop, onde a funcionalidade passa por testes de integração. De tempos em tempos, um branch de release é gerado para mostrar uma nova versão do sistema para o cliente final. Uma vez aprovada, essa versão é integrada no main e disponibilizada para a base completa de usuários.

Git-flow deve ser usado, principalmente, quando existem testes manuais e times de garantia de qualidade (QA). E também quando os clientes precisam aprovar e homologar qualquer nova versão do sistema antes que ela entre em produção.

Porém, quando se usa Git-flow, os branches de funcionalidade podem demorar muito tempo para serem integrados em develop, o que pode ocasionar diversos conflitos de integração (merge hell). Adicionalmente, caso a integração dos branches de release também demore, os desenvolvedores vão demorar para receber feedback sobre as novas funcionalidades que eles implementaram.

9.4.2 GitHub Flow

GitHub Flow é um modelo de branches muito comum quando se usa GitHub. Trata-se de um modelo mais simples do que Git-flow, pois existe apenas um branch principal e branches de funcionalidade. Por outro lado, existe suporte a revisão de código antes de integração no branch principal, por meio do mecanismo de Pull Requests (PR) do GitHub.

Quando se usa GitHub Flow, os principais passos são os seguintes:

  1. Um desenvolvedor cria um branch no seu repositório local.
  2. Implementa uma funcionalidade ou corrige um bug.
  3. Faz um push do branch para o GitHub.
  4. Entra no GitHub e abre um Pull Request, isto é, um pedido para alguém revisar seu branch, conforme descrevemos na Seção 9.2.1.
  5. Um outro desenvolvedor revisa e, eventualmente, faz o merge do PR.

GitHub Flow é usado, principalmente, em sistemas com apenas uma versão em produção, como costuma ser o caso de sistemas Web. Uma desvantagem do modelo é que os PRs podem levar muito tempo para serem revisados.

Apesar do nome, os mesmos passos propostos por GitHub Flow podem ser usados com outros serviços de controle de versão, como GitLab.

9.4.3 Desenvolvimento Baseado no Trunk

Desenvolvimento Baseado no Trunk (TBD) é ainda mais simples do que GitHub Flow, pois baseia-se em um único branch, isto é, o branch principal, chamado também de main, master ou trunk. Ou seja, todos os desenvolvedores realizam seus commits diretamente no branch principal. No entanto e apenas para esclarecer, mesmo em TBD, desenvolvedores podem criar branches de funcionalidade, mas eles devem ter uma duração muito limitada, por exemplo, de no máximo alguns dias.

Para usar TBD, é importante que exista uma boa quantidade de testes de unidade e de integração, para evitar que bugs e regressões sejam introduzidos no main. No nosso livro anterior (Engenharia de Software Moderna, link) explicamos também o mecanismo de feature flags, que é usado para evitar que implementações incompletas entrem em produção.

9.5 Conventional Commits

Manutenção, em suas várias formas (corretiva, evolutiva, refatoração, etc.), requer essencialmente mudanças de código, as quais são efetivadas por meio de commits. Nesta seção, vamos descrever uma convenção para escrita de mensagens de commits, chamada de Conventional Commits. Essas mensagens são importantes porque elas são parte do histórico de versões mantido por um repositório de código e, futuramente, serão lidas por outros desenvolvedores.

Antes de apresentar essa convenção, queremos lembrar que todo git commit possui uma mensagem que explica a mudança que está sendo realizada no repositório do projeto, tal como a seguir:

git commit -m "update"

No entanto, a mensagem acima, que usamos propositalmente como exemplo, é muito simples e vaga. Conventional Commits propõe então um formato padrão para escrita de mensagens de commits, com o objetivo de torná-las mais legíveis e também facilitar o seu processamento por ferramentas. Cada mensagem deve ter o seguinte formato:

<tipo>(escopo): <descrição>

[corpo]

[rodapé]

O campo <tipo> indica o tipo da mudança e pode ser:

<escopo> é um campo opcional que informa o módulo ou a parte do sistema que foi alvo da mudança. Por exemplo, (controlador).

Em seguida, <descrição> é uma breve descrição da mudança, em apenas uma linha. A descrição deve começar com um verbo no presente. Por exemplo, cria, atualiza, refatora, corrige, implementa, remove, envia, etc.

Opcionalmente, deve seguir uma linha em branco e uma série de linhas (<corpo>) que descrevem a mudança com mais detalhes. O <corpo> é opcional porque a mudança pode ser simples e a <descrição> já pode ser suficiente para explicá-la.

O [rodapé] é também opcional. Um rodapé comum é a mensagem BREAKING CHANGE:, a qual destaca que o commit inclui uma breaking change, tal como vimos no Capítulo 4. No entanto, em vez de indicar a breaking change no rodapé, pode-se acrescentar um ponto de exclamação (!) após o <tipo>. Por exemplo, feat! significa que estamos implementando uma funcionalidade e que ela introduz uma breaking change. O rodapé pode ser também usado para associar o commit a uma tarefa no sistema de gerenciamento de tarefas, como Jira, GitHub Issues, etc.

Uma implicação importante de Conventional Commits é que um commit deve realizar apenas um tipo de manutenção. Ou seja, não é possível ter um commit que corrige um bug e que implementa uma funcionalidade, por exemplo. Nesse caso, esse commit deve ser dividido em dois. De forma semelhante, não é recomendável ter um commit que implementa múltiplas vezes o mesmo tipo de manutenção. Por exemplo, o mesmo commit não deve corrigir dois bugs.

9.5.1 Exemplo: Fórum de Perguntas e Respostas

Mostram-se a seguir alguns exemplos de mensagens de commits, relativas a um fórum de perguntas e respostas. Ou seja, um sistema no qual um usuário pode fazer perguntas, que serão respondidas por outros usuários.

1. Implementação de uma nova feature.

feat(perguntas): adiciona um botão de like nas perguntas

2. Correção de um bug.

fix(respostas): corrige bug na contagem de visualizações de respostas

3. Atualização de documentação.

docs: atualiza instruções de instalação

4. Implementação de uma feature, com uma breaking change.

feat(respostas)!: cria uma thread de respostas

Permite agora adicionar uma resposta em uma outra resposta,
criando-se assim uma thread de respostas.

BREAKING CHANGE: adição de parâmetro com o nível da resposta
no endpoint de criação de resposta.

9.5.2 Exemplo: Angular

Um dos primeiros sistemas a adotar Conventional Commits foi o Angular, que é um framework para implementação de frontends de aplicações Web. Seguem alguns exemplos de mensagens de commits desse projeto. O leitor vai perceber que Conventional Commits também permite outros tipos de mudanças, como test (implementação de testes) e build (mudanças no sistema de build).

docs: fix wrong line highlights

fix(language-service): Detect local project version on creation

feat(forms): introduce parse errors in signal forms

refactor(core): improve tree-shaking

feat(devtools): mark special element injector providers as internal

test(forms): submit behavior while validation is pending

build: update cross-repo angular dependencies

9.5.3 Aplicações

Como afirmado, Conventional Commits padroniza e torna mais claras as mensagens de commit, facilitando o entendimento das manutenções realizadas em um projeto. Ele também facilita a implementação de ferramentas para geração de changelogs, versionamento semântico e geração de notas de release.

Bibliografia

Marco Tulio Valente. Engenharia de Software Moderna: Princípios e Práticas para Desenvolvimento de Software com Produtividade. Editora Independente, 2020.

Titus Winters, Tom Manshreck, Hyrum Wright. Software Engineering at Google: Lessons Learned from Programming Over Time. O’Reilly, 2020.

Kent Beck, Cynthia Andres. Extreme Programming Explained: Embrace Change. Addison-Wesley, 2nd edition, 2004.

Vincent Driessen. A successful Git branching model. Blog, 2010.

Nicole Forsgren, Jez Humble, Gene Kim. Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations. It Revolution, 2018.

Exercícios

1. Qual é a principal diferença entre revisão de código e programação em pares (uma das principais práticas propostas por XP)?

2. Descreva uma desvantagem de revisão de código.

3. Qual é a relação entre Conventional Commits e Versionamento Semântico (tal como estudamos no Capítulo 4)?

4. A próxima tabela apresenta valores de métricas coletadas ao longo de quatro meses por um time de desenvolvimento e manutenção de software. Todas as métricas de fluxo estão expressas na mesma unidade de tempo (semanas).

Mês Histórias entregues por semana Lead time médio (semanas) Bugs em produção
Jan 3,0 2 3
Fev 4,5 2 9
Mar 5,0 2 15
Abr 5,5 2 21
  1. Usando a Lei de Little, calcule o WIP do time em cada um dos quatro meses. Qual é a tendência dos valores de WIP ao longo dos meses?

  2. O time está comemorando o aumento de throughput como um sinal de eficiência. No entanto, qual efeito negativo pode estar ocorrendo em função desse aumento de throughput?

5. Um time de desenvolvimento e manutenção afirma que seu processo de correção de bugs é muito eficiente, pois ele sempre consegue corrigir todos os bugs reportados pelos usuários. No entanto, quais outras métricas deveriam ser analisadas antes de chegar a uma conclusão mais definitiva sobre a eficiência desse time? Justifique sua resposta.

6. Um conjunto conhecido de métricas para avaliar o desempenho de entrega de software é chamado de métricas DORA (DevOps Research and Assessment). Pesquise sobre as métricas DORA e descreva a diferença entre o conceito de lead time de tais métricas e aquele que definimos na Seção 9.3.1.

7. Define-se como churn negativo de receita (negative revenue churn) a situação em que o aumento de receita proveniente dos clientes que permanecem em um sistema supera a perda de receita causada pelo cancelamento de outros clientes. Para ficar mais claro, considere o seguinte exemplo: um serviço SaaS possui 100 clientes e fatura R$ 10 mil por mês. Em um determinado mês, 10 clientes cancelaram o serviço. Apesar disso, a empresa passou a faturar R$ 11 mil por mês com os 90 clientes restantes.

  1. Como é possível que a receita total tenha aumentado mesmo com a perda de clientes? Justifique.

  2. Cite pelo menos dois eventos que podem ter contribuído para essa situação.

8. Supondo uma organização que usa Git-flow, complete a próxima tabela com os nomes dos branches de origem e de destino para cada tipo de branch.

Tipo de Branch Branch de Origem Branch de Destino
Feature
Release
Hotfix