Versionamento Semântico – Ou como versionar software

Quando você cria softwares, você precisa lidar com versões em algum momento do desenvolvimento. Seja para gerenciar dependências, seja para dar um número de versão adequado para aquela mudança ou conjunto de mudanças que você fez. Mas, como representar corretamente uma mudança? Afinal, mudanças nem sempre são drásticas: Pode ser uma simples solução de bug, ou algum recurso tão simples que não afeta em nada os outros recursos do seu aplicativo. Bom, por essa razão hoje vou falar um pouco sobre o Versionamento Semântico, um padrão que, quando seguido corretamente, ajuda a garantir que cada nova versão lançada represente corretamente as mudanças feitas em relação à versão anterior.

Por quê o versionamento semântico é necessário?

O versionamento semântico hoje é um dos padrões de versionamento mais usados e conhecidos. Criado para evitar o chamado “dependency hell” (inferno das dependências), tem o objetivo de evitar que atualizações de dependência quebrem o seu software indevidamente, enquanto permitindo que você tenha uma noção do status de estabilidade do software (se é “seguro” usar em produção, por exemplo) e possa identificar se uma nova versão possui apenas novos recursos ou se possui apenas correções de bugs, por exemplo.

Ao todo, são 11 “regras” que compõem a especificação, e que, quando seguidas corretamente, garantem, em teoria, que as dependências do seu projeto sejam estáveis e que não ocorra surpresas durante a instalação delas e o seu uso por parte do seu projeto. Um exemplo dessas regras (vou falar mais sobre elas abaixo), por exemplo, é a que obriga que uma determinada versão de um software seja imutável, ou seja: Lançada uma versão, ela sob circunstância alguma pode ser apagada, editada ou modificada de qualquer jeito, independente de quaisquer bugs que tal versão possua. Assim, caso você tenha feito alguma modificação – qualquer modificação – e queira lançá-la, precisa lançar uma nova versão, por exemplo.

Caso essa regra não existisse, é fácil imaginar a bagunça que se tornaria o uso de dependências: Imagine você usando uma dependência na versão 1.0.0 no seu projeto, e, do dia para noite, seu software parar de funcionar graças ao fato de que o autor dessa dependência simplesmente resolveu modificar a versão 1.0.0 com alguma mudança que quebrou o seu software, seria um saco, não seria? É por isso que, por mais sutil – e talvez até óbvia – que a regra seja, ela ainda se faz importante e garante que tudo está funcionando nos seus conformes.

Além disso, o versionamento semântico também possui regras que definem o que fazer em relação ao número de versão quando:

  • O software ganha novos recursos;
  • Alguns bugs são corrigidos no software;
  • Mudanças na API pública acontecem;

Dessa forma, você consegue saber mais ou menos o que esperar quando vai atualizar o software: se há novos recursos disponíveis para aproveitar, se bugs que podiam afetar o seu projeto vão ser corrigidos,  ee vai ter mudanças que podem quebrar o seu software, e assim por diante.

Com isso, já dá para ter uma ideia de alguns dos motivos pelo qual um padrão de versionamento é importante. Agora, vamos às regras que o versionamento semântico define.

As regras

Todo padrão tem regras, e com o versionamento semântico não poderia ser diferente. Segue abaixo:

Primeira regra – API pública

O documento original do versionamento semântico define:

Software using Semantic Versioning MUST declare a public API. This API could be declared in the code itself or exist strictly in documentation. However it is done, it should be precise and comprehensive.

Ou seja, todo software usando versionamento semântico precisa definir uma API pública, seja na documentação, seja declarado no código. A existência dessa API pública pode representar várias coisas:

  • Disponibilidade de classes, métodos e funções para ser usados por outros softwares (é o caso de bibliotecas);
  • Se o seu software não foi feito para ser usado por outros softwares, algum meio de interação do seu software com os usuários do mesmo (layout, talvez?);
  • Endpoints de API (no caso de micro-services, por exemplo);

O que representa um API pública, na prática, depende exclusivamente do objetivo e da forma de interação que seu software usa.

Segunda Regra – Formato do número de versão

A especificação original do versionamento semântico define:

A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor version, and Z is the patch version. Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0.

Basicamente, essa regra define que todo software que usa versionamento semântico deve obrigatoriamente ter versões no formato x.y.z, com x, y e z sendo inteiros não-negativos e sem conter zeros à esquerda (ou seja, x ser 1, é ok, agora 01 já não pode).

