Hacker News new | past | comments | ask | show | jobs | submit login
Sentimental Versioning (sentimentalversioning.org)
201 points by zdw on Oct 12, 2020 | hide | past | favorite | 79 comments



SQLAlchemy author here. The critique of "Backbone" and the original quoted author's note at https://github.com/jashkenas/backbone/issues/2888#issuecomme... expresses an issue that SQLAlchemy has as well. A fine-grained development library has a ton of "surface area". Like Backbone, a large portion of our bugfixes necessarily infringe upon some previously undefined behavior that will now "break" for people who relied upon that. If SQLAlchemy were pure semver, we too would be at SQLAlchemy 48.0.0, the third digit for us would usually be fixed at "0" and mostly useless, and we would have no way to distinguish between a "major feature release with semi-breaking changes and removal of things that have been deprecated for years" which is what we normally have in our 1.2, 1.3, 1.4 etc. vs. a "major release with significant backwards-incompatible changes" which is what we will have in SQLAlchemy 2.0.

We necessarily include extremely detailed change documentation for a 1.x to 1.y release and our versioning system is described in great depth at https://www.sqlalchemy.org/download.html#versions . I didn't provide an easy word like "romantic" for this website to mock but we're doing essentially the same thing AFAICT.

When Jeremy says "This allows folks, immediately upon hearing about a new release, to get a rough sense of its scope.", that is exactly what we are doing as well when we are suddenly calling an upcoming release "2.0". The scope is dramatically broader than the scope in which we move from 1.x to 1.y. It's fun for semver purists to make clever (and definitely funny) websites mocking this need but that does not change that fact that semver does not work for every kind of project.


What's wrong with version 48.0.0? Be it 73838.0.0. That's OK. What's wrong with a fixed 0 at the end? If your kind of software doesn't have a reason to release a path or a minor change, well, then that's OK.

Versioning is for machines and developers. Versioning is the way to communicate the scope of change. Tools rely on it. The ideal package manager should reject non-major releases that have breaking changes.

Softwares that don't respect Semver actually don't respect the developers that use them.

End users don't care about the version of your product (even if they are developers). Fast, what's your current Chrome or Firefox versions? (Hint: greater than 48)


Hey Mike - Thanks for all your hard work on SQLAlchemy, it is very much appreciated.

I've yet to experience any serious issues updating SQLAlchemy versions and the guides for upgrading have been top notch.


This is also a very common issue for CSS libraries, where every line change would need to be interpreted as a breaking change according to semver.

Instead, a more sane thing to do there is: patch is for bug fixes, like perhaps addinng a `cursor: pointer;` that you forgot to a button. Minor is for adding new features, and major is for heavily modifying or removing features.


Surely an honorable mention for MySQL, who happily make massive breaking changes in ..x versions and bury them halfway down the release notes


Let's call that Mental Versioning.


That's pretty metal.


Indeed. I was using SET PASSWORD in a way that broke between two different versions of MySQL 5.7. (I can't remember what specifically it was. I believe I had to change my statement to no longer use the PASSWORD() function.)

I remember being pretty upset that they broke that with what surely must be only a bug fix release. Now I know better.


You forgot Linux!

https://lkml.org/lkml/2019/3/3/236

    I'd like to point out (yet again) that we don't do feature-based
    releases, and that "5.0" doesn't mean anything more than that the
    4.x numbers started getting big enough that I ran out of fingers
    and toes.
    
    Linux


I would have included an honorable mention for Guava.

With as many as three major releases a year, its versioning system is a performance art piece exploring the endless turmoil and internal struggle of a project that is expected by the world to follow semantic versioning, but whose soul yearns to move fast and break things.


Ah yes, version "9999.0-empty-to-avoid-conflict-with-guava" is especially impressive.


Guava cannot not be viewed as a stable platform library, with three major releases a year.


Over the past 4 years of using guava in a few services, I haven't needed to do anything more than bump the version number.

At least for the basics(immutable collections), it's stable enough


It's generally pretty fine as a direct dependency. It's when it comes in as a transitive dependency that the demon comes out.


Build numbers is engineering. Version numbers is marketing.

I'd love to know how semver, git-flow, and other zombie ideas get established, despite decades of effective battle proven lore. Uncle Bob's theory is that population grows faster than they can be taught (onboarded), so that cone of ignorance expands faster than adoption rate of best practices.


I don't really consider semver to be in conflict with build numbers or version numbers for marketing purposes.

My favored scheme is roughly like so. Automatically assign a build number to every build for tracking purposes. Assign a semver number to every version you publish in order to give some concise signaling to operations folks and technical consumers about how the version behaves compared to the previous one(s). And, when applicable, assign a more creative version number for marketing purposes. I've always liked year-based numbering schemes for that purpose. It's immediately meaningful, and encourages staying up-to-date by giving a reminder that you're still on a version that came out 4 years ago.


Correct. In every way.

Deming process people call this "one way flow" of information.

Paraphrasing, mostly to continue my rant:

Versions are just labels (tags, aliases) you share with clients, users. Semver is no worse than any other scheme. And fretting about semver gives scrum masters something to do.

Build numbers are used for internal processes. Like the "found", "fixed", "closed" fields that QA tools should have (but rarely do).

As you said, version is annointed AFTER an artifact is accepted, moments before release. Like an imprint. (For all workflows prior to "Test Into Prod", so ymmv.)

Then everyone can also precognitively divine a future version, so stuff like press releases can be front loaded. Genius!

Few things drive me more nucking futts than teams which predetermine version before attempting a build. Oops, build failed. Manually increment semver. Try again. Rinse, lather, repeat. Then ferret all wiki references to semver and update those JIRAs.

Trying to explain this to noobs is harder than advocating using knives for cutting to someone who's only ever seen spoons. People get it when they see it, but can't imagine it, so the push back is exhausting.


Not just noobs. It's even more exhausting to try and have this sort of conversation with someone who's been doing it that way for 20 years, and never known anything else.


> Uncle Bob's theory is that population grows faster than they can be taught (onboarded), so that cone of ignorance expands faster than adoption rate of best practices.

This probably also explains his success...


I think the TeX and METAFONT versioning system is my favorite versioning system. It's just wonderful.

For those of us working on things not worthy of a mathematical constant, I prefer plain old major.minor. If it breaks something, increment major and reset minor to 0. Anything else doesn't really matter.



I mean, a few numbers can't convey everything that's relevant for releases. But I like how comver explains well some of the concepts. I expand with a few more:

- Is the release stable? Alpha, beta, experimental new features?

- Does the release have known vulnerabilities? How insecure is it? (of course, this changes as time passes and might stop being tracked, so it's not easy to manage)

- Did a new release break back-compatibility? How much? Are we breaking everything, or just slightly changing the behavior of one function?

- Does the release include new important features or API changes?

- How often should I check for new releases, or how can I get notified if there are important unexpected changes?

To me, any non-toy software project needs to respond very clearly to all these questions. If it does, I don't care about semver or not semver. If it doesn't, I don't care about semver or not semver.


SemVer says that a PATCH can only include backwards compatible changes which fix incorrect behavior. Less important is whether or not a bug has been fixed but whether anything other that internal behavior has changed.

MAJOR versions can also be incremented when "substantial new functionality or improvements are introduced within the private code" which is really the cause for confusion and, in my opinion, should be removed from the SemVer standard. There's no semantic value to consumers when significant amounts of private code has changed except for the potentially heightened possibility of introducing bugs. Removing this would clear the confusion up altogether.

But the ComVer contention is that bugs don't deserve special treatment. I disagree. Changes to behavior that fix something but don't change anything else are changes I'm likely to want to deploy while maintaining a system. I might be using a legacy version where the main branch has progressed but I still want to deploy PATCH releases for bug fixes for an older version when they become available. I do not want to upgrade by a MINOR version if I can avoid it because it would add functionality or interfaces I don't need and which themselves could be problematic. I want to make as little change as necessary to maintain a otherwise stable system.

Minimizing the surface area of a release to only the necessary is an important principle to follow when publishing public APIs. Thinking about bug fixes as distinct from new features means you're considering every change in light of the potential impact to consumers. Even when know you don't have any actual dependent consumers, it's still a good habit of a disciplined engineer.


My general approach is to look at the unit (and possibly integration) tests: if the new release has no changes in test code, that's a PATCH release. Of course it's not clear cut and can't really be automated, but as a guiding point it has served me well.


Edit: It should read "MINOR versions can also be incremented when..." instead of MAJOR.



I wish semver adopted "era" as an optional fourth (left-most) number so you could relay that valid sense of a collection of major versions.

Without this you just leave the umbrella of versions to create a new package for your next era. This is fine except for corporate or big name projects that want the advantage of their package name for many years.


Yeah, I'm a fan of some sort of "{branding}v{major}.{minor}". That way there's a "branding" version (or "era" as you call it) that signals larger level but arbitrary changes, and you still can communicate lower level semver stuff.




You can still name your releases if you use Semver. You can for example call it 1.2.3 John, 2.0.1 John, Then if the next release is really big you can name it 2.1.0 Phil. Then when talking to customers you can ask them if they have a John Or Phil version.


Dephell has a nice overview of various software versioning approaches: https://dephell.readthedocs.io/cmd-project-bump.html#version...


Better to call it misguiding than nice - it calls semver the "most recommend versioning scheme"


Could you expand on this a bit more? That's certainly the impression I have of semver.


What about: Google "New Major Version Every Six Weeks or Die Trying" Chrome.


I respect the simplicity of Chrome's versioning scheme.

99% of people using SemVer actually only have two types of change:

1. "Updating will probably work, but you should test anyway in case we've introduced a regression or you depend on behaviour we've decided was a bug"

2. "Updating might not work, depending on which features you're using, but you should test anyway and you'll be forced to update eventually anyway"

Given users have to respond in the same way to both, why adopt a versioning system that claims 1.12 is greater than 1.2 ?


sure it looks weird if you omit the patch part of semver. However, if you think that 2 > 12, perhaps, you're not the target audience for versions in general. /s


I've seen bugs caused by a tool not knowing that version `1.12` < `1.20` :/ so yeah, some professional developers don't know that.


Windows 95 reported it's version as 3.95 instead of 4.00 for this exact reason [0]. Apparently, writing lexicographical ordering on tuples is surprisingly unintuitive:

    bool operator<=(version lhs, version rhs) {
        return (lhs.Major <= rhs.Major) || (lhs.Minor <= rhs.Minor) || (lhs.Patch <= rhs.Patch);
    }
looks correct for anyone who have never wrote such comparison before, but of course it's wrong. The correct one is

    bool operator<=(version lhs, version rhs) {
        return (lhs.Major < rhs.Major)
               || ((lhs.Major == rhs.Major) && ((lhs.Minor < rhs.Minor)
                   || ((lhs.Minor == rhs.Minor) && (lhs.Patch <= rhs.Patch))));
    }
[0] https://devblogs.microsoft.com/oldnewthing/20040213-00/?p=40...


Your second code is wrong. For lhs = "4.3.7" and rhs = "3.3.7", your code will give true. It should be false.


Uh, no it won't?

    cat >test_ver.cpp << EOF
    #include <cstdio>
    
    struct version {
        int Major;
        int Minor;
        int Patch;
    
        version(int major, int minor, int patch)
            : Major(major)
            , Minor(minor)
            , Patch(patch)
        { }
    };
    
    bool operator<=(version lhs, version rhs) {
        return (lhs.Major < rhs.Major)
            || ((lhs.Major == rhs.Major) && ((lhs.Minor < rhs.Minor)
                    || ((lhs.Minor == rhs.Minor) && (lhs.Patch <= rhs.Patch))));
    }
    
    int main(int argc, char **argv) {
        version lhs(4, 3, 7);
        version rhs(3, 3, 7);
    
        printf("lhs <= rhs? %s\n", lhs <= rhs ? "true" : "false");
    }
    EOF

    g++ test_ver.cpp -o test_ver && ./test_ver
    
prints

    lhs <= rhs? false


No, it works. It's just non-obvious that it does due to the reliance on 4 levels of braces.

It is equivalent to the following:

    lhs.Major < rhs.Major
    or (lhs.Major == rhs.Major and lhs.Minor < rhs.Minor)
    or (lhs.Major == rhs.Major and lhs.Minor == rhs.Minor and lhs.Patch <= rhs.Patch)
Deduplicating (lhs.Major == rhs.Major) decreased readability enough to be confusing.


There is so many ways to write this comparison that after a couple of times you really wish there were something in the standard library to help you so that you could write e.g.

    bool operator<=(version lhs, version rhs) {
        return std::tie(lhs.Major, lhs.Minor, lhs.Patch) <= std::tie(rhs.Major, rhs.Minor, rhs.Patch);
    }
Wait, there is! And Python has something like this too, even more laconic: you don't need to write "std::tie", naked parentheses are enough.


And Microsoft jumped from Windows 8.1 to 10 to avoid breaking applications they assume “first digit is 9 means Windows 9x”. And possibly to catch up with Mac OS X 10.x.


1v12 is greater than 1v2. The issue is the "." looks like a radix point, so people think they're decimal numbers. Pick some other separator and that confusion goes away (don't use ",", since that's a radix point for many languages).


Usually there's a 3rd type (patch versions):

"This update fixes important bugs and will almost certain work if the previous version did"


...but you should test anyway in case we've introduced a regression or you depend on behaviour we've decided was a bug.


Unless you depend on the bug, which you might not consider a bug for your use case.


I don't mind Chrome's versioning / release schedule; for the past decade that it's been out, I've never noticed an update beyond the small / incremental redesigns every couple of years (like rounding tabs, dark theme support). Nothing ever broke. Nothing was ever added as an obvious feature or annoyance.

edit because I feel like I have to: this describes my experience, it may not be fact, your experience may differ, etc.


That was actually an explicit goal. We knew that if we were going to update that frequently:

- the changes had to be small (and would be by nature of so close together) - we had to be very very careful about backwards incompat changes (either to the UI or the APIs).


Firefox too. The rapid version numbers in web browsers are a nice contrast to the days of "forever IE6" comparability. Web compatibility back then was a nightmare.

Of course we're on the opposite end of that today, where two major browser engines are Chrome and WebKit(Safari), with Gecko often an afterthought.


Firefox adopted rapid version numbers basically because the rest of the browsers did. For years Firefox was on 3.something, and they'd get questions from people saying things like "Microsoft is already on Internet 7, when are you going to support Internet 7?" If I recall right, Firefox skipped versions 5 and 6, and starting with 7 they decided to use a rapid version number scheme and deemphasize version numbers in their marketing.


I had clients who used a three digit versioning scheme: Major version was always 1 (feels more stable than "0"), minor version was iteration number, patch version was spring number.

Glad these alternative versioning schemes now have a name.


I work on a project where:

Patch - a build we do to be deployed to clients test environment that we don't control

Minor - a prod build for client

Major - reserved for when the client demands a complete redesign of the app

It works for us, for this project... And of course nobody else is depending on it


The last part is what matters.

As long as it's not a library with no external things and not more than one thing depending on it you don't necessary need semver at all.


Can you add posting year? I think it mentioned node was 12... Wasn't that before the io fork?


Looks like that posting year is 2014.

This was discussed on 2014-07-31: https://news.ycombinator.com/item?id=8247877

This matches “[Node.js] version 0.12 having been in the works for over a year”, since 0.10 was released on 2013-03-11 (per https://nodejs.org/en/blog/release/v0.10.0/) and 0.12 was finally released on 2015-02-06 (per https://nodejs.org/en/blog/release/v0.12.0/).


What the heck is up with Node.js versioning?

It jumped from 0.12.7 to 4.0.0.

https://nodejs.org/en/blog/year-2015/


versions 2 and 3 were the iojs fork. Version 4 was the reunification were they started from the nodejs base again but with the iojs people involved. The version number was bumped to indicate this was the go foward path for iojs and node.js users.


2017 probably:

    $ curl --head http://sentimentalversioning.org/
    HTTP/1.1 200 OK
    Server: GitHub.com
    Date: Mon, 12 Oct 2020 05:28:30 GMT
    [ ... ]
    Last-Modified: Fri, 12 May 2017 07:22:37 GMT



The io fork? That was 0.12. Not 12.0.


    Sentimental Versioning:
    You may see a download
    Along with release notes
    And somehow you know
    You know even then
    That somehow you'll update
    Again and again.


Just one tiny observation: I find the headings really hard to read.


TypeScript: Every minor version has breaking changes and the next version after `n.9` is `(n+1).0`


They never go higher than .9? Why?!



Wow. Also pretty funny that he says "if we followed semver rules exactly, literally every single release would be a major version bump".

Seems like quite a blase approach to the development of such a large project.


I always ask teams that say that: "OK, so every release will be major, what's the problem?"

I don't think anyone is losing sleep that we're on Chrome 86.0.4240.75 and that communicates exactly the amount of information I need to know if that's older or newer than another version.

I agree it's not semantic versioning, per se, and maybe not nice to developers but it's at least communicating clearly IMO.


I'm on version 83.0.4103.116 which is 3 major versions behind. I am averse to upgrading because it's working for me and I'm concerned about 3 rounds of incompatible/breaking changes. I have no idea about the relative maturity of the two versions, how big the breaking changes are or if they're likely to affect me, or how much time has transpired between the two versions. With every "major release" of Chromium my wariness increases, until the only way you'll get me to upgrade is when I'm forced to upgrade my system.

How is this communicating clearly or being helpful to me? Does anyone know offhand what major version of Chromium/Firefox they're using? If it's just an incremental counter, then why do we need .0.4103.116?


4240 - 4103 = 137 days between versions.


Instead of being a single blog post / article as part of another site, here we have a whole domain for a single article.

Hope more articles coming up on the site.


In case you missed the joke, it's evoking https://semver.org/.


A lot of people in the embedded world still use A,B,C,.. for versioning.

Add to it the dangers of names being taken directly from de clients, and enjoy the fun. Having clients name something “machine v2” was a nightmare.

On the plus side, it gave us a lot of jokes when a client named something “machine X” about how many iterations would it take to get it right.


As someone who fights with other teams about not SemVer-ing properly, this is beautiful and I'm so excited to give people grief with this.


Erlang used to use a bastardized version of Ericssons internal versioning scheme, before they Switched to semantic versioning.


How did Ericsson format the internal version numbers?


Surely Perl has a poetic versioning history. Sort of an epic poem, alas.


Beautiful!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: