Versões semânticas (SemVer) explicado
Semântica de versão não é “número bonito”: é um contrato. Neste post eu organizo o raciocínio do SemVer, mostro como decidir entre major, minor e patch, e explico como tratar pré-lançamentos e compatibilidade real.
1) O que é SemVer e por que ele existe
O SemVer (Semantic Versioning) define um formato padronizado para a versão do software, normalmente usado por bibliotecas e APIs. A ideia central é que a mudança de versão comunique, com previsibilidade, o impacto para quem usa.
O SemVer reduz atrito entre equipes porque elimina adivinhação: o consumidor consegue entender a compatibilidade potencial antes de atualizar.
2) Como interpretar “MAJOR.MINOR.PATCH” com critérios objetivos
A versão segue o padrão: MAJOR.MINOR.PATCH (com opcionais para pré-lançamento e build). A parte mais importante é decidir quais tipos de mudanças justificam cada incremento.
-
PATCH (
z): correções de bugs, ajustes internos e melhorias que não alteram a API/contrato nem o comportamento observável de forma incompatível. -
MINOR (
y): adição de funcionalidades compatíveis para consumidores existentes. Tipicamente: novos recursos opcionais, novos endpoints com comportamento não-breaking, sobrecargas, novos campos com semântica compatível (ex.: retornos adicionais). -
MAJOR (
x): mudanças incompatíveis com versões anteriores. Se um consumidor precisa ajustar código para voltar a funcionar, é MAJOR.
Um detalhe que eu sempre reforço: “compatível” significa compatível na prática. Se o consumidor quebra por causa de uma mudança na forma esperada, a versão não pode ser minor ou patch — é major.
3) Compatibilidade real: o que quebra alguém (mesmo sem mudar endpoints)
Em APIs e SDKs, “não quebrou” é relativo ao contrato que o consumidor usa. Às vezes você altera algo que parece interno, mas muda comportamento observável.
Exemplos clássicos de mudanças incompatíveis (MAJOR):
- Remoção de endpoint, parâmetro obrigatório, campo ou evento usado por consumidores.
- Mudança de semântica: antes “X”, agora “Y” com o mesmo formato. Mesmo mantendo o tipo/shape, muda a interpretação.
-
Alteração de tipos de retorno/contratos que invalida parsing do consumidor
(ex.: antes retornava
string, agora retornaobject). - Regras de validação que deixam entradas antes aceitas como inválidas.
- Mudança de ordenação, idempotência, timeouts e garantias que alteram o comportamento dependente.
Para evitar surpresas, eu costumo validar mudanças com testes de contrato e cenários de consumidor: simulo o uso real (cliente, serialização, erros, estados) e vejo se a atualização “passa” sem ajustes.
4) Pré-lançamentos, build metadata e como escolher a próxima versão
Além de MAJOR.MINOR.PATCH, o SemVer permite identificadores opcionais:
-
Pré-lançamento:
MAJOR.MINOR.PATCH-PRERELEASE(ex.:1.4.0-beta.2). Indica instabilidade e pode quebrar compatibilidade. Consumidores normalmente tratam isso como “não estável”. -
Build metadata:
MAJOR.MINOR.PATCH+BUILD(ex.:1.4.0+20260429). É informativo; não deve afetar compatibilidade.
Na prática, existe um ciclo típico:
- Se a mudança é compatível: incrementa MINOR e, se aplicável, prepara beta.
- Se é correção compatível: incrementa PATCH (e sobe pré se estiver em ciclo de teste).
- Se é quebra: incrementa MAJOR e volta o MINOR/PATCH para valores base, seguindo seu padrão de release.
Exemplo técnico: decidir o incremento de versão
Use um “checklist” simples antes do release. Abaixo vai um pseudocódigo que eu uso como guia mental para escolher o tipo de mudança.
function nextVersion(current, change) {
// current: { major, minor, patch }
// change: { breaksContract, addsCompatibleFeatures, fixesBugs, prerelease, buildMeta }
if (change.breaksContract) {
return `${current.major + 1}.0.0${change.prerelease ? '-beta.1' : ''}${change.buildMeta ? '+' + change.buildMeta : ''}`;
}
if (change.addsCompatibleFeatures) {
return `${current.major}.${current.minor + 1}.0${change.prerelease ? '-beta.1' : ''}${change.buildMeta ? '+' + change.buildMeta : ''}`;
}
if (change.fixesBugs) {
return `${current.major}.${current.minor}.${current.patch + 1}${change.prerelease ? '-rc.1' : ''}${change.buildMeta ? '+' + change.buildMeta : ''}`;
}
// Nenhuma mudança relevante no contrato/comportamento
return `${current.major}.${current.minor}.${current.patch}${change.buildMeta ? '+' + change.buildMeta : ''}`;
}
O que torna isso útil não é o “código”: é a disciplina. Repare no primeiro caso (breaksContract) — ele domina todos os outros.
Quer continuar melhorando seu processo?
Se você gostou do raciocínio por trás de SemVer, vale a pena ler mais posts técnicos aqui no yurideveloper.com.br sobre versionamento, contratos de API e releases com previsibilidade.