SemVer is fine, actually

I recently came across an article by Anthony Fu in which he talks about the ways Semantic Versioning doesn't fully work, and why he typically releases software that's kept in the 0.x era of versioning.

Anthony takes issue with the fact that moving major versions feels more impactful when version numbers are lower, so going 2.x to 3.x feels like a bigger deal than going from 126.x to 127.x. He also mentions that, to work around this issue, he keeps most of his packages in 0.x, uses the minor version as if it was major, and then the patch as a minor+patch. This, he says, is because he likes to introduce small breaking changes early instead of grouping them for a later, bigger, major version bump.

I don't think the idea of introducing small breaking changes early instead of big changes grouped together is a bad one, but it makes absolutely no sense to tie that idea with keeping the version at 0.x. You're just literally kicking the can down the road, while also losing information and signaling to the world "this package might break everything at any time". A 0.46.3 version in this situation is just 46.3.x, but worse. If your API is stable enough that you're thinking about compatibility, stop using 0.x and release a 1.0.0, because otherwise you're signaling that your API isn't stable. It's not that a minor bump in 0.x is always a breaking change, but that any version bump, minor or patch, might be, because the API is not stable.

What SemVer is actually for

Software had version numbers way before SemVer arrived to the scene, so there must be a point in writing such a standard in the first place, right? The name "semantic" might give you a clue: it's a standardization of the meaning of versions.

The whole point of SemVer is to convey information when doing any sort of update. A version on its own is fundamentally meaningless, and 2.4.1 might as well just be an integer, a timestamp, or a random assortment of characters. It becomes meaningful when comparing it with a different version, like 2.4.3, 2.5.0, or 3.1.12. The individual numbers, or even the difference between them, mean nothing. What's important is which numbers changed, which identifies if it was a simple fix, new functionality, or an incompatible change.

This is what makes SemVer powerful. By establishing a standard and assigning meaning, version management can be partially automated. That's why npm and other tools allow you to specify simple version ranges, like ~1.2.3 meaning "version 1.2.3 and any patch bump of it" or ^1.2.3 meaning "version 1.2.3 and any patch or minor bump of it", because those are meaningful specification of what we usually want: A version plus some/all of its improvements, but no incompatibilities.

What SemVer is not for

Because SemVer was so successful, it's been used to version basically everything there is. However, one of the main points of the article linked above is that it's pretty shit as a marketing tool. I'll add to that that it's a pretty bad versioning scheme for anything with a user interface, as apposed to a programmer interface, because defining what is or isn't a compatible change when it comes to UI becomes really hard really fast.

I simply think SemVer is useless for user-facing software. Web applications, desktop applications, and even command-line applications, because they target humans, have different ideas of what a breaking change is. With the exception of some command line apps, who are usually designed to be callable from other software, there's usually not such a clear-cut definition of what breaking means. An application could change every aspect of its design, menus, etc. and still retain all of its features; would that be a breaking change? Sometimes, applications get rewritten entirely but end up looking and acting the same; would that be a breaking change? What, then, means to bump each of the numbers in a version?

What to do when SemVer isn't useful

In the web application we developed at a previous job, we stopped using SemVer for our externally facing versions for exactly those reasons. Out back-end and front-end projects moved at different paces, and so we ended up having wild discrepancies in the version strings; we also likd to retain the 1.x version all the time, even though we introduced changes to the design in the front-end, the API in the back-end, etc., but as we weren't really publishing the API for public consumption, those were technically internat changes. Instead, we started using a version scheme that was intended to keep both projects roughly in sync, while still being useful to us. The version was not published externally, only internally.

What we arrived at was a SemVer-compatible date-based version that looked like this:

The versioning system was automated, so that we didn't choose the version string ourselves. This change did nothing for our software, really, but it helped with a few major human problems:

Note again that this was the version for our user-facing application. Internal tools that we depended on still retained their SemVer scheme, because it was actually useful there. This is also just a story about how I solved this problem in the past, but it's not the solution. Few things have just one solution.

Marketing versions and SemVer

One of Anthony's points in its article is that SemVer is terrible as a marketing tool. What this means is that what a marketing department wants to call "2.0" in some software might not really introduce any breaking changes, thus really just being boring version 1.27.3, while a major bump to 3.0 might just be dropping some old runtime versions and not really change anything (see the recent Express 5.0 release that did pretty much that)

Anthony's response to this is to add an extra number at the beginning, the epoch, adding this marketing number that fundamentally means nothing at the front. To keep SemVer compatibility, he proposes to simply form a major version that's EPOCH * 1000 + MAJOR, so that epoch version 1.2.3.4 becomes SemVer 1002.3.4.

I'll be completely honest: I hate this. The only thing that this "solves" is being able to increase the epoch without increasing the major version, but that completely breaks the semantics of SemVer for no good reason.

The issue is that the software Anthony is trying to use this for is software that does benefit from using SemVer. It's libraries, depended on by thousands of projects, and the semantic information their version convey is very important. Trying to solve a marketing problem with a programming tool is insane, in my opinion.

A possible solution is to name releases independently of their semantic version, or simply accept that marketing doesn't align with API changes and work around it.

Breaking changes

One of the issues that was mentioned, however, was the reluctance of many developers, developer teams, and companies, to upgrade their dependencies. We really need to make a better job of communicating the severity of breaking changes in version bumps. Sometimes you find that a library you depend on and is pinned at 0.16 has a 2.0.2 release and you kind of freak out, only to realize that the 1.0.0 release really broke nothing, and the only breaking change that isn't dropping support for old stuff in 2.x was a small change that was literally what you were looking to upgrade for (this is based on a true story, by the way, and the package in question is superstruct). But sometimes everything breaks between majors, and upgrading really takes hours. Being able to quickly look at something that can tell you if upgrading is worth it would be great.

But, once again, that's not really an issue that the versioning scheme needs to resolve, is it? An incompatible version is incompatible, no matter if it's a small or big incompatibility. That's why many projects collect breaking changes for future, scheduled major upgrades (such as Node.js, releasing a new major version every 6 months) instead of doing a small major every few weeks. Having a strong changelog, dedicated migration guides, or similar documentation is the way to go in this case, not forcing your version to say things that it can't.

Conclusion

I firmly believe SemVer is fine for the uses it has, and will still be fine as long as we keep using it as it was intended: to communicate the meaning behind version bumps and how changes affect compatibility and risk. It works for that, and while it's true we need other tools for other ends, such as versioning software that isn't intended to be consumed by other code or to help with marketing events, that's just not what SemVer is for.