Escrito por João Dartora,

22 minutos de leitura

Tudo o que você precisa saber sobre commits semânticos

Aviso: este post pode lhe incentivar fortemente a seguir boas práticas de versionamento de código!

Compartilhe este post:

O problema de sexta-feira

A situação é a seguinte: é uma tarde de sexta-feira, você deu git push na sua última task finalizada da semana, porém ainda falta uma hora  para ir para casa. Você questiona se algum de seus colegas precisa de ajuda e eis que um deles lhe passa a tarefa de dar uma olhadinha em um microsserviço antigo, em busca de um pequeno bug que está rodando em produção.

Então, você abre o código e começa a procurar pela origem do bug.  Após achar sua possível origem, o git blame lhe indica em qual commit aquilo aconteceu, eis que a mensagem é “fixando bugs”. Você vai até o repositório, verifica na diff o que foi modificado e não entende absolutamente nada.

Isso faz com que você comece a abrir os commits anteriores, todos com mensagens claras e explicativas, como “consertando bug”, “adicionando método”, “fixando API”, “melhorando correções do CR”, entre outras. Após uma hora tentando entender a origem do erro e aproximadamente 15 commits abertos, você encontra o maldito commit e reverte todas as mudanças.

Ao abrir seu Jenkins para redeployar o serviço, se depara com o último deploy tendo sido realizado há dois meses, sem nenhum dos commits que você reverteu, e com o seguinte commit deployado: “revertendo erros”.  Ou seja, aquela uma hora foi para o além, você continua não fazendo ideia de onde o erro aconteceu e só queria nunca ter aceitado aquela pequena task que se tornou um pesadelo de sexta-feira.

A situação acima, apesar de um pouco dramatizada, provavelmente já aconteceu em algum nível com você, seja por ter que achar uma modificação feita por outra pessoa ou por buscar entender o que você mesmo fez em outro momento.  Quase sempre isso é uma tarefa cansativa e entediante, principalmente quando temos mensagens de commit confusas e pouco explicativas. E é para resolver esse tipo de problema que falarei um pouco sobre commits semânticos.

Mas o que são esses tais commits semânticos que o meu neto falou?

Os commits semânticos são um modelo de padronização de commits dentro de um projeto ou de uma squad. Nele, algumas pequenas regras para escrita e envio dos commits são adicionadas, visando melhorar problemas recorrentes. Alguns exemplos são o gasto de tempo excessivo na procura do momento no qual foram criados “locais onde a magia acontece” em um histórico do git – popularmente conhecidos como bugs – ou para se ter o entendimento de quantas e quais modificações estão sendo deployadas em um ambiente (de homologação para produção, por exemplo), que podem gerar um impacto para um microsserviço.

Origem e atualidade

A origem dos commits semânticos não é 100% confirmada, mas o primeiro grande projeto conhecido a utilizar regras para seus commits foi o repositório do Angular no Github, por meio de seu Angular Commit Message Guidelines. Ele  trazia algumas regras básicas criadas pelo time de colaboradores sobre como realizar commits para o repositório de maneira organizada e padronizada, facilitando tanto o trabalho dos contribuidores quanto a legibilidade do CHANGELOG gerado a cada versão.

Atualmente, existem diversos projetos open-source que utilizam uma padronização para seus commits, como os repositórios do Karma e do jQuery. Além disso, há também o Conventional Commits, um projeto que traz consigo uma tentativa de oficializar uma convenção para padronização de commits, sendo fortemente inspirado no modelo do Angular.

E por que eu devo usar isso nos meus projetos?

Apesar de alguns modelos de padronização serem muito focados em projetos open-source, nada impede que se utilize um padrão em projetos privados, realizando as devidas adaptações para que melhor se encaixe nas necessidades do seu projeto. O importante é que se crie um conjunto de regras que serão usadas em todos os commits, e é sobre isso que quero falar aqui.

Em um contexto no qual temos um squad de desenvolvedores trabalhando em um projeto e compartilhando um repositório, é importante que se mantenha um certo nível de organização e coordenação na hora do versionamento do código, pelos seguintes motivos:

  • Melhorar a legibilidade do histórico de versionamento do Git;
  • Aumentar a velocidade na hora de procurar por mudanças específicas no código;
  • Entender, facilmente, quais mudanças estão sendo deployadas dentro de um pipeline;
  • Possibilitar a geração de um CHANGELOG ou de release notes de maneira totalmente automatizada;
  • Incentivar os desenvolvedores a realizarem commits de maneira pensada e específica, sem realizar commits grandes e cheios de mudanças;
  • Possibilita a utilização de ferramentas de automação de commits, como o Commitizen.

Mas, e como funciona?

<type>[scope]: <description>

[body]

[footer(s)]

O modelo acima é o esqueleto de um commit semântico completo com todas as partes opcionais, mas ele não precisa ser utilizado em todos os commits.  Devido ao seu tamanho, pode acabar gerando um gasto de tempo desnecessário em algumas ocasiões. Na maioria dos casos, usar o modelo simplificado sem as partes opcionais já vai ajudar bastante.

Tipos

Os tipos são a descrição inicial de o que o commit está realizando, sendo obrigatórios e devendo seguir um padrão bem definido. Abaixo, vou descrever os tipos que mais utilizamos no projeto hoje. Eles foram escolhidos para se adaptar às necessidades do nosso projeto, tendo como base o Conventional Commits, e realizando algumas pequenas modificações.

  • feat: utilizado quando se adiciona alguma nova funcionalidade do zero ao código/serviço/projeto.

Exemplo: adição de um novo endpoint para uma API REST ou um novo consumer para um serviço de mensageria.

  • fix: usado quando existem erros de código que estão causando bugs.

