Semver failing the Developers
This article is a repost of an article by Jongleberry, as it was really interesting and it is not available online. The article has been substantially edited to match the current URLs.
As a maintainer of many popular packages such as express, the maintainers, have come to realisation that semantic versioning simply does not work in practice. It has caused a lot of bikeshedding, creating non-constructive discussions in the repository. It generally makes development actually more difficult.
Recently, some people have created some ideas about how to improve versioning. I first proposed getting rid of versions < 1
all together as 0.x.x
modules are inherently unstable due to its lack of specification. Damon Oehlman proposed slimver, a stricter variant of semantic versioning.
Contents
Current Issues & Suggestions
Jongleberry is proposing ferver, which changes the semantics of semver
to be more practical with breaking changes. You can read more about it on the GitHub page, but first, let's talk about the failures of semver.
Patches break
Part of the major issues with semver is that patch
s could break. A feature could be "fixed", but a consumer could have relied on that very buggy feature, and patching it would break their app. An example are times when a library simply does not behave according to specifications and fixes it to cohere to the specification.
An example is with Express 3.4.3. A bug with redirects was fixed, but some users were relying on that bug. Thus, even though it was a patch (3.4.2
-> 3.4.3
), it broke some people's apps. A user asked to at least bump a minor version, but if we strictly cohere to semver, we can't because it's a patch, not a new feature.
I greatly sympathise with this user, and this particular case is essentially the first time people realised, "semver doesn't work".
0.x.x is anarchy
Versions < 1.0.0
do not have semantic versions according to semver. These are considered libraries "in development" and developers could version however they see fit. Thus, developers bump the minor or patch numbers however they like. It's anarchy.
The problem isn't that versions less than < 1.0.0
are allowed. Nope, it makes sense for libraries to be able to break changes before declaring a stable 1.0.0
. The problem is that there are no semantics to 0.y.z
versions. Consumers simply don't know how to depend on these modules using version ranges without introducing a significant amount of risk into their app.
Pinned dependencies
The current solution to the above problems is to pin all dependencies. However, this is absolutely stupid and annoying to most of the developers. If you're pinning versions, then you're reducing the package manager to a glorified cURL
.
It makes maintaining very difficult and annoying. Duplicate dependencies are bad, especially in frontend development where file size matters, which is one reason frontend developers prefer Bower's flatter dependency directory vs. npm's nested. When every library pins, you're going to have a lot of duplicate dependencies, even if they're the same version! Not everyone has the time to update every patch
and make a new release.
Even if you control the dependencies, some people like pinning dependencies. To me, this is absolutely silly, but it is necessary because patch
s can break. For example, if you look at the 2.x branch of Connect, you'll see that all the commits are just dependency updates. However, they all do not break backwards compatibility because Connect coheres to semver. These updates should not be necessary and should be available just from typing npm update
.
Slower development
If you look at Express' current issues, you'll see that half of them are planned for the next major version, 5.0.0
:
The problem with this is that these minor issues may be backwards incompatible, but they are nevertheless issues that consumers would have to deal with until v5.0.0
.
For example, Update path regexp functionality
would break routes for a very few people who write really weird routes, but it introduces many new features and provides better semantics. This introduces a lot of benefits for most developers while introducing risk to a very few developers.
Ideally, these changes should happen as fast as possible, but in a way that tells consumers, "Hey, this is new and improved, but it might break your app. Proceed with caution.". There's no way to say that with semver except with major version releases.
Prereleases
Semver does not have a good scheme for pre-releases. People append all sorts of weird strings to their versions. 1.0.0-beta1
. 1.0.0-3.2.3.1
. Who knows what these mean. It only makes libraries more difficult to consume as well as confuse consumers.
Since semver is liberal with these affixes, package managers like npm
have trouble dealing with them. For example, if you use 1.5.0-beta1
of a library and the latest version is 1.4.0
, npm outdated
will mark 1.5.0-beta1
as outdated. Yeah, I don't think that's outdated.
A good versioning system would allow for pre-releases and beta builds while still cohering to x.y.z
versioning. It would also be able to allow consumers to distinguish between pre-releases and releases semantically.
The fear of x.0.0
Many developers never release v1.0.0
of their projects. With semver, this is really annoying because you have to pin to reduce the risk of breaking changes.
But others absolutely hate when libraries update the major version. They see it as a sign of "instability", but according to semver, these backwards-incompatible changes could be something as insignificant as returning null
instead of undefined
, which wouldn't break most people's apps.
The problem here is that people don't associate major versions with "breaking changes". They associate it with the character, purpose, and philosophy of a library. 0.x.x
means "We don't know what we're doing". 1.0.0
means "We think we know the direction of this library.". 2.0.0
means "We're changing directions a little bit". 3.0.0
means "We're changing directions a little bit, again".
Semver simply has the wrong semantics. Not every breaking change is a fundamental difference in a library's character. People are okay if you break things here and there, but it must be easy for them to know when you break something. This can only be done with a major version bump with semver.
Solving semver
There are two ways to solve semver: bump major versions often, or use a different versioning scheme.
Currently, Jongleberry releases most new modules he writes as 1.0.0
and liberally bump major versions. For example, koa-session is already at 2.0.0
, but koa hasn't even reached 1.0.0
. Hell, co has already reached 3.0.6
and ES6 isn't even finalized.
This is why he proposed having semver drop versions < 1
. Who cares if you're at version 36
like Chrome. He just wants to know if something would break! But this is not suitable for most people since, due to semver's semantics, they correlate a lot of major version bumps with the library being "unstable".
The other solution is just use a different versioning scheme. This is what ferver - versioning based on whether a change is breaking. Please, don't use it though. It's only a thought.