Além disso, essa regra define nomes para x, y e z para uma versão exemplo x.y.z. A saber:

  • x é a versão principal (ou major version);
  • y é a versão secundária (ou minor version);
  • z é a versão de remendo (ou patch version);

Como você pode ver, os nomes entre parenteses, em inglês, com total certeza são bem melhores do que os fora do parenteses, em português. Mas é detalhe. Dado os nomes de cada número, suas responsabilidades vão ser dadas nas próximas regras.

Um outro detalhe também que é válido lembrar: Ao ser lançada uma nova versão, os números de cada componente, x, y, ou z, podem apenas ser incrementados. Ou seja, se um software está na versão 2.0.0, ele não pode ir para a versão 1.9.9 como uma “evolução” da versão 2.0.0.

Terceira regra – Versões imutáveis

A especificação original do versionamento semântico define a terceira regra da seguinte forma:

Once a versioned package has been released, the contents of that version MUST NOT be modified. Any modifications MUST be released as a new version.

Como abordado acima, essa regra define que, uma vez que uma determinada versão seja disponibilizada, ela não pode ser modificada de nenhuma maneira. Assim, quaisquer mudanças envolvendo tal versão, mesmo que seja correções de bug, devem ser lançadas como novas versões, de forma que, uma vez que um software esteja usando uma dependência fixada em uma determinada versão, seu comportamento permaneça estável caso não haja mudanças no código do software. Isso não quer dizer que o software não vai ter bugs, só quer dizer que, caso o código do software não seja modificado, seu comportamento em relação ao uso da dependência em uma versão fixa será sempre o mesmo.

Quarta Regra – Desenvolvimento inicial

A quarta regra na especificação original do versionamento semântico define:

Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.

Basicamente, nessa regra é definido que, todo projeto em status de desenvolvimento inicial deve ter sua principal versão setada como zero, pois qualquer coisa pode mudar a qualquer hora (algo comum em projetos em fase de desenvolvimento inicial)  e devido a isso a API pública não deve ser considerada estável.

Isso implica que a API pública de um projeto em desenvolvimento inicial (com versão principal setada em zero) pode mudar a qualquer hora, mas não significa  necessariamente que dependências do tipo não possam ser usado em projetos, só é um alerta para tomar mais cuidado com futuras atualizações (afinal, a API pública pode mudar drasticamente de uma versão para outra), mesmo aquelas no qual há mudanças na versão secundária e na versão de patch.

Quinta Regra – Versão 1.0.0

A quinta regra do documento oficial definindo o versionamento semântico fala:

Version 1.0.0 defines the public API. The way in which the version number is incremented after this release is dependent on this public API and how it changes.

Ou seja, a partir da versão 1.0.0, a API pública pode ser considerada definida, ou seja, toda versão futura deve ser baseada (ou dependente) nesta API pública, e na forma como ela muda.

Dessa forma, uma versão 1.1.0, por exemplo, não pode ser baseada numa API da versão 0.8.0, completamente diferente da versão 1.0.0. Versões acima de 1.0.0 devem ser de alguma forma dependentes de alguma forma na API pública estabelecida na versão 1.0.0 (e apenas versões com mudança na versão principal podem ter mudanças que quebrem a compatibilidade com essa versão).

Sexta Regra – Versão de remendo (patch version)

A sexta regra do documento que define o versionamento semântico fala:

Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced. A bug fix is defined as an internal change that fixes incorrect behavior.

Ou seja, basicamente, essa regra define a “função” da versão de remendo no versionamento semântico, que é indicar a correção de bugs que mantém a compatibilidade com versões anteriores do software (ou seja, uma versão 1.0.1 corrige um problema da versão 1.0.0 e é totalmente compatível com tal versão).

Além disso, essa regra também define o que é uma “correção de bug”: Para o versionamento semântico, trata-se apenas de uma mudança interna que corrige comportamento incorreto. Ou seja, não trata-se de mudanças na API pública, que podem quebrar o uso do projeto em questão por outros projetos.

Sétima Regra – Versão secundária (minor version)

Essa regra, definida no documento que define o versionamento semântico, apresenta:

Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced to the public API. It MUST be incremented if any public API functionality is marked as deprecated. It MAY be incremented if substantial new functionality or improvements are introduced within the private code. It MAY include patch level changes. Patch version MUST be reset to 0 when minor version is incremented.