Exemplo: proteção de uma variável que está gerando um NullPointerException em produção.

  • refactor: utilizado na realização de uma refatoração que não causará impacto direto no código ou em qualquer lógica/regra de negócio.

Exemplo: melhorias de performance revisadas em um code review.

  • style: utilizado quando são realizadas mudanças no estilo e formatação do código que não irão impactar em nenhuma lógica do código.

Exemplo: realizar a indentação de um código.

  • test: usado quando se realizam alterações de qualquer tipo nos testes, seja a adição de novos testes ou a refatoração de testes já existentes.

Exemplo: adição de testes de contrato e modificação de testes unitários.

  • doc: ideal para quando se adiciona ou modifica alguma documentação no código ou do repositório em questão.

Exemplo: adição de documentação sobre o response de uma API ou adição de um README.md.

  • env: utilizado quando se modifica ou adiciona algum arquivo de CI/CD.

Exemplo: modificar um comando do Dockerfile ou adicionar um step a um Jenkinsfile.

  • build: usado quando se realiza alguma modificação em arquivos de build e dependências.

Exemplo: adição de dependências do Apache Kafka.

Description

A descrição, juntamente com o tipo, é uma das partes mais importantes do padrão: é aqui que deve ser descrito, de maneira clara, sucinta e simplificada, o que foi realizado no commit. É recomendado que essa parte tenha, no máximo, 70 caracteres, para que não se estenda muito. Exemplo:

feat: adicionando API para cadastro de investidores

Scopes

O escopo do commit é uma parte opcional, curta e de fácil compreensão.  É nela que iremos dizer qual parte do código foi modificada, como indicar que fizemos alterações apenas na camada de Client de um microsserviço. Exemplo:

refactor(InvestidorService): modificando regra para cálculo de juros

Body

O corpo do commit é também opcional. Nele,  pode-se realizar uma descrição mais detalhada do commit, indicar razões para a realização dele e consequências que ele pode vir a causar, além de alguma outra observação que seja pertinente. Exemplo:

fix(ProdutoApi): retirando variável do path da API e ajustando loggers

- O path anterior tinha variáveis desnecessárias e não utilizadas por nenhum consumidor

- Os loggers foram adaptados ao novo padrão utilizado

Footer

O rodapé, assim como o corpo, é opcional e informativo.  Ele pode ser usado como uma finalização do commit, informando o encerramento de uma issue ou o pertencimento/associação a uma task também. Exemplo:

env: mudando parâmetros de timezone da JVM no Dockerfile

- Os parâmetros anteriores estavam causando uma diferença de horários nas operações devido à incompatibilidade de timezones

# Finaliza Task JD-3108

Na prática

Aqui vou exemplificar uma sequência de alguns commits, comparando e mostrando a diferença entre apenas commitar e commitar usando commits semânticos:

Processo de adoção da prática em uma equipe

Para finalizar, vou falar um pouco sobre como foi o processo de implantação dessa cultura de commits semânticos desde o início em uma squad.

Ao entrar no projeto que estou trabalhando atualmente, notei que, na hora de realizar algumas tasks que envolviam manutenção de códigos antigos (e alguns novos também), eu perdia muito tempo algumas vezes. Eram diversos “fix bug” no Git que não me diziam absolutamente nada, e faziam com que eu tivesse de abrir uma dezena de commits individualmente para achar onde foi realizada uma modificação. Isso tudo gerava uma grande perda de tempo, pois aumentava a complexidade e dificultava a compreensão do histórico do código.

Pensando nisso, estimei que se cada desenvolvedor do time tivesse essa mesma dificuldade de entendimento do que está acontecendo (ou aconteceu) com o projeto em pelo menos um dia da semana, estaríamos perdendo algumas horas por mês, somente por culpa da falta de clareza nas mensagens de commits. Isso me despertou a ideia e a vontade de trazer a utilização de commits semânticos para o squad.

Após uma retrospectiva com o time, apresentei a ideia e todos os membros da equipe gostaram.  Então, acabei ficando de responsável por monitorar, cobrar e dar suporte nessa iniciativa. Fiz uma apresentação explicando melhor a todos do time como funcionavam os commits semânticos, quais eram as vantagens e os motivos de os usarmos e o quanto iriam nos ajudar em termos de organização. Além disso, também estipulamos, em conjunto, qual seria o padrão que iríamos seguir.

No início foi bem difícil, porque todos tinham um costume muito forte de commitar de qualquer maneira, sem nenhum modelo ou regra. aproximadamente duas semanas depois, lembrando e apoiando a equipe, quase todos já haviam entendido o funcionamento e realizavam boa parte de seus commits já no padrão que havíamos estipulado.

As mudanças mais notáveis começaram a aparecer após um mês e meio. Nesse momento todos já estavam realizando quase todos os commits de maneira padronizada. Os repositórios novos que nasceram nesse meio tempo já possuíam quase todos os seus commits semânticos e os pipelines de deploy de nossos serviços já mostravam exatamente quais modificações estavam sendo levadas aos ambientes a cada novo build. Isso melhorou muito a organização de nossos repositórios e a organização da equipe ao trabalhar em conjunto no mesmo código.

Hoje, todos na equipe têm esse padrão internalizado e fazem, de maneira quase automática, um processo que demanda apenas um pouco de esforço e algumas semanas de tentativa.

É legal pensar também que, uma vez que os repositórios já estejam inteiramente padronizados e a cultura de commitar corretamente já esteja estabelecida no time, fica muito mais fácil para algum membro novo entender e adotar a prática, algo que também ocorreu durante o processo.

Compartilhe este post: