The real meaning of semantic versioning

Most likely you have seen dot-separated version numbers. Maybe at some point single-dot versions like 2.13 have brought up some confusion, as 2.13 is bigger than 2.2. And when more granular version numbers like 0.3.0.1 appear any attempt to use floating point logic falls apart.

But this is no floating point notation. It is semantic versioning.

A little history lesson

When the only way of distributing software was via disk (first floppy, than compact) it was sufficient to say “this is version 1, that is version 2 of our software”. There was no in-between.

Sometimes there was an exception. For some reason a new version had to be released that was not groundbreaking enough to say “well, you have our version 3, here is version 4”, but too significant to wait until there are the big revelations rolling in on the roadmap. This is where the the major an minor release was born.

In the beginning the most common minor release version was with long-living operating systems; e.g. the combination of Windows 3.11 and MSDOS 6.22, where the big new thing on Windows 3.11 was the networking capabilities being treated as a first class citizen for the first time, granting this version the subtitle “Windows for Workgroups”.

While there were internal build-numbers for development teams to refer to the current version at individual points in time, this was the most granular, widely used, versioning system until the computer magazines with disk and later the internet hit the industry.

With disk-bearing magazines there was a distribution channel for rolling out software updates to a broad audience at ease. This allowed to roll out more in-between versions to the public, fixing bugs and sometimes bringing minor features to make the customer happy and keep them on your product until the next major release happened. This is where the patch-level hit the consumer mass-market, bringing the 2.4.42-style versioning.

Then the internet happened. Suddenly you could roll out updates at any time you wanted with next to no additional cost, once the infrastructure was provided. You just had to tell your customer to look for it and download the update installer. Hotfixes came rolling in, fixing the most annoying or most critical bugs right after the fix being written. In a best case scenario the customer could get a hold of the hotfix mere hours after the QA finished their work. Welcome the fourth number: 2.4.42.12.

Lastly the broadband became widely available. Welcome to the present, where most software is self-updating and nearly anything with people interacting directly over the network updates basically every time you start the program, probably without you actually doing anything about it. You became used to your favourite online game having a launcher with a progress bar filling up until the “play”-button gets unlocked. And with that the build numbers actually fought their way out to the customer. Most software today gets shipped as ‘major’.’minor’.’patch’.’hotfix’-‘build’. Some pieces of software that changes very rapidly, like some massively online games, even got rid of version numbers completely by changing to a ‘date’-‘build’ model like ‘2018/04/25-5132’. But this is not for everyone. Why? Compatibility and breaking changes!

The meaning of the levels

If you cannot guarantee, that every customer is on your latest build you need to keep compatibility in mind and need a good way to scope problems if a customer calls in or submits a customer service ticket. Most online games do that by only booting the application through a self-update-launcher and shutting the app down once the game servers know of a new release.

This is where the actual meaning of the version levels become relevant.

Major release

This is the big gun. This is the “everything goes” level. Do not expect anything old to be compatible when you update to a new major version as a user. If you break some external interface like APIs or file formats as a developer these changes often get postponed to the next major release. Usually architectural changes, protocol changes and others changes in the structure of the program and its interface have to wait for the next major release to make it to the public.

When you update a major version in your dependencies be careful and test everything touching the dependency, even when only touching it indirectly!

This bump either appears when revolutionary things like a rewrite happen or when marketing needs a reason to get those dollars for a “new version” being sold or sell a big new functionality as a unique selling point.

Minor release

Depending on your project’s scope, longevity and culture this can mean one of two things:

  1. This might break small, unimportant things for a small portion of the users, but nothing groundbreaking.
  2. This includes some new features of significance. We won’t break your system, but the footprint might change and you can do something great now, you could not do before.

Depending on what you build you can see what fits better for you. If you build a widely used plugin or framework you should try really hard to break nothing at all in a minor release. If your application is a end user gui where the user needs to make 2 clicks more now, than before to save in a particular format, you might be fine with that.

Patch

Patches traditionally are only meant as a collection of bug fixes, sometimes minor features get released through a patch as well.

