Versionamento semântico (SemVer)

8 min
Imagem com números repesentando o versionamento semântico

Muito provavelmente, você já precisou instalar uma biblioteca para utilizar em seu projeto e, ao fazer isso, deve ter percebido que ela vem com um número de versão. Esse número pode aparecer no nome do arquivo que você está referenciando no src da tag script ou dentro de um arquivo package.json, por exemplo. Esse número nos ajuda em diversas coisas: a achar a documentação correta para a versão que estamos utilizando, a saber se estamos na mais atual, etc. Mas o mais importante é que ele nos ajuda a entender o que mudou entre uma versão e outra. E é aí que o semantic versioning entra.

O que é o versionamento semântico?

O versionamento semântico (semantic versioning em inglês), ou SemVer, é uma convenção de versionamento utilizada para definir como as versões de um software devem ser incrementadas.

Essa convenção é baseada em três números, separados por pontos, que representam, respectivamente, a versão major (1.), minor (2.) e patch (3). Exemplo: 1.2.3.

Patch

O número do patch é incrementado quando correções de bugs são feitas na biblioteca. Ou seja, quando a mudança não adiciona novas funcionalidades e não quebra a compatibilidade com a versão anterior; o que funcionava antes continua funcionando.

Exemplo:

Quando a versão do React DOM foi atualizada de 17.0.0 para 17.0.1:

17.0.1 (October 22, 2020)
React DOM
Fix a crash in IE11. (@gaearon in #20071)
trecho do changelog do react

Podemos ver, através de um trecho do changelog do React , que a versão 17.0.1 foi lançada para corrigir um bug que estava causando um crash no Internet Explorer 11 (RIP).

Minor

O número minor é incrementado quando novas funcionalidades são adicionadas à biblioteca. Essas novas funcionalidades não devem afetar o funcionamento do que já existia, ou seja, não devem quebrar a compatibilidade com a versão anterior.

Durante a publicação dessa nova versão, o número do patch é resetado para 0.

Um ponto importante é que, durante a publicação de uma nova versão minor, também é possível que correções de bugs sejam incluídas. Mas mesmo que isso aconteça, devemos manter o reset do patch para o número 0.

Exemplo:

Podemos pegar outra biblioteca como exemplo, o Jest. Quando a versão foi atualizada de 29.6.4 para 29.7.0, vemos que novas funcionalidades adicionadas, mas também houve correções de bugs.

29.7.0

Features

[create-jest] Add npm init / yarn create initialiser for Jest projects (#14453)
[jest-validate] Allow deprecation warnings for unknown options (#14499)

Fixes

[jest-resolver] Replace unmatched capture groups in moduleNameMapper with empty string instead of undefined (#14507)
[jest-snapshot] Allow for strings as well as template literals in inline snapshots (#14465)
[@jest/test-sequencer] Calculate test runtime if perStats.duration is missing (#14473)
trecho do changelog do jest

Não vamos nos apegar ao o que mudou, mas podemos ver que a versão 29.7.0 foi lançada com novas funcionalidades e também com bug fixes, ela avançou de 29.6.4 para 29.7.0. Se somente as correções de bugs fossem incluídas, a versão seria 29.6.5, pois somente o patch deveria ser incrementado.

Major

O número major é incrementado quando mudanças que quebram a compatibilidade com a versão anterior são feitas. Ou seja, quando mudanças que afetam a forma como a biblioteca é utilizada são realizadas e, com isso o desenvolvedor que utiliza a biblioteca precisa fazer alterações em seu código para que ele continue funcionando. Essas mudanças são conhecidas como breaking-changes.

Resumindo, mudanças que quebram a compatibilidade com a versão anterior são mudanças que fazem com que o código que funcionava antes não funcione mais com a nova versão.

Quando isso acontece o mantenedor da biblioteca deve incrementar o número major e resetar os números minor e patch para 0.

Assim como na minor, é possível que novas funcionalidades e correções de bugs sejam incluídas em uma nova versão major, mas mesmo que isso aconteça, devemos manter o reset dos números minor e patch para 0.

Guia de migração

É bem comum que os desenvolvedores das bibliotecas forneçam um guia de migração para ajudar os usuários a migrarem de uma versão para outra. Bibliotecas como o React, Next.js e Jest, por exemplo, possuem guias de migração bem detalhados:

Changelogs

Além do guia de migração, também existe um tal de "changelog", que é um documento que lista todas as mudanças feitas em uma biblioteca/aplicação, desde a primeira versão até a atual.

Com esse arquivo, conseguimos atualizar a versão da biblioteca mesmo que ela não forneça um guia de migração, pois, se bem escrito, ele deve conter todas as mudanças e, principalmente, todas as breaking-changes que ocorreram.

Também podemos encontrar esse tipo de informação dentro das releases no repositório da biblioteca, como, por exemplo, no repositório do React

Exemplo na prática:

Aqui teremos uma simulação de biblioteca que provém funções matemáticas básicas e que segue o SemVer.

export function sum(a, b) {
  return a + b;
}

// temos um bug aqui, a subtração está somando
export function subtract(a, b) {
  return a + b;
}

No exemplo acima, temos uma simulação de lib onde, a cada nova versão, o número de versão é incrementado seguindo o semantic versioning.

Além disso, temos um arquivo CHANGELOG.md que lista todas as mudanças feitas em cada versão, desde a primeira até a última.

Versões 0.y.z

Uma biblioteca com versão 0.3.0, por exemplo, indica que ela ainda está em fase de desenvolvimento, isso significa que ela pode ter breaking changes a qualquer momento, e essas breaking changes não irão incrementar o número major.

O desenvolvedor da biblioteca deve seguir o SemVer a partir da versão 1.0.0. A versão 1.0.0 deve ser lançada quando a biblioteca estiver pronta para ser utilizada em produção e sua API (métodos/funções/classes que ela disponibiliza ) estiver estável.

Um grande exemplo disso é o React Native, que está por aí há anos e ainda continua em uma versão de desenvolvimento. Quem já precisou atualizar a versão do React Native sabe o trabalho que isso pode causar com tantas breaking changes possíveis 🙁

Portanto, tenha cuidado a atualizar as dependências do seu projeto que estão em versões de desenvolvimento (0.y.z), pois podem ocorrer inconsistências. Sempre leia o changelog e procure por guias de migração na documentação da biblioteca.

Ponto importante:

Caso você venha a desenvolver uma biblioteca, tome cuidado ao utilizar ferramentas como o semantic-release, lerna, para gerenciar o release da primeira versão 1.0.0. Caso sua biblioteca esteja em alguma versão 0.x.y, essas ferramentas podem não modificar a versão para a primeira major 1.0.0, mesmo que você informe a elas que ocorreram breaking changes, pois elas seguem o semantic version e não irão incrementar o major enquanto a versão for 0.x.y.

Prereleases

Além das versões 0.y.z, também temos as prereleases, que são versões lançadas antes da versão final proposta, para testes e feedbacks da comunidade.

Essas versões são identificadas por um sufixo, como 1.0.0-alpha.1, 1.0.0-beta.1, 1.0.0-rc.1, etc. Quando os testes são finalizados e a versão estiver pronta para ser lançada, a versão final será lançada sem o sufixo.

Podemos ver que o Next.js utiliza bastante as versões prereleases para testar novas funcionalidades antes de lançar a versão final.

Tais sufixos não seguem nenhuma regra específica, ficando a cargo do mantenedor da biblioteca decidir como irá nomear essas versões. Porém é comum vermos alpha, beta, next, rc e canary sendo utilizados.

Elas não são instaladas por padrão ao utilizar o comando npm install, sendo necessário informar o sufixo para instalar uma versão prerelease.

Exemplo:

npm install [email protected]

E para terminar, vale mencionar que o SemVer é uma convenção e não uma regra. Portanto, é possível que algumas bibliotecas não sigam essa convenção, mas é importante que o desenvolvedor da biblioteca informe isso em sua documentação.

Por fim, quem criou o SemVer foi Tom Preston-Werner e sua documentação pode ser encontrada em semver.org

Sem comentários ainda, seja o primeiro a comentar!