A sétima regra define o conceito de versão secundária, e mostra a que veio em relação ao versionamento semântico: Basicamente, ela só deve ser incrementada sob três condições:

  • Quando funcionalidades novas, mas ainda compatíveis com as versões passadas, são inseridas na API pública;
  • Quando alguma funcionalidade da API pública é marcada como depreciada (deprecated);
  • Opcionalmente quando novas funcionalidades ou melhorias são introduzidas no código privado (não acessível diretamente a partir da API pública);

Além disso, versões com incremento na versão secundária opcionalmente incluem mudanças a nível de correção de bugs relativas a versões anteriores, e a versão de remendo (patch version) deve ser obrigatóriamente setada para zero (ou seja, se um projeto na versão 1.0.5 tem sua versão secundária incrementada, a versão de remendo deve ser zerada, assim, a próxima versão com versão secundária incrementada deve ser 1.1.0, e não 1.1.5).

Oitava Regra – Versão principal (major version)

A oitava regra, conforme definida no documento que define versionamento semântico, é explicada originalmente como:

Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API. It MAY include minor and patch level changes. Patch and minor version MUST be reset to 0 when major version is incremented.

Nona Regra – Versões de “pré-lançamento” (pre-release versions)

A 9º regra, conforme definida no documento que define versionamento semântico, trada das versões de pré-lançamento e é explicada como:

A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version. Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes. Pre-release versions have a lower precedence than the associated normal version. A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.

Essa regra define algo que, embora não seja obrigatório nos números de versão padronizados pelo versionamento semântico, pode ajudar em algumas situações: Imagine um projeto que é tão importante que precisa garantir que tudo esteja bem testado antes de lançar uma versão oficialmente – como um driver de banco de dados, por exemplo – Esse tipo de software pode contar com o uso de versões de pre-release, ou seja, versões futuras do software que ainda não estão estáveis o suficiente, mas que após estarem estáveis vão ser lançadas com aquele número de versão.

Assim, um projeto pode se tornar 1.0.0-alpha.1, 1.0.0-alpha2, 1.0.0-beta.1, 1.0.0-beta.2 e assim por diante até ficar, finalmente, com a versão 1.0.0, por exemplo.

Décima Regra – Sobre dados de build

A 10º regra, conforme definida no documento que define versionamento semântico, trada de dados adicionais de build e é explicada como:

Build metadata MAY be denoted by appending a plus sign and a series of dot separated identifiers immediately following the patch or pre-release version. Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Build metadata SHOULD be ignored when determining version precedence. Thus two versions that differ only in the build metadata, have the same precedence. Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.

Como você pode ver,  essa regra é, novamente, apenas um adicional, e portanto, opcional, às versões que respeitam o versionamento semântico:  segundo essa regra, você pode, se quiser, adicionar informações extras de build junto à versão usando “+” seguido das informações adicionais separadas por pontos e que contem apenas caracteres alfanuméricos e hifen.

Mas, que informações extras de build são essas? Simples: Pode ser o timestamp de quando o build foi feito, um hash do commit ou de um dos arquivos de build, por exemplo. E assim por diante.

Décima-Primeira Regra – Sobre precedência de versões

Nessa 11º regra, o documento do versionamento semântico fala sobre algo que é muito usado por gerenciadores de dependência como Bower e Composer. Veja a definição:

Precedence refers to how versions are compared to each other when ordered. Precedence MUST be calculated by separating the version into major, minor, patch and pre-release identifiers in that order (Build metadata does not figure into precedence). Precedence is determined by the first difference when comparing each of these identifiers from left to right as follows: Major, minor, and patch versions are always compared numerically. Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1. When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version. Example: 1.0.0-alpha < 1.0.0. Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by comparing each dot separated identifier from left to right until a difference is found as follows: identifiers consisting of only digits are compared numerically and identifiers with letters or hyphens are compared lexically in ASCII sort order. Numeric identifiers always have lower precedence than non-numeric identifiers. A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal. Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.

Explicando de forma bem simples, essa regra define a “ordem” que as versões que seguem o versionamento semântico devem ter, ou seja, que versão vem antes ou depois de outra versão, por exemplo. No caso do versionamento semântico, essa ordem é definida primeiro numericamente (1.0.0 < 2.0.0 < 2.0.1 < 2.1.0, por exemplo) e, se a versão for igual, é considerada então a versão de pre-release (definido na nona regra), só que dessa vez seguindo algumas regras:

  • Cada identificador (separado por ponto) é comparado da esquerda para a direita, até que uma diferença seja encontrada;
  • Identificadores compostos apenas por números são comparados..numericamente;
  • Identificadores contendo letras ou hifens são comparados lexicalmente conforme a organização da tabela ASCII, ou seja “A < a < b”.
  • Se os identificadores forem misturados entre numéricos e não-numéricos, então não-numéricos tem mais precedência que numéricos;
  • E por fim, uma versão com maior quantidade de identificadores tem mais precedência do que uma versão com menor quantidade de identificadores, no caso da versão com maior quantidade de identificadores ter todos os identificadores da versão com menor quantidade de identificadores na mesma posição (ou seja, 1.0.0-alpha < 1.0.0-alpha.1);

