How so? It's precisely as intended. Here's thesemver summary:
Given a version number MAJOR.MINOR.PATCH, increment the:
MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards compatible manner, and
PATCH version when you make backwards compatible bug fixes.
I'm not tedajax, but my guess as to what they meant is that it's an abysmal mindset to think that making a breaking change to an API means that the previous API was wrong. And I tend to agree. Needs and assumptions change and sometimes that means your API needs to adapt in a backwards-incompatible way. That doesn't mean your previous API was wrong for what it was built for. It just means it's wrong for the new expectations.
I agree with tedajax that looking at any breaking changes as a failure is a pessimistic, fatalistic, harmful viewpoint... assuming that's what tedajax had in mind.
Not necessarily. See my comment above. Needs change. Breaking an API can be reasonable to accommodate for that. It does not mean the past decision was incorrect even with current knowledge.
That's my point: "Needs change". If you were a supernatural omniscient being with full knowledge of all possible pasts and futures you would have predicted that need.
Since none of us are omniscient it's OK for us not to build the perfect API the first time round!
Maybe I’m building my API for the users and use-cases for today and next year, intentionally not accommodating for those in 10 years. Those will be addressed in an upcoming breaking release at some point until then. This can be a sane decision even if you have full knowledge of what’s coming.
The new version fails under the previous versions API. That doesn’t mean that there is anything wrong with either old or new release or API, just that there are breaking API changes and it should not be considered a drop-in upgrade before properly reading through release notes.
There’s no implication of failure. Like you say, it happens.
>The thing about semver major version numbers are that they don't mean new stuff, they're a permanent reminder of how many times you got the API wrong.
I'd add further that more than likely once your API became something usable new use-cases and ideas came up. I wouldn't call major version numbers "failures" but "evolutions".
The context of your project changing over the years requiring breaking API changes is good and healthy.
> they're a permanent reminder of how many times you got the API wrong
I'd agree in part with this sentiment only for the last segment: PATCH. Otherwise, unless you're just producing a mountain of garbage on a routine basis, they're more an indicator of _improvements_ in an API or a versioned thing.
As it goes with Twitter, the tweet author didn't provide nearly enough nuance to justify the statement.
I mean just read the tweet again, but ignore the zingy psuedo-snowclone X.Y.Z
If someone told you "every breaking change to your API is a failure", would you give them the time of day?
But repackage that frankly useless opinion in a zinger and suddenly you have people spending hundreds of comments slowly working backwards to a more nuanced commentary that no longer has anything to do with the original point...
-
Seriously look at their reply:
> I'm watching with amusement as this tweet spreads and it turns out a decent proportion of people are finding it really offensive - feels like people are OK with learning from their mistakes but not with learning from their failures
... what?
Because people don't agree with your "hot take" that breaking changes are a failure, they... are ok with making mistakes. But also not ok with failure.
Pass me a little bit of whatever it took to make this seem profound?
-
Why do we give this kind of drivel room? Why give the time of day to an opinion so bad that you need to have a 10 level deep conversation to come up with something resembling a useful take from it?
My bad, Twitter's UI strikes again... but it honestly makes no difference to my point. It was an aside, neither tweet makes sense once you get past the way they're presented.
The original tweet is making a point that is so plain nonsensical that the moment you actually evaluate what it says rather than getting caught up in the clever presentation you question why anyone would spare a thought on it.
-
I mean seriously, "major versions track how many times you've gotten the API wrong". Would you give anyone at all the time of day for such a bad take?!
If I have to imagine someone who'd say this (and I honestly can't otherwise, it's such a cartoonishly bad/wrong thing to say) , it'd be a salesperson forced to play PM. Maybe right before promising a client a new version that supports a new use-case? Like a clever way to make the dev team feel bad for not foreseeing an undiscussed future need.
The fact to the most reasonable hypothetical situation where someone would share an opinion this bad has so many layers of weirdness... it's saying something about the opinion. Giving a new meaning to "you can't make this stuff up"
I'll be honest: "Major versions track how many times you've gotten the API wrong" really does strike me as a very true statement. I think it's an excellent take.
In my own projects I try really hard to avoid major version bumps. When I have to do them I feel bad about it - because I know that it means anyone using my libraries will have to think a lot harder about how they upgrade.
I'm not saying major version bumps should be avoided at all costs - but they should be avoided at /some/ costs. We should aspire to design our APIs well enough that we don't have to later break them and force our users to upgrade. That's clearly impossible to always hold to, which is why we have major version bumps - but major version bumps should be rare and apologetic, not careless and frequent.
> I'm not saying major version bumps should be avoided at all costs - but they should be avoided at /some/ costs. We should aspire to design our APIs well enough that we don't have to later break them and force our users to upgrade. That's clearly impossible to always hold to, which is why we have major version bumps - but major version bumps should be rare and apologetic, not careless and frequent.
This is exactly what I'm talking about.
If the tweet had said "breaking changes should not be careless and frequent", this post would not exist and those replies would not exist because... it's so painfully obvious!
You really think people are arguing against that?
That is simply not what the original comment says! That's not even what you said at the start of your own comment just now! It is not the same as saying "you got the API wrong"!
-
And I don't mean that in a semantical "haha gotcha" way, I mean they literally are two different statements and one is wrong, and one is so obvious it's literally part of the standard!
I mean have you actually read the document that defines SemVer?!
It literally says in 10 different ways that you should not be breaking your API often! That if you are either you need to stay below 1.0.0 or use a separate branch.
-
It's like by saying (or supporting) a viewpoint so extreme it's straight up wrong, you position yourself to then retroactively take an extremely moderate position in a way that makes you sound super clever...
Except you needed to support/say something super unclever as a spring board. And anyone who's gone through enough if these kinds of platitudes will see that immediately and call you out on the unclever thing you said.
That's why you see such strong opposition, not because a senior ASF contributor needs you of all people to tell them that major version bumps in semver, aka breaking changes, shouldn't be careless and frequent!
Do you not see what you're doing as patronizing at all? Luring people in with a conversation about a downright bad take (even if it's someone else's) then browbeating them with a super obvious non-statement that would never have been worth the conversation in the first place?
It's the worst form of ego-stroking if I'm being real for a second. Don't do that.
If you want to start a conversation about something and maybe help grow people's understanding of it, using pithy, semi-comedic statements is a great way to do it.
I don't think this discussion was patronizing. I think that Will helped a lot of people understand a concept that they may have missed.
Fundamentally our disagreement here seems to be about whether "major version numbers are [...] a permanent reminder of how many times you got the API wrong" means the same thing as "major version bumps should be rare and apologetic". I think the first is a slight exaggeration of the second, made for impact - and that the response to it showed that exaggerating it had the desired effect.
> If you want to start a conversation about something and maybe help grow people's understanding of it, using pithy, semi-comedic statements is a great way to do it.
I've said in maybe 20 different ways how this isn't pithy or semi-comedic
You've made my point splendidly. Do you remember how this thread started? With me saying the gray area doesn't get you engagement? How people pretend to be backing the extreme takes because of that?
Now lo and behold, "oh well obviously they never held that view in the first place, it was just pithy and semi-comedic"
> I don't think this discussion was patronizing. I think that Will helped a lot of people understand a concept that they may have missed.
You acting like the catalyst for the conversation was a "pithy semi-comedic" way of saying "major version bumps should be rare and apologetic, not careless and frequent" is extremely patronizing. It's an insult to people's intelligence!
It wasn't, it was doing exactly what I said above. Saying something whacky and wrong so that when people start to engage with it you can come back with a much more moderate vanilla point that's no one disagreed with in the first place so that audience the first statement collected gets to see you make <very correct statement>.
> Fundamentally our disagreement here seems to be about whether "major version numbers are [...] a permanent reminder of how many times you got the API wrong" means the same thing as "major version bumps should be rare and apologetic". I think the first is a slight exaggeration of the second, made for impact - and that the response to it showed that exaggerating it had the desired effect.
They are literally tangential statements. One literally does not follow the other.
That's why I said before, this isn't some academic "well achtually technically they use different words" type of deal, they are not the same!
But you know what, hey maybe I should assume positive intent. Maybe you actually don't realize how non-sensical it sounds to pretend that one follows the other. So let's leave it at that.
"How many times you got the API wrong"? I get the concept, and maybe it's correct some of the time... but it ignores the vast majority of software releases over time, which are evolved products/projects. What I initially set out to do != what's valuable/useful to the world.
To anyone who think they disagree: The tweet is not really an opinion or a hot take, just a rephrasing of the semantics (you know, the sem in semver). Read “Fails” as verb and not plural noun and it makes sense.
Given a version number MAJOR.MINOR.PATCH, increment the:
MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards compatible manner, and
PATCH version when you make backwards compatible bug fixes.
Oh, you can definitely get into DLL hell with semantically versioned APIs. DLL hell is mostly a symptom of diamond dependency problems... One library cares deeply about the version of a dependency, a second library cares deeply about a different version of the same dependency, and your code requires both of those libraries.
I absolutely love this (I was part of this Twitter conversation).
My take on SemVer is that the ideal version number would be something like 1.452 - 452 new feature release without ever breaking backwards compatibility.
Almost impossible to actually achieve this, but it's good to be ambitious!
Also: doing SemVer properly requires you to provide really good documentation. A bug fix should correct an issue where actual behavior differs from the documented behavior.
Without this, a "bug fix" is indistinguishable from a breaking change.
In that case the maintainer can release version 2.0 and have a note in the README that says "if you don't want the breaking changes in 2.0 here's how to install the last release from the 1.0 series - note that this series is no longer maintained so any bugs or security issues will not be addressed."
IMO, the difference between those 2 should be that jinja2 1.0 should be a complete rewrite, possibly in a different language, and probably even a mental shift in working with it.
That sounds like a package manager/module system issue, not a library issue. A sound system would let you use both as long as you weren't ambiguous with your imports.
The difference is tooling. Tools like Dependabot know that Jinja 2.0 is a major release of Jinja. They have no way of knowing that a package with a separate name is related to the first one.
Yeah that's a reason in most languages to go with the separate package names.
As a maintainer I'd much rather not have to put in that extra work just for the tiny minority of my users who might someday want to run two versions of my library in the same project.
Someone who cares about not breaking others' code can keep working on the existing project by fixing bugs, adding new features and even make a "2.0" version that remains backwards compatible.
1) Many Python applications are not pinning dependencies, and even if you are personally, if jinja is a transitive dependency you might pull in a breaking change. You can't if the new package has a different name.
2) You can more easily simultaneously use both to gradually transition your code to use the new API. I don't believe Python package managers have any built-in way to use an "all" conflict manager like Apache Ivy, so you need a different name to enable this.
In one sense, you could. That's just taking the semantically significant information and moving it from the major revision number to the name.
But then most systems are going to consider those libraries to be entirely separate, most search engines are going to make it hard to find out about one if you find out about the other, and so on and so forth. Major version number also implies that if you have no reason to choose one over the other, you should be choosing the most recent one.
Any reasonable dependency management tool should allow you to pin down any part of the semver version. For example on regular upgrade only pull patches or minors and require a manual change to upgrade otherwise. Appending a version number to the library name is a heck around completely inadequate dependency management tools. A language not having a tool en par with bundler, cargo or yarn is a massive negative for me when looking at new languages.
“So I spent a week doing OAuth2 stuff. It took a couple days to read through [Google’s] ten-thousand lines of code, so then I went and looked for other libraries. There's `python-oauth2`... it's the second version of python-oauth, it doesn't actually do OAuth2. But that took a while to figure out. So that one was a little bit better, 540 source lines of code, 15 classes, but I wasn’t going to put that in either because it didn’t actually do what I needed to do. So I rewrote that to just do bearer-token authentication, it’s ‘python-foauth,’ or if you prefer, ‘eff oauth.’”
With CPAN this was solved by hierarchical package/class naming.
Net.pm
Net/IMAP.pm
Net/IMAP/SSL.pm
^ Each of those is published by a different author, and inherits the previous one to add features. As a result CPAN is a tremendously useful library of OO, not because they're all different, but because they are designed to inherit and extend existing code, rather than re-write the whole thing and have 500 differently-named-almost-identical codebases.
CPAN's been around for decades, and everybody talks about DRY code, and yet no other language seems to want to actually reuse and extend code.
No existing project should break unless its developers take the action to upgrade (bumping the version of the library that the library manager is looking for).
The reason I'd personally prefer package 2.0.0 rather than package2 1.0.0 is because a lot of tools understand the former. For example tools can let the user know a breaking update is available, or allow me to specify that my project works with many major releases of package, or upgrade/downgrade more conveniently.
Or rather 'knowingly'. If you didn't intend it, but you find out (e.g. user reports it), you should still bump major. If you consider yourself to actually be 'doing semver' that is. And even yank the minor breaking release I suppose.
At my previous employer, we ended up handing over our versioning to the marketing/business teams and our development teams started using a semver-style internally recognized release number for tracking features and fixes. Too often our customers would demand a new feature/driver/interface in a .PATCH or .MINOR release in order to more quickly adopt the software without having to run through their internal qualifications and upgrade policies.
Yup. We use code names for products internally for the same reason. Never use a user-facing value as an identifier, as obnoxious as trying to adhere to that can be.
> they're a permanent reminder of how many times you got the API wrong.
I mean, that's clever for a tweet, but I hope nobody takes that seriously.
It's pretty counterproductive to view design through the lens of getting something wrong or right. There's just iteration in the direction of improvement. It didn't start out wrong -- it was your best first attempt with what you knew at the time. Then you learned more and so it improved. But it'll never be "right", just incrementally "more useful".
I'm not offended. I just think it's wrong to paint with such a wide brush. Breaking changes don't always mean that something was a mistake. They often mean a change in direction, and that can happen for a myriad of reasons.
Seems like it has to do with the word failure more than anything. It is a failure, every time you don’t ship a perfect product it’s a failure of some kind. The iteration process is simply removing failures from a product until you reach feature completeness. I don’t think the difference is that large between a failure and a mistake.
I don't believe that a prior thing which lacks a new advancement means the prior was a mistake or a failure. We're a creative species that strives for improvement. The first running shoe wasn't a mistake nor was it a failure. But the ones I use to run 40 years later sure have improvements.
This is definitely true for library code. I don't think the idea holds up for squishier things- moving from version 1.x to 2.x could make the user experience worse, depending on what you prioritize and who's making decisions.
> It is a failure, every time you don’t ship a perfect product it’s a failure of some kind.
It's impossible to ship a perfect product. So perhaps we shouldn't use "failure" to denote not doing the impossible, and reserve it for cases where "success" is even possible.
Perhaps the approach of vapid, pessimistic short-takes doesn't play well outside of Twitter, where folks aren't as impressed by blue checks and large followings, and where there isn't a benefit to jockeying for engagement. Based on your comments throughout the threads here, you appear to be overly eager to paint with an overly negative, wide brush that ignores nuance and the multitudes of situations in which your assertion doesn't come close to being true. For instance, adopting an upstream dependency change containing a breaking change, which necessitates releasing a major version in which no notion of failure, mistake, or misstep exists. I'm unsure how you gained such a large following, but your comments aren't at all inspiring or insightful - only somewhat controversial.
It's possible to ship a product that doesn't break though.
You can add new APIs, etc, instead of breaking existing ones. Anytime you do a major rev because your changing an API or removing one, you could also ship new APIs instead and keep the major version the same.
As I mentioned below, “fail” is an overloaded word and in server another meaning is intended. Longer but more correct: “BREAKING_CHANGES.FEATURES.BUG_FIXES”
You can get everything right and still intentionally introduce a breaking change in a future release.
Opengl jumped from 2 to 3 because hardware changed. It doesn't mean the old API failed or was a mistake, it did the job it was intended to do, and then they needed it to do something radically different.
Stating that they failed to predict the future hardware implies that they should or could have.
Is this true? Maybe, maybe not, I'm neither a GPU driver write or a hardware writer, but I suspect no software writer in 1997 was going to predict the hardware of 2008.
OpenGL 3 is backwards compatible with 2 itself being backwards compatible with 1, etc. (ignoring the whole profiles thing which was a mistake and aside from Apple who went their own way with Metal, everyone else eventually agreed with that) as is 4.
Also OpenGL 1.x didn't map to the underlying hardware even considering SGI, a lot of stuff was done on the CPU. The only time OpenGL matched the underlying hardware was around GeForce 2 and AFAIK that was really matching Nvidia's hardware - before and after that little window, OpenGL was its own abstraction.
But of course OpenGL is a very good example of an API done right (especially before Khronos took over and made a mess out of it): it did a reasonably good prediction for the future while providing a good API for the present (at the time) and even when the underlying hardware changed considerably, it still remains a very useful API with a reasonable mapping and its extension system allows it to take advantage of new hardware features without breaking existing code.
While i do not think Khronos did a good job on OpenGL, at least they did the right thing by making Vulkan a separate API instead of calling it "OpenGL 5", allowing the two APIs to coexist and not break any existing code. After all Vulkan is fine (even if not to my taste, it is unnecessarily oververbose and pretends to be a low level API when it isn't) if you absolutely want the best performance from a hardware abstraction API while OpenGL is still around when you do not need that and want something more user (programmer) friendly instead (though do not get me wrong, depending on what you do, you can extract a lot of performance out of OpenGL, though after some point you may need to switch to Vulkan).
True. Many people get offended when you say the glass is half empty, but nobody gets offended when you say the glass is half full. If I must point out a failure, I will make sure to wink and smile while doing it, which is tough on Twitter.
Is something that can be improved inherently a mistake?
Mistake/Failure both imply an extra level of judgement "Oh, if only you'd worked harder you would have had the right API design the first time and you'd never need a new version".
Sometimes the world changes around an API.
Sometimes there is data that can only be gathered from real world usage.
If one is desperate for a "fails/mistakes" kind of verb then "BREAKS" (as in "includes breaking changes") might actually hold sway, but then you'd leave out the "major functionality added" scenario for, well, MAJOR.
In short, I think OP's observation is that of a pessimist's.
If not "MAJOR.MINOR.BUG" (which needs no improvement IMO) I'd go with...
My first thought was along the same lines. Especially for the major version, there's no need to imply there was a failure. Version 1.0 was the right thing at the time. Version 2.0 means something about the requirements or use cases changed in the intervening time, so it's necessary to track that change. Minor and Patch have the same implications, although in my mind a "patch" can also happen when there's just the sort of mistake the can creep in because of human imperfections.
In any case, it doesn't matter except to consumers of the API who need to know the level of backward compatibility.
Seeing a project on a pretty high major is a positive signal to me. It tells me that they understand semver and know how to use breaking changes. Of course if a project is just very stable and sticks to 1.x, that‘s great too. I don‘t consider that a negative signal. Purely anecdotally, 3 is the scariest version number to me.
React sets a pretty good example here: react 17 is the most boring release in the world. The react team are not bad API designers that made 17 major mistakes. They may be the most considerate and pleasant API designers in all of frontend, actually.
React Native is on 0.64.4 (?) not because it‘s unfit for production but because they know the level of guarantees they can make about version upgrades is zero.
The Naval Research Lab releases their radar processing algorithms package for geointelligence products by build number. We always got a kick out of a new delivery updating us from nrltools-3872 to nrltools-4923 or something.
0.x releases are also major versions according to semver, just pre-stable. So many people were referring to React 0.x releases as "React x" that they just bumped it straight to version x.
Unfortunately, go has broken semver, so all of my go projects now stay at 1.x.y, with x being the "major" version (breaking changes) and y being the "minor" version (non-breaking changes) and the 1 sitting there useless. I suspect that many others do the same, so for go projects the x version field is the one to pay attention to.
Although TBH it's not all bad; all anybody has ever really cared about is "are there breaking changes or not?"
1: We made something that we think is good, but we don‘t know if others will and how they will use it. We tested it well in one scenario but not much else.
2. Now we actually understand how people use this and why they like it. We have collected all of their biggest problems and it‘s time to fix them.
3. Everyone loves our thing and it‘s very productive for them. That must mean we‘re geniuses. Let‘s fuck around and change the world.
4, 5, 6… Both we and our users understand that sometimes we need to make a breaking change. They already went through the terrible 2->3 migration so every other upgrade will be fine by comparison.
In an enterprise context there are probably enough layers that keep everyone in check so the step 3 where you run wild with your ideas never happens.
I disagree with the author. However, I don't think this was meant to be taken literally, but more as a tongue in cheek observation of the poster's perception of their own situation.
As an aside, recently, I've started doing YYYY-MM-DD for releases. I don't know why, but that's how I ended up subconsciously, after using semver for years. Currently, I version the API, and date the release. I have not yet had the audacity to pull this off in any larger projects, but small tools, internal releases, who's going to judge for trying something new where the risk is low?
I'm not saying it's right, just that it works for me.
In an imaginary world that is not very much like our current world of software development, there are no version numbers at all - only commit references.
You generally want to upgrade to the latest high-quality commit reference available, by following an upgrade path from the commit your application is currently deployed from.
Breaking changes can be identified automatically using static analysis of the commits thanks to language-level support and/or well-defined interface definitions.
For all of the important workflows you use, you have external integration tests, so you can navigate the commit-space (and potentially even merge multiple paths) fairly arbitrarily with some degree of both statistical (test coverage, performance) and qualitative (feature support, hardware compatibility) confidence.
Using semver is a promise that if you make a backwards-incompatible change you will update the major version.
That's entirely separate from any promise, or statement of intent, that you might want to make about whether you're hoping to make backwards-incompatible changes rarely or frequently.
Teaching people that they should consider incrementing the major version as indicating a failure isn't going to be helpful.
In practice, given the way semver is defined, if this idea catches on I fear the main effect will be to increase the number of projects that never choose to declare they're version 1.0.
More concretely, scrolling up I see that the tweeter says their threshold for declaring 1.0 is when they « feel that the API will only need to be extended in a backwards compatible way. When taking care not to break people's code is not a burden. »
I don't think that's the general understanding we should be aiming for.
Rather I think a better threshold (and the one implicit in semver's definition) is to declare 1.0 when the API is stable enough that you're willing to go to the effort of tracking whether the changes you're making are backwards-compatible.
Otherwise we're discarding useful information in the meantime.
So in your model 1.0 is the point at which you start writing proper release notes?
I'm not keen on that. During my 0.x period I still want interested third parties to try out and use my code, which means I need to give them good release information to help them help me test everything out.
You can start writing release notes any time you like.
The idea of semver was that you use the version number to encode compatibility information, and stop using it as a vague indication of the maturity of the project or the size of the changes in a particular release.
I find this framing to be too inward looking. SemVer isn't for me as the library developer, it's for users of my library. What it really means in that context for most users/customers outside of toy projects is:
EXPENSIVE-HIGH-RISK-UPGRADE. MEDIUM-RISK.LOW-RISK
There is a carrot-side to this but that usually only matters in specific cases for a subset of users.
EDIT: one small practical addition: If you are fixing a big that requires very large changes to how things work at a more fundamental level, I'd at least think twice about shipping that in a patch. While it technically fits the definition of SemVer, it might violate implicit expectations of users who are using your library in production. The change really needs to carry its weight.
In theory MINOR and PATCH are both supposed to be low risk. The risk only increases if the developer/process in control of the versioning doesn't recognize when a fix or feature contains a breaking change.
This is how I handle my libraries too.
My standard is that you will never have a breaking change in any of my minor or patch versions.
I may ship new API in minor versions, and if I need to do an API rewrite, there will be a few years where you can use both APIs, then in a future major version, I will deprecate the old API (emit warnings), then in yet another future major version, I will finally remove the old API.
Basically my expectation is that if you use my library, you’re set. You’ll get new features with minimal stress.
At this point, I feel like the version numbering of semi-ver has lost all meaning.
I've been broken by every level of version bump. I've seen APIs that went from 1.0 -> 2.0 and really were only backwards incompatible because of a single method change.
I've seen countless major changes to method signatures from a 1.0 -> 1.1 bump.
I've even seen breaking API changes from a 1.0.0 -> 1.0.1 bump.
It seems like everyone has a different feeling on when those numbers should be rolled. That inconsistency makes it impossible really understand what an upgrade is going to do without tests.
Not every project follows semver. Just because there are numbers with periods interspersed doesn't mean it's semantic versioning. People have been throwing numbers together to communicate something about what it is for a long time now, long before semantic versioning was formalized.
Semver only really works if everyone, or most everyone, is following it.
There are a lot of build tools that make that assumption. (cargo, npm, gradle, pip, for example).
Without that, you end up needing to have very deep understandings of your dependencies and their subdependencies. Otherwise, it's impossible to know what will happen when you update something.
I get more what you're trying to say now. To some degree, some people are definitely failing to adhere to semver when they're part of a larger ecosystem that needs it, like the ones you mentioned. Regardless of what versioning a module uses internally for development, they should be exposing a good semver compatible version to the tooling that uses them that expects it, but they often fail in this.
There is, alternatively, likely some assumption at this point in end users that some random project on github or elsewhere might be following semver even if they haven't stated as much, and without a stated intention to follow semver by the project and showing that they are willing to do so that means little and is a poor assumption. That's not necessarily applicable to your original point though.
It doesn't need everyone to follow it; it just needs library maintainers to declare when they do follow it, actually mean it when they do, and check their own dependencies for breaking changes.
ie a library that declares it uses semver should create an abstraction barrier around all of its dependencies, such that the user should only need to care about declared breakages in their direct dependencies.
This is funny because everybody makes mistakes , so of course people fail at semver.
However, if you are the maintainer of a popular cargo package, and make a mistake, people _will let you know_. Because a lot of people in the Rust ecosystem expect versions to follow semver.
Crates that fail to adhere stop being used, get bad rep, people fork them, and keep them stable, making them loose users.
Rust actually has a tool called rust-semverver that does an AST to AST diff of the current version of a crate that you want to release, with the latest release, and suggests how to increment the version.
It can list all API changes, and it will tell you which changes are minor, major, or patch.
You can use it in your deployment workflow on CI to, e.g., deploy a new version of a crate only if, the changes follow the version change that was specified. Such that, e.g., if you bump your 2.0.0 crate to 2.1.0 but your crate has an API breaking change, the tool and CI fails telling you that you need to change the version to 3.0.0 because of these changes.
I think some of this is because even package maintainers have a hard time deciphering what is potentially "breaking". There was a great talk at GopherCon about an ambitious project whose goal was to be able to detect if you made backwards incompatible changes, which on the facade seems easy (to some folks, apparently) but is in fact not.
I see the middle number as "RISK-FREE-UPGRADE" - if I bump that number in one of my packages it means there's a new feature, which should have no impact at all on existing code.
The PLaneT network package manager did it even simpler: APIBREAKING.NONAPIBREAKING. Which might be interpreted as NEEDSENGINEERINGLOOKBEFOREUPGRADE.SAFEAUTOUPGRADE.
This was used for PLT Scheme, and was a rather nice 90%+ very lightweight solution.
If you were doing a package where backward-compatibility was a very high priority, you might be at PLaneT version 1.x forever.
If you were doing rapidly evolving research, and wanting to share code, while not surprise-breaking people who might use your stuff, you could end up with a large major version. For example, a package by Dave Herman (of Mozilla and Rust fame) got up to 9.x in only 2 years. http://planet.racket-lang.org/display.ss?package=javascript....
I don't recall whether anyone ever encountered unexpected breaking changes in practice that the version number didn't indicate. I'd guess probably. It wasn't a 100% solution alone.
PLaneT also supported specifying in a code file import which versions of the package you were known to work with (like meaning "major version 4, with minor version 2 or later; or major version 5"), with automatic installation of those.
You could also mix multiple versions of a package in a system, in the normal case (though maybe not if you had something like singleton state or resource that multiple versions of the package wanted to control). This seemed very valuable for pulling in lots of small open source dependencies, directly and indirectly, from different developers.
One downside of this simple approach is that it's bad as a "marketing version number". Like if you wanted to convey the value of large new features (or maybe talk about them in an easy way, like "version 4 foo features"), but you've been sticking to version 1.x backward compatibility the whole time. So there was a separate marketing version number, which seemed confusing enough that there must be a better way. But it was at least 90% there.
I think the difference between minors that include non-stop breaking features and patches actually is important. If you run a mission-critical system you want to minimize risk as much as possible but also get your needed fixes, especially if they are for CVEs. If it was a fair assumption that we only ever break things on purpose the PLaneT versions would be totally sufficient though.
Good point. If we had something PLaneT-like, I wonder this-is-important-security-fix should be included in the version number (maybe a big `S` suffix), or separate. Or whether it much matters.
Security fixes are exceptional situations, requiring scrutiny from developers and also sometimes by end users. To understand the implications, what's affected, the urgency, any other impact, etc.
And we also have situations like a popular distro often shipping regressions in their security updates (including a "bricking" one for some hardware platforms last year, which threatened some appliances I had installed overseas).
Given this need for scrutiny by at least developers, I don't know whether having is-a-security-update information in the version number itself helps, or is just as well (or better) represented separately.
One distinction that might be intuitive and relevant... We already know that is-backward-compatible-with-previous-version(s) is something that could be handled more flexibly separate from the version number, such as for updating changing knowledge about that after a version release -- but there's lightweight benefits to being able to talk about that in a version number, such as in the import line. But the import line doesn't need to talk about security update handling, and probably shouldn't.
This is literally the only tweet I've ever seen posted to HN that was worth upvoting. It's not an entire book spread over 200 posts, it's just one nugget of useful info
In the Node.js ecosystem, major semver bumps often just mean that support for an EOL version of Node was dropped. Which is a good thing IMO - allows the devs to use modern JS conventions without as much transpiling overhead.
Tweet author here. Please don’t @ me. I was only half serious.
The context was re Python libraries. I increase the major version when I revise the API, even if it’s single method. To my mind, that is a failure on my part to come up with the perfect API on the first pass. Of course it’s going to happen and I don’t lose sleep over it.
I’m not dissing any project that doesn’t start with a 1.!
If an API is like a painting, then a changing landscape could be one reason you can paint it accurately each time but still need to make later revisions. "All models are wrong but some are useful" applies here as well. Most of SW iteration is about adjusting the painting/model to match present realities.
Either way the tweet cracks me up because it's just a matter of perspective that is giving some folks a good rankle.
People are just reacting strongly to a word that I guess in their culture has too many negative connotations. Your idea was pretty much on point and made for a fun tweet, and I think it's good enough without needing to split hairs between the slightly different possible interpretations of the word, as some are doing.
I think would be better. And when you look at it that way, BREAKING could be in order to provide new features that necissated breaking changes, or it could be because the old way sucked and nobody liked it, or any other reason, but the version bump communicates that the changes are breaking backwards compatibility in some known way.
As an aside, this is why some people say SemVer is bad, because ideally a dependency should never be breaking you.
An alternative to SemVer is to do:
FEATURES.BUGS and simply never push breaking changes.
So what if you want to make some changes that mandates breaking compatibility?
Well you simply release a new library or framework under a new name.
That way the name of a dependency is a 1:1 correspondence with its exact interface. If the interface changes in breaking ways it's a new dependency.
This also allows you to then depend on both, possibly even letting you transition from one to the other in phases.
> So what if you want to make some changes that mandates breaking compatibility?
> Well you simply release a new library or framework under a new name.
I don't think that's necessary. If you use the model you propose (BREAKING.FEATURES.BUGS) and steer very clear of breaking changes until absolutely necessary, then "Framework 2" becomes in a very real sense a new product while still conveying continuity of development team, ideas, etc.
I think of Vue 3, Python 3, Bootstrap 5, or Fontawesome 6. No one would make the mistake of thinking they could just update the version number and be on their merry way. They're treated as "new dependencies", in effect, but re-using the name makes it still obvious that it's the same team and philosophy behind the new language/framework.
One aspect of each of the projects I list above is the continued commitment to maintenance on the old versions, with security and bugfixes and with the old documentation still being available for years after the new version is out. This allows organizations to treat the new major version as a new dependency, with all the caution that entails, without fear of having the rug yanked out from under them.
After thinking about this topic too much, I've come to the same answer you have except you've said it better. (Although I disagree on people not thinking they can upgrade majors no problem. That for sure happens).
If you think every breaking change should have a new name, as the person above you does and I've heard come out of the Clojure community, then just append the major version number to the product name in your head. Otherwise, what do you expect them to name it? A name that kinda reminds people of the first one? If it's mostly the same group of people solving mostly the same problem in mostly the same way at a later point in time, NAME N -> NAME N+1 captures that better than anything else.
For all the shit we give the Python 2 to 3 upgrade (and they give themselves) I have never heard an alternative that I really find satisfying. It seems the problem was: We want to stop working for free on this old stuff we don't like any more. We want to keep the name, the websites and t-shirts since we own them and we're doing almost the same thing. Every answer I hear wants the people working for free to give up one of those reasonable things.
I think you're conflating name and executable. There's not a Python2 Software Foundation and a Python3 Software Foundation. When I run python I get back 3.9.6, not 9.6.
My point is that people advocating for "never break stuff, make new things with a new name" are wrong when they act like that's superior to semantic versioning because those two options are isomorphic. You can translate Birdy 1.3.45 to Birdy1 3.45 and Birdy 2.1.0 to Birdy2 1.0. You can even translate it in your code with aliasing. And if this somehow a way better system, there should be some big benefit, right? But there isn't.
People want their software to not break, and they want to know when it does. Some people accept that software is going to break all the time and they're like "Please just tell me about it by bumping major version. High major versions = honest." And then other people don't accept it will break and are like "Please don't break my stuff. High major versions = unreliable.". Where I think the "don't break my stuff" crowd goes astray is when they act like it has anything to do with naming. They'd be just as annoyed if I released 17 different software packages named after bird species, and all but the most recent are not supported. The problem is the 17, now how it's expressed.
I'm talking from the point of view of dependency management and code linking, for which it makes a big difference.
This isn't about the natural language interpretation. You cannot link against two things that have the same name, that's the point of changing the name.
And if you keep the version string as a mechanism to show non-breaking changes, you can safely bump it up.
Python packagers mostly followed the advice I mentioned, and they packaged Python 2 and 3 using different package and executable names.
If they had used the same name, a bunch of things would have been broken.
Imagine a script that is broken by Python 3 which does:
#!/usr/bin/env python
So when the 2 to 3 migration happened package managers did this:
python2
python3
python -> python2
And if you are writing a script that needs Python 3 features you'd do:
#!/usr/bin/env python3
Had they not decided to change the name, a lot of things would have been broken.
Now the more common case are going to be direct code dependencies for an app.
You have a lib called foo, and I depend on it for my application:
But this forces me to upgrade all at ounce. For example, if bar dependency is also broken by foo 2.0.0, I first have to wait for them to upgrade, etc.
If you had released foo2 I could choose to upgrade whenever I want, before bar does, after bar does, or even start by upgrading only half my code to foo2 while the other half is still on foo.
"dependencies": {
"foo2": "1.0",
"bar": "1.3.7"
}
The downsides of this I've heard is that it might mean less people end up migrating to the new thing, and that it can end up growing the number of dependencies pulled in, since you would pull foo2 and bar and bar would pull foo, for a total of 3. Where-as otherwise you'd wait for bar to use foo2 and then you'd pull foo2 and bar, for a total of 2.
And some people say it's good to have this downward pressure to force others to also upgrade to keep everyone aligned on latest versions, might be good for security, or just to keep the pace of progress high.
There's also another strategy which is more granular, which is to version the actual code modules or even functions:
So maybe your breaking change doesn't break everything but it does break your date module:
module foo.v1.date
becomes:
module foo.v2.date
And the dependency is just a feature bump:
"foo": "1.1.0"
Now your users can choose the V2 module if they prefer it's APIs and features or stay on V1.
That one might be harder for the author to maintain over time though.
And even more granular is at the function level:
function foo-cool() {...}
function foo-cool-v2 {...}
169 comments
[ 3.4 ms ] story [ 224 ms ] threadGiven a version number MAJOR.MINOR.PATCH, increment the:
https://semver.org/I agree with tedajax that looking at any breaking changes as a failure is a pessimistic, fatalistic, harmful viewpoint... assuming that's what tedajax had in mind.
Since none of us are omniscient it's OK for us not to build the perfect API the first time round!
The new version fails under the previous versions API. That doesn’t mean that there is anything wrong with either old or new release or API, just that there are breaking API changes and it should not be considered a drop-in upgrade before properly reading through release notes.
There’s no implication of failure. Like you say, it happens.
>The thing about semver major version numbers are that they don't mean new stuff, they're a permanent reminder of how many times you got the API wrong.
I'd add further that more than likely once your API became something usable new use-cases and ideas came up. I wouldn't call major version numbers "failures" but "evolutions".
The context of your project changing over the years requiring breaking API changes is good and healthy.
I'd agree in part with this sentiment only for the last segment: PATCH. Otherwise, unless you're just producing a mountain of garbage on a routine basis, they're more an indicator of _improvements_ in an API or a versioned thing.
As it goes with Twitter, the tweet author didn't provide nearly enough nuance to justify the statement.
People seem to really hate the gray area despite the fact the most of life lies in it.
I mean just read the tweet again, but ignore the zingy psuedo-snowclone X.Y.Z
If someone told you "every breaking change to your API is a failure", would you give them the time of day?
But repackage that frankly useless opinion in a zinger and suddenly you have people spending hundreds of comments slowly working backwards to a more nuanced commentary that no longer has anything to do with the original point...
-
Seriously look at their reply:
> I'm watching with amusement as this tweet spreads and it turns out a decent proportion of people are finding it really offensive - feels like people are OK with learning from their mistakes but not with learning from their failures
... what?
Because people don't agree with your "hot take" that breaking changes are a failure, they... are ok with making mistakes. But also not ok with failure.
Pass me a little bit of whatever it took to make this seem profound?
-
Why do we give this kind of drivel room? Why give the time of day to an opinion so bad that you need to have a 10 level deep conversation to come up with something resembling a useful take from it?
I guess because we're bored?
Important you don't continue to believe that Will posted the reply that you're criticizing here.
The original tweet is making a point that is so plain nonsensical that the moment you actually evaluate what it says rather than getting caught up in the clever presentation you question why anyone would spare a thought on it.
-
I mean seriously, "major versions track how many times you've gotten the API wrong". Would you give anyone at all the time of day for such a bad take?!
If I have to imagine someone who'd say this (and I honestly can't otherwise, it's such a cartoonishly bad/wrong thing to say) , it'd be a salesperson forced to play PM. Maybe right before promising a client a new version that supports a new use-case? Like a clever way to make the dev team feel bad for not foreseeing an undiscussed future need.
The fact to the most reasonable hypothetical situation where someone would share an opinion this bad has so many layers of weirdness... it's saying something about the opinion. Giving a new meaning to "you can't make this stuff up"
In my own projects I try really hard to avoid major version bumps. When I have to do them I feel bad about it - because I know that it means anyone using my libraries will have to think a lot harder about how they upgrade.
You're not alone in disagreeing with me on this. I had a big back-and-forth about with a senior Apache Software Foundation contributor on Twitter this morning: https://twitter.com/myrleKrantz/status/1424006566609629184
I'm not saying major version bumps should be avoided at all costs - but they should be avoided at /some/ costs. We should aspire to design our APIs well enough that we don't have to later break them and force our users to upgrade. That's clearly impossible to always hold to, which is why we have major version bumps - but major version bumps should be rare and apologetic, not careless and frequent.
This is exactly what I'm talking about.
If the tweet had said "breaking changes should not be careless and frequent", this post would not exist and those replies would not exist because... it's so painfully obvious!
You really think people are arguing against that?
That is simply not what the original comment says! That's not even what you said at the start of your own comment just now! It is not the same as saying "you got the API wrong"!
-
And I don't mean that in a semantical "haha gotcha" way, I mean they literally are two different statements and one is wrong, and one is so obvious it's literally part of the standard!
I mean have you actually read the document that defines SemVer?!
https://semver.org/
It literally says in 10 different ways that you should not be breaking your API often! That if you are either you need to stay below 1.0.0 or use a separate branch.
-
It's like by saying (or supporting) a viewpoint so extreme it's straight up wrong, you position yourself to then retroactively take an extremely moderate position in a way that makes you sound super clever...
Except you needed to support/say something super unclever as a spring board. And anyone who's gone through enough if these kinds of platitudes will see that immediately and call you out on the unclever thing you said.
That's why you see such strong opposition, not because a senior ASF contributor needs you of all people to tell them that major version bumps in semver, aka breaking changes, shouldn't be careless and frequent!
Do you not see what you're doing as patronizing at all? Luring people in with a conversation about a downright bad take (even if it's someone else's) then browbeating them with a super obvious non-statement that would never have been worth the conversation in the first place?
It's the worst form of ego-stroking if I'm being real for a second. Don't do that.
Will's original "FAILS.FEATURES.BUGS" tweet, https://twitter.com/willmcgugan/status/1423678688802058244 has been retweeted 256 times and got 855 likes.
I included "major version bumps should be rare and apologetic, not careless and frequent" in this tweet: https://twitter.com/simonw/status/1424050624388206600 - and it got 1 retweet and 12 likes.
I don't think this discussion was patronizing. I think that Will helped a lot of people understand a concept that they may have missed.
Fundamentally our disagreement here seems to be about whether "major version numbers are [...] a permanent reminder of how many times you got the API wrong" means the same thing as "major version bumps should be rare and apologetic". I think the first is a slight exaggeration of the second, made for impact - and that the response to it showed that exaggerating it had the desired effect.
I've said in maybe 20 different ways how this isn't pithy or semi-comedic
> I included "major version bumps should be rare and apologetic, not careless and frequent" in this tweet: https://twitter.com/simonw/status/1424050624388206600 - and it got 1 retweet and 12 likes.
You've made my point splendidly. Do you remember how this thread started? With me saying the gray area doesn't get you engagement? How people pretend to be backing the extreme takes because of that?
Now lo and behold, "oh well obviously they never held that view in the first place, it was just pithy and semi-comedic"
> I don't think this discussion was patronizing. I think that Will helped a lot of people understand a concept that they may have missed.
You acting like the catalyst for the conversation was a "pithy semi-comedic" way of saying "major version bumps should be rare and apologetic, not careless and frequent" is extremely patronizing. It's an insult to people's intelligence!
It wasn't, it was doing exactly what I said above. Saying something whacky and wrong so that when people start to engage with it you can come back with a much more moderate vanilla point that's no one disagreed with in the first place so that audience the first statement collected gets to see you make <very correct statement>.
> Fundamentally our disagreement here seems to be about whether "major version numbers are [...] a permanent reminder of how many times you got the API wrong" means the same thing as "major version bumps should be rare and apologetic". I think the first is a slight exaggeration of the second, made for impact - and that the response to it showed that exaggerating it had the desired effect.
They are literally tangential statements. One literally does not follow the other.
That's why I said before, this isn't some academic "well achtually technically they use different words" type of deal, they are not the same!
But you know what, hey maybe I should assume positive intent. Maybe you actually don't realize how non-sensical it sounds to pretend that one follows the other. So let's leave it at that.
There's no shame in admitting that with a major version bump. None of us are infallible.
[edit] Yes, I missed the semver part of the tweet. This is why I’m not on twitter, and should stay out of these threads in the future.
https://www.chromium.org/developers/version-numbers
The idea is that users can get expectations right when assessing an upgrade and plan accordingly. If you don't follow this, you're not doing semver.
My take on SemVer is that the ideal version number would be something like 1.452 - 452 new feature release without ever breaking backwards compatibility.
Almost impossible to actually achieve this, but it's good to be ambitious!
Also: doing SemVer properly requires you to provide really good documentation. A bug fix should correct an issue where actual behavior differs from the documented behavior.
Without this, a "bug fix" is indistinguishable from a breaking change.
What if I want to use code from both packages?
As a maintainer I'd much rather not have to put in that extra work just for the tiny minority of my users who might someday want to run two versions of my library in the same project.
1) Many Python applications are not pinning dependencies, and even if you are personally, if jinja is a transitive dependency you might pull in a breaking change. You can't if the new package has a different name.
2) You can more easily simultaneously use both to gradually transition your code to use the new API. I don't believe Python package managers have any built-in way to use an "all" conflict manager like Apache Ivy, so you need a different name to enable this.
But then most systems are going to consider those libraries to be entirely separate, most search engines are going to make it hard to find out about one if you find out about the other, and so on and so forth. Major version number also implies that if you have no reason to choose one over the other, you should be choosing the most recent one.
-- Jack Diederich, “Stop Writing Classes”, https://youtube.com/watch?v=o9pEzgHorH0
CPAN's been around for decades, and everybody talks about DRY code, and yet no other language seems to want to actually reuse and extend code.
The reason I'd personally prefer package 2.0.0 rather than package2 1.0.0 is because a lot of tools understand the former. For example tools can let the user know a breaking update is available, or allow me to specify that my project works with many major releases of package, or upgrade/downgrade more conveniently.
How is that helpful?
Without ever intentionally breaking backwards compatibility.
I mean, that's clever for a tweet, but I hope nobody takes that seriously.
It's pretty counterproductive to view design through the lens of getting something wrong or right. There's just iteration in the direction of improvement. It didn't start out wrong -- it was your best first attempt with what you knew at the time. Then you learned more and so it improved. But it'll never be "right", just incrementally "more useful".
API's aren't mathematical proofs.
Just in case anyone needs to hear it ;)
In American culture (in my experience) everyone takes pride in learning from their mistakes - but no-one likes to think of them as failures.
Everything becomes a mistake eventually.
We like more nuance on HN than Twitter seems to.
It's impossible to ship a perfect product. So perhaps we shouldn't use "failure" to denote not doing the impossible, and reserve it for cases where "success" is even possible.
You can add new APIs, etc, instead of breaking existing ones. Anytime you do a major rev because your changing an API or removing one, you could also ship new APIs instead and keep the major version the same.
Which is why I think it is bad semantics to say changing an API is an indication of failure.
You can get everything right and still intentionally introduce a breaking change in a future release.
A failure isn't always a bad thing. Sometimes it's the best thing possible.
Is this true? Maybe, maybe not, I'm neither a GPU driver write or a hardware writer, but I suspect no software writer in 1997 was going to predict the hardware of 2008.
Also OpenGL 1.x didn't map to the underlying hardware even considering SGI, a lot of stuff was done on the CPU. The only time OpenGL matched the underlying hardware was around GeForce 2 and AFAIK that was really matching Nvidia's hardware - before and after that little window, OpenGL was its own abstraction.
But of course OpenGL is a very good example of an API done right (especially before Khronos took over and made a mess out of it): it did a reasonably good prediction for the future while providing a good API for the present (at the time) and even when the underlying hardware changed considerably, it still remains a very useful API with a reasonable mapping and its extension system allows it to take advantage of new hardware features without breaking existing code.
While i do not think Khronos did a good job on OpenGL, at least they did the right thing by making Vulkan a separate API instead of calling it "OpenGL 5", allowing the two APIs to coexist and not break any existing code. After all Vulkan is fine (even if not to my taste, it is unnecessarily oververbose and pretends to be a low level API when it isn't) if you absolutely want the best performance from a hardware abstraction API while OpenGL is still around when you do not need that and want something more user (programmer) friendly instead (though do not get me wrong, depending on what you do, you can extract a lot of performance out of OpenGL, though after some point you may need to switch to Vulkan).
Mistake/Failure both imply an extra level of judgement "Oh, if only you'd worked harder you would have had the right API design the first time and you'd never need a new version".
Sometimes the world changes around an API.
Sometimes there is data that can only be gathered from real world usage.
Sometimes there's new use cases to handle.
In short, I think OP's observation is that of a pessimist's.
If not "MAJOR.MINOR.BUG" (which needs no improvement IMO) I'd go with...
TECTONIC.FEATURE.FIX.
In any case, it doesn't matter except to consumers of the API who need to know the level of backward compatibility.
React sets a pretty good example here: react 17 is the most boring release in the world. The react team are not bad API designers that made 17 major mistakes. They may be the most considerate and pleasant API designers in all of frontend, actually.
React Native is on 0.64.4 (?) not because it‘s unfit for production but because they know the level of guarantees they can make about version upgrades is zero.
Although TBH it's not all bad; all anybody has ever really cared about is "are there breaking changes or not?"
1: We made something that we think is good, but we don‘t know if others will and how they will use it. We tested it well in one scenario but not much else.
2. Now we actually understand how people use this and why they like it. We have collected all of their biggest problems and it‘s time to fix them.
3. Everyone loves our thing and it‘s very productive for them. That must mean we‘re geniuses. Let‘s fuck around and change the world.
4, 5, 6… Both we and our users understand that sometimes we need to make a breaking change. They already went through the terrible 2->3 migration so every other upgrade will be fine by comparison.
In an enterprise context there are probably enough layers that keep everyone in check so the step 3 where you run wild with your ideas never happens.
It think I understand all the projects afraid to go to 1.0:
“0 fails”!
As an aside, recently, I've started doing YYYY-MM-DD for releases. I don't know why, but that's how I ended up subconsciously, after using semver for years. Currently, I version the API, and date the release. I have not yet had the audacity to pull this off in any larger projects, but small tools, internal releases, who's going to judge for trying something new where the risk is low?
I'm not saying it's right, just that it works for me.
You generally want to upgrade to the latest high-quality commit reference available, by following an upgrade path from the commit your application is currently deployed from.
Breaking changes can be identified automatically using static analysis of the commits thanks to language-level support and/or well-defined interface definitions.
For all of the important workflows you use, you have external integration tests, so you can navigate the commit-space (and potentially even merge multiple paths) fairly arbitrarily with some degree of both statistical (test coverage, performance) and qualitative (feature support, hardware compatibility) confidence.
That's entirely separate from any promise, or statement of intent, that you might want to make about whether you're hoping to make backwards-incompatible changes rarely or frequently.
Teaching people that they should consider incrementing the major version as indicating a failure isn't going to be helpful.
In practice, given the way semver is defined, if this idea catches on I fear the main effect will be to increase the number of projects that never choose to declare they're version 1.0.
I don't think that's the general understanding we should be aiming for.
Rather I think a better threshold (and the one implicit in semver's definition) is to declare 1.0 when the API is stable enough that you're willing to go to the effort of tracking whether the changes you're making are backwards-compatible.
Otherwise we're discarding useful information in the meantime.
I'm not keen on that. During my 0.x period I still want interested third parties to try out and use my code, which means I need to give them good release information to help them help me test everything out.
The idea of semver was that you use the version number to encode compatibility information, and stop using it as a vague indication of the maturity of the project or the size of the changes in a particular release.
EXPENSIVE-HIGH-RISK-UPGRADE. MEDIUM-RISK.LOW-RISK
There is a carrot-side to this but that usually only matters in specific cases for a subset of users.
EDIT: one small practical addition: If you are fixing a big that requires very large changes to how things work at a more fundamental level, I'd at least think twice about shipping that in a patch. While it technically fits the definition of SemVer, it might violate implicit expectations of users who are using your library in production. The change really needs to carry its weight.
In theory MINOR and PATCH are both supposed to be low risk. The risk only increases if the developer/process in control of the versioning doesn't recognize when a fix or feature contains a breaking change.
Realver.org? Enterprisever? Corpver?
I may ship new API in minor versions, and if I need to do an API rewrite, there will be a few years where you can use both APIs, then in a future major version, I will deprecate the old API (emit warnings), then in yet another future major version, I will finally remove the old API.
Basically my expectation is that if you use my library, you’re set. You’ll get new features with minimal stress.
I've been broken by every level of version bump. I've seen APIs that went from 1.0 -> 2.0 and really were only backwards incompatible because of a single method change.
I've seen countless major changes to method signatures from a 1.0 -> 1.1 bump.
I've even seen breaking API changes from a 1.0.0 -> 1.0.1 bump.
It seems like everyone has a different feeling on when those numbers should be rolled. That inconsistency makes it impossible really understand what an upgrade is going to do without tests.
Semver only really works if everyone, or most everyone, is following it.
There are a lot of build tools that make that assumption. (cargo, npm, gradle, pip, for example).
Without that, you end up needing to have very deep understandings of your dependencies and their subdependencies. Otherwise, it's impossible to know what will happen when you update something.
There is, alternatively, likely some assumption at this point in end users that some random project on github or elsewhere might be following semver even if they haven't stated as much, and without a stated intention to follow semver by the project and showing that they are willing to do so that means little and is a poor assumption. That's not necessarily applicable to your original point though.
ie a library that declares it uses semver should create an abstraction barrier around all of its dependencies, such that the user should only need to care about declared breakages in their direct dependencies.
However, if you are the maintainer of a popular cargo package, and make a mistake, people _will let you know_. Because a lot of people in the Rust ecosystem expect versions to follow semver.
Crates that fail to adhere stop being used, get bad rep, people fork them, and keep them stable, making them loose users.
Rust actually has a tool called rust-semverver that does an AST to AST diff of the current version of a crate that you want to release, with the latest release, and suggests how to increment the version.
It can list all API changes, and it will tell you which changes are minor, major, or patch.
You can use it in your deployment workflow on CI to, e.g., deploy a new version of a crate only if, the changes follow the version change that was specified. Such that, e.g., if you bump your 2.0.0 crate to 2.1.0 but your crate has an API breaking change, the tool and CI fails telling you that you need to change the version to 3.0.0 because of these changes.
That said, my sentiment is the same as yours. I was surprised that even needed addressing.
No software story is more classic than the bugfix breakage. There is a reason versions are pinned. There is a reason lockfiles exist.
---
Whatever your intentions, versioning is HIGH.MEDIUM.LOW breakage.
I don't degenerate versioning at all; I find it to be very helpful. But don't pretend it guarantees something that it can't.
Versioning is simply a best-effort communication of risk.
A library user can't afford to assume any change is risk-free. Even when the author claims it is.
This was used for PLT Scheme, and was a rather nice 90%+ very lightweight solution.
If you were doing a package where backward-compatibility was a very high priority, you might be at PLaneT version 1.x forever.
If you were doing rapidly evolving research, and wanting to share code, while not surprise-breaking people who might use your stuff, you could end up with a large major version. For example, a package by Dave Herman (of Mozilla and Rust fame) got up to 9.x in only 2 years. http://planet.racket-lang.org/display.ss?package=javascript....
I don't recall whether anyone ever encountered unexpected breaking changes in practice that the version number didn't indicate. I'd guess probably. It wasn't a 100% solution alone.
PLaneT also supported specifying in a code file import which versions of the package you were known to work with (like meaning "major version 4, with minor version 2 or later; or major version 5"), with automatic installation of those.
You could also mix multiple versions of a package in a system, in the normal case (though maybe not if you had something like singleton state or resource that multiple versions of the package wanted to control). This seemed very valuable for pulling in lots of small open source dependencies, directly and indirectly, from different developers.
One downside of this simple approach is that it's bad as a "marketing version number". Like if you wanted to convey the value of large new features (or maybe talk about them in an easy way, like "version 4 foo features"), but you've been sticking to version 1.x backward compatibility the whole time. So there was a separate marketing version number, which seemed confusing enough that there must be a better way. But it was at least 90% there.
Security fixes are exceptional situations, requiring scrutiny from developers and also sometimes by end users. To understand the implications, what's affected, the urgency, any other impact, etc.
And we also have situations like a popular distro often shipping regressions in their security updates (including a "bricking" one for some hardware platforms last year, which threatened some appliances I had installed overseas).
Given this need for scrutiny by at least developers, I don't know whether having is-a-security-update information in the version number itself helps, or is just as well (or better) represented separately.
One distinction that might be intuitive and relevant... We already know that is-backward-compatible-with-previous-version(s) is something that could be handled more flexibly separate from the version number, such as for updating changing knowledge about that after a version release -- but there's lightweight benefits to being able to talk about that in a version number, such as in the import line. But the import line doesn't need to talk about security update handling, and probably shouldn't.
1. "Features" released since last Intentional-Breakage.
2. Acknowledged mistakes since last "feature" release.
3. Scuff-ups since last mistake or "feature".
4. Comical 'release name' to distract management from size of last two numbers.
0.1.2-3-haffagudtay
The context was re Python libraries. I increase the major version when I revise the API, even if it’s single method. To my mind, that is a failure on my part to come up with the perfect API on the first pass. Of course it’s going to happen and I don’t lose sleep over it.
I’m not dissing any project that doesn’t start with a 1.!
Either way the tweet cracks me up because it's just a matter of perspective that is giving some folks a good rankle.
I think would be better. And when you look at it that way, BREAKING could be in order to provide new features that necissated breaking changes, or it could be because the old way sucked and nobody liked it, or any other reason, but the version bump communicates that the changes are breaking backwards compatibility in some known way.
As an aside, this is why some people say SemVer is bad, because ideally a dependency should never be breaking you.
An alternative to SemVer is to do:
FEATURES.BUGS and simply never push breaking changes.
So what if you want to make some changes that mandates breaking compatibility?
Well you simply release a new library or framework under a new name.
That way the name of a dependency is a 1:1 correspondence with its exact interface. If the interface changes in breaking ways it's a new dependency.
This also allows you to then depend on both, possibly even letting you transition from one to the other in phases.
> Well you simply release a new library or framework under a new name.
I don't think that's necessary. If you use the model you propose (BREAKING.FEATURES.BUGS) and steer very clear of breaking changes until absolutely necessary, then "Framework 2" becomes in a very real sense a new product while still conveying continuity of development team, ideas, etc.
I think of Vue 3, Python 3, Bootstrap 5, or Fontawesome 6. No one would make the mistake of thinking they could just update the version number and be on their merry way. They're treated as "new dependencies", in effect, but re-using the name makes it still obvious that it's the same team and philosophy behind the new language/framework.
One aspect of each of the projects I list above is the continued commitment to maintenance on the old versions, with security and bugfixes and with the old documentation still being available for years after the new version is out. This allows organizations to treat the new major version as a new dependency, with all the caution that entails, without fear of having the rug yanked out from under them.
If you think every breaking change should have a new name, as the person above you does and I've heard come out of the Clojure community, then just append the major version number to the product name in your head. Otherwise, what do you expect them to name it? A name that kinda reminds people of the first one? If it's mostly the same group of people solving mostly the same problem in mostly the same way at a later point in time, NAME N -> NAME N+1 captures that better than anything else.
For all the shit we give the Python 2 to 3 upgrade (and they give themselves) I have never heard an alternative that I really find satisfying. It seems the problem was: We want to stop working for free on this old stuff we don't like any more. We want to keep the name, the websites and t-shirts since we own them and we're doing almost the same thing. Every answer I hear wants the people working for free to give up one of those reasonable things.
I think you're conflating name and version.
Your new name can be: name2 if you want, but your version string can reset to 1.0
So if I had a lib called Birdy, and I was doing FEATURES.BUGS maybe I'm now at Birdy v3.45
Now say I want to make a breaking change I can release Birdy2 v1.0
My point is that people advocating for "never break stuff, make new things with a new name" are wrong when they act like that's superior to semantic versioning because those two options are isomorphic. You can translate Birdy 1.3.45 to Birdy1 3.45 and Birdy 2.1.0 to Birdy2 1.0. You can even translate it in your code with aliasing. And if this somehow a way better system, there should be some big benefit, right? But there isn't.
People want their software to not break, and they want to know when it does. Some people accept that software is going to break all the time and they're like "Please just tell me about it by bumping major version. High major versions = honest." And then other people don't accept it will break and are like "Please don't break my stuff. High major versions = unreliable.". Where I think the "don't break my stuff" crowd goes astray is when they act like it has anything to do with naming. They'd be just as annoyed if I released 17 different software packages named after bird species, and all but the most recent are not supported. The problem is the 17, now how it's expressed.
This isn't about the natural language interpretation. You cannot link against two things that have the same name, that's the point of changing the name.
And if you keep the version string as a mechanism to show non-breaking changes, you can safely bump it up.
Python packagers mostly followed the advice I mentioned, and they packaged Python 2 and 3 using different package and executable names.
If they had used the same name, a bunch of things would have been broken.
Imagine a script that is broken by Python 3 which does:
So when the 2 to 3 migration happened package managers did this: And if you are writing a script that needs Python 3 features you'd do: Had they not decided to change the name, a lot of things would have been broken.Now the more common case are going to be direct code dependencies for an app.
You have a lib called foo, and I depend on it for my application:
Now you make breaking changes, you could do: But this forces me to upgrade all at ounce. For example, if bar dependency is also broken by foo 2.0.0, I first have to wait for them to upgrade, etc.If you had released foo2 I could choose to upgrade whenever I want, before bar does, after bar does, or even start by upgrading only half my code to foo2 while the other half is still on foo.
The downsides of this I've heard is that it might mean less people end up migrating to the new thing, and that it can end up growing the number of dependencies pulled in, since you would pull foo2 and bar and bar would pull foo, for a total of 3. Where-as otherwise you'd wait for bar to use foo2 and then you'd pull foo2 and bar, for a total of 2.And some people say it's good to have this downward pressure to force others to also upgrade to keep everyone aligned on latest versions, might be good for security, or just to keep the pace of progress high.
There's also another strategy which is more granular, which is to version the actual code modules or even functions:
So maybe your breaking change doesn't break everything but it does break your date module:
becomes: And the dependency is just a feature bump: Now your users can choose the V2 module if they prefer it's APIs and features or stay on V1.That one might be harder for the author to maintain over time though.
And even more granular is at the function level:
https://youtu.be/oyLBGkS5ICk
It's <BREAKING CHANGES>.<100% BACKWARDS COMPATIBLE CHANGES>
That's it. Fully SemVer compatible, if you want (just add .0 at the end)
DEFINITELY.PROBABLY.POSSIBLY
https://www.youtube.com/watch?v=oyLBGkS5ICk