No mundo de gerenciamento de software existe algo terrível conhecido como inferno das dependências (“dependency hell”). Quanto mais o sistema cresce, e mais pacotes são adicionados a ele, maior será a possibilidade de, um dia, você encontrar-se neste poço de desespero.
Em sistemas com muitas dependências, lançar novos pacotes de versões pode se tornar rapidamente um pesadelo. Se as especificações das dependências são muito amarradas você corre o risco de um bloqueio de versão (A falta de capacidade de atualizar um pacote sem ter de liberar novas versões de cada pacote dependente). Se as dependências são vagamente especificadas, você irá inevitavelmente ser mordido pela ‘promiscuidade da versão’ (assumindo compatibilidade com futuras versões mais do que é razoável). O inferno das dependências é onde você está quando um bloqueio de versão e/ou promiscuidade de versão te impede de seguir em frente com seu projeto de maneira fácil e segura.
Como uma solução para este problema proponho um conjunto simples de regras e requisitos que ditam como os números das versões são atribuídos e incrementados.
Para que este sistema funcione, primeiro você precisa declarar uma API pública. Isto pode consistir de documentação ou ser determinada pelo próprio código. De qualquer maneira, é importante que esta API seja clara e precisa. Depois de identificada a API pública, você comunica as mudanças com incrementos específicos para o seu número de versão. Considere o formato de versão X.Y.Z (Maior.Menor.Correção). Correção de falhas (bug fixes) que não afetam a API, incrementa a versão de Correção, adições/alterações compatíveis com as versões anteriores da API incrementa a versão Menor, e alterações incompatíveis com as versões anteriores da API incrementa a versão Maior.
Eu chamo esse sistema de “Versionamento Semântico”. Sob este esquema, os números de versão e a forma como eles mudam, transmite o significado do código subjacente e o que foi modificado de uma versão para a próxima.
As palavras-chaves “DEVE”, “NÃO DEVE”, “OBRIGATÓRIO”, “DEVERÁ”, “NÃO DEVERÁ”, “DEVERIA”, “NÃO DEVERIA”, “RECOMENDADO”, “PODE” e “OPCIONAL” no presente documento devem ser interpretados como descrito na [RFC 2119] (http://tools.ietf.org/html/rfc2119).
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.
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. X é a versão Maior, Y é a versão Menor, e Z é a versão de Correção. Cada elemento DEVE aumentar numericamente por incrementos de um. 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 e uma série de identificadores separados por ponto (dot) imediatamente após a versão de Correção. o identificador DEVE compreender apenas caracteres alfanumérios e hífen [0-9A-Za-z-]. Versão de Pré-Lançamento tem precedência inferior à versão normal a que está 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 versão de Correção ou Pré-Lançamento. Identificador DEVE compreende apenas caracteres alfanumérios e hífen [0-9A-Za-z-]. Metadados de Construção DEVEM ser ignorados quando for determinada uma versão precendente. Por isso, dois pacotes com a mesma versão, mas diferentes metadados de construção são considerados sendo da mesma versão. Exemplos: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
A precendê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 possuem precendência). Versões Maior, Menor e Correção são sempre comparadas numericamente. A precedência de versões de Pré-lançamento DEVE ser determinada comparando cada identificador separado por ponto 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. Exemplo: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
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 bastante. 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. Dando um nome e definições claras às ideias acima, fica fácil comunicar suas intenções aos usuários de seu software. Uma vez que estas intenções estão claras, especificações de dependências flexíveis (mas não tão flexíveis) finalmente podem ser feitas.
Um exemplo simples vai demonstrar como o Versionamento Semântico pode fazer do inferno de dependência uma coisa do passado. Considere uma biblioteca chamada “CaminhaoBombeiros”. Ela requer um pacote versionado dinamicamente chamado “Escada”. Quando CaminhaoBombeiros foi criado, Escada estava na versão 3.1.0. Como CaminhaoBombeiros utiliza algumas funcionalidades que foram inicialmente introduzidas na versão 3.1.0, você pode especificar, com segurança, a dependência da Escada como maior ou igual a 3.1.0 porém menor que 4.0.0. Agora, quando Escada versão 3.1.1 e 3.2.0 estiverem disponíveis, você poderá lançá-los ao seu sistema de gerenciamento de pacote e saberá que eles serão compatíveis com os softwares dependentes existentes.
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.
Se tudo isto soa desejável, tudo que você precisar fazer para começar a usar Versionamento Semântico é declarar que você o esta usando e então, seguir as regras. Adicione um link para este website no seu README para que outros saibam as regras e possam beneficiar-se delas.
A coisa mais simples a se fazer é começar sua versão de desenvolvimento inicial em 0.1.0 e, então, incrementar a uma versão ‘menor’ em cada lançamento subsequente.
Se seu software está sendo usado em produção, ele já deve ser provavelmente 1.0.0. Se você possui uma API estável a qual usuários passaram a depender, deve ser 1.0.0. Se você está se preocupando bastante com compatibilidade com versões anteriores, já deve ser 1.0.0.
A versão Maior zero tem o foco exatamente no desenvolvimento rápido. Se você está mudando a API todo dia, provavelmente você está na versão 0.y.z ou num branch separado de desenvolvimento, trabalhando numa próxima versão Maior.
Esta é uma questão de desenvolvimento responsável e conhecimento antecipado. Mudanças incompatíveis não devem ser levemente introduzidas para o software que tem um monte de código dependente. O custo que deve ser incorrido para atualizar pode ser significante. Tendo que aumentar a versão maior para lançar mudanças incompatíveis, significa que você pensará no impacto das suas mudanças, e avaliará a relação de custo/benefício envolvida.
É sua responsabilidade como desenvolvedor profissional documentar corretamente o software que será usado por outros. Gerenciar a complexidade de software é uma parte muito importante para manter o projeto eficiente, e isto é difícil de fazer se ninguém sabe como usá-lo ou que métodos são seguros de chamar. A longo prazo, Versionamento Semântico e a insistência em uma API pública bem definida podem deixar tudo e todos funcionamente suavemente.
Assim que você perceber que quebrou a especificação de versionamento semântico, conserte o problema e lance uma nova versão menor, que corrige o problema e restaura a retrocompatibilidade. Mesmo sob esta circunstância, é inaceitável modificar versões lançadas. Se for apropriado, documente a versão ofensiva e informe seus usuários do problema de forma que eles fiquem cientes da versão em questão.
Isso seria considerado compatível, uma vez que não afeta a API pública. Software que depende explicitamente das mesmas dependências que seu pacote, deve ter sua própria especificação de dependência e o autor notificará quaisquer conflitos. Para determinar se a mudança é a nível de correção ou modificação de nível menor dependente se você atualizou suas dependências a fim de corrigir um bug ou introduzir nova funcionalidade. Eu normalmente esperaria código adicional para última instância, caso em que é obviamente um incremento no nível menor.
Use o bom senso. Se você tem um público enorme que será drasticamente impactado pela mudança de comportamento de volta para o que a API pública pretendida, então pode ser melhor realizar um lançamento de uma versão maior, mesmo que a correção pudesse ser considerada estritamente uma versão de correção.Lembre-se, Versionamento Semântico trata de transmitir o conhecimento das mudanças ocorridas na versão. Se estas mudanças são importantes para seus usuários, utilize o número da versão para informá-los.
Descontinuar funcionalidades é um processo comum no desenvolvimento de software e muitas vezes é necessário para haver progresso. Quando você descontinua parte de sua API pública, você deve fazer duas coisas: (1) atualizar sua documentação, para que os usuários saibam das mudanças, (2) lançar uma versão Menor anunciando a descontinuação. Antes de remover completamente a funcionalidade em uma versão Maior deve haver ao menos uma versão Menor que possui a descontinução anunciada, fazendo com que os usuários realizem uma transição tranquila para a nova API.
A Especificação da Semântica de Versionamento é autoria de Tom Preston-Werner, criador do Gravatars e co-fundador do GitHub.
Caso queira deixar sua opinião, por favor abra uma issue no GitHub.