Usando essas regras, você consegue definir da melhor forma possível uma lista de versões compatíveis com o software que você está desenvolvendo, e isso é útil especialmente para criadores de bibliotecas, pois:

  • Reduz chance de conflito com outras bibliotecas – Imagine que 3 bibliotecas: A, B e C. A e B tem como dependência a biblioteca C. Agora, considere que A definiu que precisa da versão 1.2.3 da biblioteca C, enquanto B está um pouco mais desatualizada e tem definido como dependência a versão 1.1.0 da biblioteca C. Isso implica que A e B não podem ser instalados simultaneamente em um projeto, pois dependem de versões diferentes de C. Agora considere que A na verdade não usa os recursos novos encontrados nas versões 1.2.x, mas que usa os recursos da versão 1.1.x em diante, se A definir como regra >=1.1.0 <= 1.2.x, então, torna-se possível instalar A e B em um projeto;
  • Garante que sua biblioteca tenha versões mais atualizadas – Se a sua biblioteca teoricamente funciona bem com uma dependencia da série 1.2.x, não há motivo aparente para especificar uma versão exata 1.2.3, e portanto faz sentido usar algo como 1.2.* como versão fixa;

Entre outras vantagens.

Óbviamente, vale ressaltar que para uma aplicação, utilizar essas regras pode ser um problema: Não faz sentido, num primeiro momento, não usar uma versão exata de uma dependência pois uma aplicação, diferente de uma biblioteca, normalmente não é “requerida” por outra aplicação diretamente (ou seja, instalada como dependência), e portanto o uso de versões exatas em aplicações tende a garantir mais consistência aqui (enquanto ajudando a evitar problemas maiores com dependências que não usam o versionamento semântico corretamente).

Conclusão

Depois de toda essa breve revisão por cada regra e aspecto do versionamento semântico, chega a pergunta que todos esperavam:

DEVO USAR VERSIONAMENTO SEMÂNTICO?

Uma opção de versionamento alternativa ao Versionamento Semântico..
Outra opção de versionamento.. (clique na imagem para ampliar). Vi no commitstrip.

Bom, aí vai a resposta: Depende. O versionamento semântico hoje é um dos padrões de versionamento mais conhecidos e usados, sendo usado por alguns software grande por aí, como o NodeJS (da versão 4 em diante, salvo engano_ e Ruby (a partir da versão 2.1.0), por exemplo. Mas como tudo no mundo do desenvolvimento, sempre há outras opções, como o versionamento sentimental (risos), e fica a seu critério decidir qual usar.

Dentre as opções que conheço, entretanto, o versionamento semântico me parece ser o mais bem documentado e definido, pois resolve rapidamente questões como qual deve ser a primeira versão de um software (que é a 0.1.0, e que não está especificado nas regras, mas sim numa seção de perguntas frequentes do site deles) enquanto te dá flexibilidade infinita para criar quantas versões quiser, seja para versão principal, secundária ou de patch (eu, por exemplo, já cheguei a usar 32 versões de patch em um software que criei, num padrão tipo 1.1.32, causados por modificações realmente pequenas junto as features adicionados na versão secundária do software), entre outras características bem interessantes.

No fim, isso ajuda a evitar problemas com dependências enquanto ajuda a entender melhor o que cada atualização definitivamente é, portanto, na maioria das vezes, o versionamento semântico tem mais a adicionar do que a atrapalhar.

Se você conhece outros padrões de versionamento ou tem alguma dúvida sobre o versionamento semântico, mande nos comentários!

Ah, e para quem quiser ler mais sobre o versionamento semântico, recomendo acessar a especificação oficial, que também possui uma tradução para português.

Até a próxima, pessoal. 🙂

Gostou do conteúdo desse post? Apoie o blog pagando a partir de R$1/mês através da nossa página no Apoia.se! Isso me ajudará a manter o blog no ar, além de trazer mais coisas legais para vocês! Obrigado desde já! 😀