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!

Namespacing and Solution Structuring

The bigger a Visual Studio solution gets the more important is a good structure. When either constructing a monumental application, a web of small connected applications (e.g. micro services), a single application with lots of different entry points (e.g. a Xamarin, ASP and WPF version of the same application) or a set of applications and tools that use the same logic components, you will get to the point where you need to define discrete components in your code.

The Big Picture

Components

Just like a file tree in your operating system, your solution is built as a tree. There are solution folders that are a purely virtual construct to structure your solution. They do not have to be part of the file system structure, but are held as a node in the .sln-file. They can be nested at will.
The second important structure element are your solution projects. Generally each project will compile to one assembly when you build your solution. This makes projects a tool to structure your deployed code. Also it is a scoping tool, als you can define classes and their members as internal. This makes those classes or members visible like public members or types, but only within that assembly. This is a very useful tool if you want a decoupled application and expose only interfaces and factories or builders and want to hide the concrete implementations from the rest of the world.

Folders placed inside of projects in the file system act in the same way within the solution, as child nodes in your solution tree.

Notation

Paths on the tree of a file system get constructed using delimiter notation. A windows file system path can be written as C:\users\foo\someDirectory with the delimiter \. Unix systems write them as /home/foo/someDirectory with the /-delimiter.

The same concept applies to the path on the tree that builds your solution. There the path is called a “namespace” and the nodes are separated by a period (.).

Example:
In the a solution named “CoreSkills” there is a solution folder “Examples”. The Examples folder contains a project named “StructuredData”. The StructuredData project contains a file system folder called “Models” and within that folder there is a class called “JSON”.
The fully qualified name (meaning the full namespace followed by the actual class name) would be CoreSkills.Examples.StructuredData.Models.JSON.

Reasons for Namespaces

Why should we use namespaces instead of just naming the class and use that name?

The most obvious reasons are preventing conflicts and providing context.
Picking up the example from above JSON might be a very suitable name for the class, but without using namespaces there is a big risk of someone else naming their class JSON as well, as JSON is a popular format for storing data or moving it between systems. Also the use of the class is only vaguely told by its name, but when you look at the full qualified name with the namespace included you see that it is used for depicting structured data, not for transport or persistence.

When coding close to the origin you can call it by its short name JSON, but the further away from its origin namespace you use it, the more of the name space would be appropriate to use. If you are for example in the class CoreSkills.Examples.Persitence.Serializer you could call the type StructuredData.Models.JSON to distinguish it from a type called CoreSkills.Examples.Persitence.JSON, referred to as JSON in that context.

Or you could call both JSON classes by their fully qualified class name for maximum clarity.

So using namespaces gives you the ability to choose how much verbosity you want to have in your code as a trade off against readability, while granting context.

Reasons for a nested solution structure

There are three main reasons for nesting your solution structure.

  1. Readability
  2. Security
  3. Maintainability

To be honest maintainability emerges from the other two, but it is important enough as a reason to be mentioned on its own.

Readability

When you use nested solution folders, solution projects and project-internal file system folders you get multiple readability benefits.

The most obvious one is the solution tree folding in your IDE. Most IDEs like Visual Studio or anything built by JetBrains allow to collapse or expand your solution tree as you please. This way you only have to look at what currently is relevant to your work.

When your solution starts to grow you do not want so see 50 or more projects next to each other. If you set aside an individual namespace for all your tests that also allows you to put them aside when you are only working on your business logic and not your tests.

Also it is a good visual indicator to have the namespaces written as assembly file names, so you can tell at the first glance, what CoreSkills.Examples.Persitence.dll does.

The same applies when you decide to use a layer pattern. Then the structure dictates that your namespace contains phrases like Foundation, Feature and Project or phrases like Persistence and UI.

Security

As briefly touched above solution structuring can be used as a tool to slim out your public api. There is less risk of exploits or you breaking other people’s code if you only ever expose interfaces and factories/builders. If you declare everything internal that only really matters internally no one can rely on it.
But keep in mind, that you still need to be able to unit test your external API. Depending on your testing philosophy you might have to weigh your priorities here.

Maintainability

Last but not least a good solution structure enables a maintainable code base. If you cannot break other people’s code as easily you also are less likely to break your own.

It’s easier to introduce new, different logic and test your code if you heavily rely on interfaces.

If you have distinct namespaces for distinct parts of your application you will find what you need faster, are less likely to produce duplicate code inadvertently and are less likely to put new code in bad places.

Also it is much easier to extract or replace components if you draw clear lines of concerns and structure in your code base. You can pull out parts of your code to build a NuGet package from it or deprecate full projects.

For quick and dirty tooling you could even pick up a single assembly, drop it into a different solution and use it there without having the plain text code in there.

The final lesson

In the end finding the right structure for your solution and your name spaces is up to you, but you should definitely think at least twice about your structure when you start a new solution or significantly grow an existing one.
Your future self will thank you for the time you spend on it tenfold!

The importance of coding guidelines

Deciding how to do things and sticking to it, until a solid reason arises to change it, is important.

In fact a concrete definition of how to do things is even more important than what you actually decide to do. You might lean back now, with a suspicious look on your face, when I say that, but hear me out.

Read more

Dependency Injection

Dependency injection is a great tool to decouple your software architecture and manage your dependency graph.

At the most fundamental level dependency injection means handing dependencies, ideally as abstractions rather than concretions, as a parameter of some sort instead of directly coupling them to the consuming logic.

This allows to define a foundation for a type of dependency at a low-level in your code, use it in a medium level and decide on the concrete implementation and the proper initialisation of the dependency on a high level.

Read more

Testing 101 with xUnit

Unit testing your business logic is essential to maintainability. Only if you have a good coverage (read as “as complete as reasonable in your situation”) and enough regression tests (tests proving that something that was broken and got fixed stays fixed) you can be sure not to break anything when refactoring or changing something down the line.

Also if some manager asks you “can your code do X?” and you don’t know: Write a unit test for “X” and you know for now and all time later.

Read more

Top 7 Podcasts for the modern software developer

As a modern software developer you should always look out for ways to improve yourself as a programmer, architect and human being. Being a professional software developer is being a lifelong learner!

I found a very good medium for self-improvement to be podcasts. I like to listen to them on my commute. The thing where podcasts (and audiobooks for that matter) trump traditional books, blogs etc. is: you don’t need your eyes for them! You can listen no matter if you drive with your own car, ride your bike or use public transportation.

In this week I share with you my personal top 7 podcasts every modern and driven (.NET) developer should subscribe and listen to.

Read more

ASP.NET Core (MVC) 101

In today’s post I will explain the basic features, purpose and usage of the ASP.NET Core MVC framework and thus the underlying ASP.NET Core framework.

Basic features

ASP.NET Core is the default .NET Core web hosting framework. MVC is, as you already guessed, the Model-View-Controller-extension for it.

Read more

Why .NET Core is awesome!

You could be here, reading this, for a number of reasons.

  • You don’t know what .NET Core is and are intrigued
  • You are a driven and curious developer and want to know the new and shiny is.
  • You kinda know .NET Core and want to dig deeper.
  • You believe to know .NET Core and wonder what might be so awesome.
  • You stumbled upon this post and have no clue what this all is about.

Whatever brought you here, I got you covered.

Read more

This website is using Google Analytics. Please click here if you want to opt-out. Click here to opt-out.