Semantic Versioning
Mauro de Carvalho
Posted on November 13, 2021
Essa é a terceira postagem correspondente à minha jornada para ser um desenvolvedor melhor. Novamente, gostaria de deixar claro que estou seguindo o excelentíssimo Web Developer Roadmap 2021, criado por Kamran Ahmed, como base para os meus estudos. Caso você queira ler a a minha postagem anterior, na qual eu resumi um pouco sobre o DRY, YAGNI e KISS, clique aqui.
Introdução
Quando entramos no mundo do gerenciamento de software, existe algo extremamente enfadonho e que assombra qualquer equipe de desenvolvimento conhecido como Inferno das dependências (“dependency hell”). Quanto mais o sistema cresce, e consequentemente quanto mais pacotes são adicionados a ele, maior é a probabilidade de quiçá, um dia, você encarar o abismo. E meu amigo, citando Friedrich Nietzsche, quando se olha muito tempo para um abismo, o abismo olha para você.
Certo, eu concordo. Talvez eu realmente tenha pegado um pouco pesado invocando Nietzsche (risos), mas vamos continuar falando um pouco sobre esse bendito Inferno das dependências, e para isso, devemos entender dois conceitos e como eles ocorrem. São eles:
Bloqueio de versão: Ocorre quando as especificações das dependências são muito ‘amarradas’, ou seja, quando há uma incapacidade de atualizar um pacote sem ter de liberar novas versões de cada pacote dependente.
Promiscuidade da versão: Ocorre quando as dependências são vagamente especificadas, ou seja, assumindo compatibilidade com futuras versões mais do que é razoável.
O Inferno das dependências é exatamente onde você está quando o Bloqueio de versão e/ou a Promiscuidade da versão impedem que você siga em frente de maneira fácil e segura.
Contudo, existem meios de evitar que problemas como esse aconteçam, e é aí que entramos no tema principal desta publicação, o Semantic Versioning (Versionamento Semântico).
Semantic Versioning
Basicamente, este um conjunto simples de regras e requisitos que ditam como os números das versões são atribuídos e incrementados, criado por Tom Preston-Werner. Provavelmente no seu dia-a-dia como desenvolvedor, você já deve ter visto em alguma linguagem de programação, biblioteca e/ou framework uma sequência de números assim:
Python 3.8.0
Django 2.2.7
Node.js 13.0.1
React 16.11.0
E eu me arrisco a afirmar que você saiba que essa sequência de números corresponde a uma versão específica de um software. Porém, você sabe qual é a lógica por trás desses números?
Como o próprio criador dessa especificação diz em seu paper, que você pode ler na íntegra clicando aqui, esta não é uma ideia nova ou revolucionária. De fato, você provavelmente já faz algo próximo a isso. O problema é que “próximo” não é bom o suficiente. Sem a aderência a algum tipo de especificação formal, os números de versão são essencialmente inúteis para gerenciamento de dependências.
Contudo, antes de entrarmos na real especificação do Semantic Versioning, um adendo interessante é que Tom Preston-Werner definiu algumas palavras chaves para melhor aderência ao princípio:
“As palavras-chaves “DEVE”, “NÃO DEVE”, “OBRIGATÓRIO”, “DEVERÁ”, “NÃO DEVERÁ”, “PODEM”, “NÃO PODEM”, “RECOMENDADO”, “PODE” e “OPCIONAL” no presente documento devem ser interpretados como descrito na RFC 2119.”
Enfim, sem mais delongas, vamos partir para a especificação, distribuída sob a licença Creative Commons - CC BY 3.0, na qual é definida em 11 regras básicas:
- “Software usando Versionamento Semântico DEVE declarar uma API pública. Esta API poderá ser declarada no próprio código ou existir estritamente na documentação, desde que seja precisa e compreensiva.”
PS: É interessante salientar que “API pública” pode representar várias coisas, como: Classes, métodos e funções disponibilizadas por uma biblioteca, endpoints de uma API RESTful, endpoints de microserviços, etc.
“Um número de versão normal DEVE ter o formato de X.Y.Z, onde X, Y, e Z são inteiros não negativos, e NÃO DEVE conter zeros à esquerda. X é a versão Maior, Y é a versão Menor, e Z é a versão de Correção. Cada elemento DEVE aumentar numericamente. Por exemplo: 1.9.0 -> 1.10.0 -> 1.11.0.”
“Uma vez que um pacote versionado foi lançado (released), o conteúdo desta versão NÃO DEVE ser modificado. Qualquer modificação DEVE ser lançado como uma nova versão.”
“No início do desenvolvimento, a versão Maior DEVE ser zero (0.y.z). Qualquer coisa pode mudar a qualquer momento. A API pública não deve ser considerada estável.”
“Versão 1.0.0 define a API como pública. A maneira como o número de versão é incrementado após este lançamento é dependente da API pública e como ela muda.”
“Versão de Correção Z (x.y.Z | x > 0) DEVE ser incrementado apenas se mantiver compatibilidade e introduzir correção de bugs. Uma correção de bug é definida como uma mudança interna que corrige um comportamento incorreto.”
“Versão Menor Y (x.Y.z | x > 0) DEVE ser incrementada se uma funcionalidade nova e compatível for introduzida na API pública. DEVE ser incrementada se qualquer funcionalidade da API pública for definida como descontinuada. PODE ser incrementada se uma nova funcionalidade ou melhoria substancial for introduzida dentro do código privado. PODE incluir mudanças a nível de correção. A versão de Correção deve ser redefinida para 0(zero) quando a versão Menor for incrementada.”
“Versão Maior X (X.y.z | X > 0) DEVE ser incrementada se forem introduzidas mudanças incompatíveis na API pública. PODE incluir alterações a nível de versão Menor e de versão de Correção. Versão de Correção e Versão Menor devem ser redefinidas para 0 (zero) quando a versão Maior for incrementada.”
“Uma versão de Pré-Lançamento (pre-release) PODE ser identificada adicionando um hífen (dash) e uma série de identificadores separados por ponto (dot) imediatamente após a versão de Correção. Identificador DEVE incluir apenas caracteres alfanuméricos e hífen [0-9A-Za-z-]. Identificador NÃO DEVE ser vazio. Indicador numérico NÃO DEVE incluir zeros à esquerda. Versão de Pré-Lançamento tem precedência inferior à versão normal a que está associada. Uma versão de Pré-Lançamento (pre-release) indica que a versão é instável e pode não satisfazer os requisitos de compatibilidade pretendidos, como indicado por sua versão normal associada. Exemplos: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.”
“Metadados de construção (Build) PODE ser identificada por adicionar um sinal de adição (+) e uma série de identificadores separados por ponto imediatamente após a Correção ou Pré-Lançamento. Identificador DEVE ser composto apenas por caracteres alfanuméricos e hífen [0-9A-Za-z-]. Identificador NÃO DEVE ser vazio. Metadados de construção PODEM ser ignorados quando se determina a versão de precedência. Assim, duas versões que diferem apenas nos metadados de construção, têm a mesma precedência. Exemplos: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.”
“A precedência refere como as versões são comparadas com cada outra quando solicitado. A precedência DEVE ser calculada separando identificadores de versão em Maior, Menor, Correção e Pré-lançamento, nesta ordem (Metadados de construção não figuram na precedência). A precedência é determinada pela primeira diferença quando se compara cada identificador da esquerda para direita, como se segue: Versões Maior, Menor e Correção são sempre comparadas numericamente. Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1. Quando Maior, Menor e Correção são iguais, a versão de Pré-Lançamento tem precedência menor que a versão normal. Example: 1.0.0-alpha < 1.0.0. A precedência entre duas versões de Pré-lançamento com mesma versão Maior, Menor e Correção DEVE ser determinada comparando cada identificador separado por ponto da esquerda para direita até que seja encontrada diferença da seguinte forma: identificadores consistindo apenas dígitos são comparados numericamente e identificadores com letras ou hífen são comparados lexicalmente na ordem de classificação ASCII. Identificadores numéricos sempre têm menor precedência do que os não numéricos. Um conjunto maior de campos de pré-lançamento tem uma precedência maior do que um conjunto menor, se todos os identificadores anteriores são iguais. 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.”
Conclusão
Eu poderia finalizar essa postagem com minhas próprias palavras sobre o Semantic Versioning, mas depois de ler esse trecho, tenho certeza que não conseguiria achar que superasse essa colocação:
“Como um desenvolvedor responsável você irá, é claro, querer certificar-se que qualquer atualização no pacote funcionará como anunciado. O mundo real é um lugar bagunçado; não há nada que possamos fazer quanto a isso senão sermos vigilantes. O que você pode fazer é deixar o Versionamento Semântico lhe fornecer uma maneira sensata de lançar e atualizar pacotes sem precisar atualizar para novas versões de pacotes dependentes, salvando-lhe tempo e aborrecimento.”
Caso você esteja interessado em saber mais sobre essa especificação, na qual na minha humilde opinião está muito bem definida e clara, sugiro que você leia ela na íntegra clicando aqui.
Se você assim como eu também está nessa jornada de aprendizado, sugiro também que você pesquise sobre outros tipos de versionamentos (Qual a diferença entre eles? Qual é mais interessante usar? Essas perguntas você só vai conseguir responder após ler sobre).
Tenho certeza que caso você esteja vendo essa publicação e chegou até aqui, é porque você e eu temos um desejo em comum, o de nos tornarmos desenvolvedores MUITO melhores.
Obrigado pela leitura e até a próxima! 😉
Posted on November 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.