Patches do not change the public api or behaviour of your application other than by fixing previous misbehaviour. When you work on a big plugin or framework you, again, might want to consider either providing a fallback or postpone the fix to the next release version, if that misbehaviour is known to be relied on for some kind of workaround for something.

Your might want bug fixes in a patch release rather than wait until the next minor for different reasons

  • The next minor is still quite some time away.
  • This fix is of some urgency and should reach even those people, who might hesitate to bump to a new minor version.
  • This fix would make a very big portion of your customers happy enough, that it’s worth not to keep them waiting.

Hotfix

Hotfixes are what they sound like – (bug) fixes that need to be rolled out while still ‘hot’. These are either very urgent and need to get out there as soon as possible or they should reach even the most update-wary consumer of your application.

More often than not hotfixes are patches for security issues or critical misbehaviour that makes the application useless to a significant portion of the username.

Build

A build practically happens every time a developer or continuous integration suite hits the “build the whole application at once”-button. While all the other version levels usually keep to the single or double digit numbers, build numbers can go into the thousands or tens of thousands in some projects.

Some projects reset this counter for each version bump, some carry it on forever.

Usually this is no level to be released to the general public. There are settings like canary or nightly builds or Microsoft’s fast lane release pipeline that release builds before they get bundled to a version bump and some projects just put the build number behind the version number anyway, even though there might be only one official “1.5.23.0”.

Bumping versions

Now you know the meaning of each number, but how do you properly increment them?

Simple: Whenever you bump one, reset all levels right of it to 0. And if you want to do it right do not skip numbers.

Do-Examples:

  • 1.2.3.4 -> new minor release -> 1.3.0.0
  • 4.3.2.1 -> new hotfix -> 4.3.2.2
  • 4.3.2.1 -> new patch -> 4.3.3.0
  • 0.8.0.3 -> first major release -> 1.0.0.0

Don’t-Examples:

  • 1.2.3.4 -> new minor release -> 1.4.0.0 (minor skipped)
  • 4.3.2.1 -> new hotfix -> 4.3.3.1 (hotfix not reset)
  • 4.3.2.1 -> new patch -> 4.3.3.2 (bumped patch and hotfix at the same time)
  • 0.8.0.3 -> first major release -> 1.0.0.1 (hotfix version skipped)

Why is that so?

You can include everything that goes into a patch into a minor release as well – so there is no need to bump the patch with the minor.

Users get confused if you skip versions. This usually is only used as a marketing tool. Do you remember Windows 9 or php 6? No? That’s because they every publicly existed. Windows 9 was skipped to move the public recognition of the next version further apart from the Windows 8 hate and php 6 was partly because it was just scrapped half way through development and partly because of the infamous reputation of php5.

Build numbers are quite arbitrary, as stated before. Those can basically be handled any way you like, as long as you do it consistently.

Why?

So should you use semantic versioning in your application? That totally depends on your project. Why? Because there are three main reasons for it, that may or may not be applicable to your specific project.

  1. You have other developers integrating with your system; maybe even in some (semi) automated way like NuGet or npm. In that case you want to let the consuming developer and his tooling know if you potentially break their code with your changes and you want them to know how important/impactful a new version is. Those cannot be achieved by date-based versioning.
  2. You want to establish an easily understandable release culture so any end customer using your app can decide if they want the new and shiny or rely on the proven – or at least let them know why they have to wait for a 250MB auto-update to be downloaded and installed.
  3. You want to keep track of development velocity when working as a team on a long-running project.

If you can say yet to at least one of the above you may very likely benefit from using semantic versioning instead of other kinds of keeping track of releases.

Closing note

If you decide to use semantic versioning in your project there is some wiggle room in how you define the levels in you project; especially if you have nothing placed intentionally, that other developers can hook into.

If there are developers hooking into your system (via file exchange, programming APIs or networking APIs), keep the most important rules in mind:

  • Only mayor version may ever break your consumer’s code.
  • Completely new features always at least bump the minor version
  • Decide for your individual case if you want to use hotfix and/or build numbers of if you stick to 3-number versioning.
  • Whatever you do: Stay consistent!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.