I will note that this reverses the direction of implications.
In SemVer, you should expect breaking changes only in major versions. (All version changes with breaking changes should be major, but nonbreaking changes can occur in major or minor versions.)
This is not the same as expecting only breaking changes in major versions. (Which would mean all changes in major versions should be breaking, nonbreaking changes cannot occur in major versions, and breaking changes might still occur in minor versions.) .
Even skilled English speakers make mistakes here because English is both very permissive about word order, but also tends to give different shades of meaning to each other. "Only" is a pernicious one. When I got my last book copyedited, fixing the location of "only" was one of the most common changes.
Red Bull Racing argued that in that sentence "any" doesn't mean "all". As a non-native speaker, this is just mind games for me.
More reading: https://english.stackexchange.com/questions/580131/does-any-...
If you really have a lot of free time, interest in regulations, F1 AND the English language, this deep dive can also be interesting: https://www.reddit.com/r/F1Technical/comments/rkv633/unpacki...
I remember a puzzle which presented a (fairly long) sentence and asked "provide a word that can be correctly inserted at any point in this sentence".
The answer was "only". (Of course the meaning would change according to where the "only" was placed, but still... you'd have a hard time inserting "experience" at every position of a sentence.)
(And in fact, the primary meaning of the first sentence is the one identical to the meaning of the second - you can read it "she only told him that she loved him [and she didn't do anything else]", but by default you'll read it "she only told him that she loved him [and she didn't tell him anything else]".)
The reason for the ambiguity is that the verb is the head of the sentence, and so an "only" placed to scope over the verb has several different options for where the scope "really is".
English syntax is not mathematical logic. The song, “I Can’t Get No Satisfaction” is not a song about someone who is forced to receive satisfaction.
There's an episode of "This is Pop!" on Netflix that explores why the Swedes write so many pop hits.
One of the arguments presented is that they speak English well as a second/third language so they're less focused on the lyrics making sense and being grammatically correct, and are free to make lyrics that sound like they work but are non-sensical on reflection. It's just the right amount of separation from the language.
Ever since I saw that, I've been listening closer to a lot of the top pop songs over the last decade, and it's fascinating how English is so breakable while sounding right to our mind, but falls apart under scrutiny. (Obviously this is not unique to the swedish writers, even people who only speak English do it as well)
Even more recently, Shake it Off by Taylor Swift.
Somewhat along the same lines, I was watching the Get Back documentary on the Beatles and the number of songs they write by effectively scatting and then filling in the words that were their big hits is amazing.
Again they're writing to fit the tune, then in filling the words based on a general theme. It's very different than the songs they write where you can tell someone's sat down and written the words first.
Probably?
That was kind of his thing, when he wasn’t just inventing words to fit rhyme/meter/mood.
He’s not who you would go to if you wanted unambiguous documentation of the guarantees you were providing downstream users, that wasn’t really his thing.
Open any book or paper on semantics (the linguistics kind, not the philosophy or the early-20th-century pseudoscience kind) and you might be surprised :) It’s just that the logic is not that straightforward to get out, and of course there’s semantics (the literal meaning, where “Can you pass the salt?” is a question about ability) and then there’s pragmatics (the thing one is actually trying to communicate, where “Can you pass the salt?” is a request with certain degrees of respect and formality attached to it).
> “I Can’t Get No Satisfaction”
This is probably not a very good example as it’s just (AFAIU) not strictly “standard English” in that it exhibits negative concord absent from the standard grammar (for dramatic effect, although “We Don’t Need No Education” is probably a better case). That is to say, this is a phrase in a language (which is very similar to standard English but not quite the same) where the grammar requires dummy negations on complements of negative verbs—a purely syntactic thing that doesn’t even reach the layer of semantics / logic.
But then many other varieties of English do that (I remember reading somewhere that the dialect that gave rise to the current standard is actually somewhat unusual among other dialects in that respect), so does my native Russian (and other Slavic languages, and some but not all Romance ones), and Japanese actually requires you to negate the adjective when using the adverb meaning “hardly, not very”[1] while at the same time using some double negations as polite affirmations[2].
“Not everything that glitters is gold”. (The truly 1:1 translation is “not everything is gold that glitters” but that word order felt very wrong and I believe the change doesn’t change the meaning, does it ?)
How does it sound for a native speaker ?
To avoid ambiguity (whether actual or merely perceived by me), I might say “Just because something glitters, it need not be gold”, but that is not much of an aphorism!
It's still just really bad work from the gem developers though. Ideally you shouldn't ever drastically change the behavior of a method. Just introduce it again with a new name and remove the old one. Yeah it might not be as nice, but it avoids triggering $73,000 worth of incorrect transactions.
TFA doesn't mention or acknowledge this aspect of the issue, but it did surprise me that they considered a version bump from 7.0.8 to 7.3.3 to be innocent looking.
In fact, these 'minor' releases actually appear to be quite significant and the issue in question in TFA was explicitly documented. [0][1]
I hate to say it but this was just a case of sloppy work on the startup's part. It took me less than 5 minutes to find that info.
[0] https://docs.mongodb.com/mongoid/current/tutorials/mongoid-u... [1] https://github.com/mongodb/mongoid/releases/tag/v7.1.0
Expecting programmers to audit all of their code before a minor update is ridiculous.
It's not the responsibility of OSS maintainer to ensure your software still functions after an upgrade if you're not even willing to spend a modicum of effort to hold up your own end of that bargain. This isn't a big ask, it's why there are changelogs and release notes in the first place; they make it so you don't have to audit the code every time the version changes.
If you're not willing to do that and want unattended upgrades for all dependencies, then this is where the 'no warranty' aspect of that OSS license comes in.
The OSS maintainer has no responsibilities at all so you are right. But if anyone was to blame, it's certainly the library. It's outrageous to radically change a function while keeping the name the same. Because of exactly this issue. If they renamed it you would get an exception which is much nicer.
Some deps you can trust the owner and just carefully review the change log. Even that would have caught this issue, though I'm not sure I'd count this gem as trustworthy.
Maybe you are a very careful developer, but the vast majority out there is not.
Shipping an udate that will corrupt data if you don't read the changelog is very very dangerous.
I see why they did it. Having a method with the same name as in Active Record but with different behavior is also dangerous.
But they really could have handled this better.
These days, it's more a case of updating one thing at a time, and doing the research up front to see what I'm gaining from it or if I might as well stay on the current version. No point updating for the sake of it.
That's 5-10 minutes of up-front, preventative effort that might otherwise become hours of reactive firefighting, and as much time spent on damage control, if it got into production unchecked.
However, you can't rely on this. Many open-source projects fill a niche and become popular when it isn't the intention of the author. This causes a disconnect where the users expect the project to be for them, when the project is really for the author. Mongoid seems to be more in the former category (for the user).
Because some libraries are badly managed, you need to audit all of them.
That said, strong types can be a godsend for catching accidental breaks, even if it wouldn’t necessarily have helped here. A strongly typed language that has a more disciplined ecosystem is less likely to run into these kinds of issues.
It’s one of the tradeoffs you pick when deciding between languages. I decided the tradeoffs didn’t work well for me and left for compiled languages, for now.
This would not be caught by compiling, strong types or anything else - this is a change in behavior which has been arbitrarily done by the maintainers of the dependency.
> Certainly it has something to do with the ecosystem, if the most popular MongoDB driver in the ecosystem is making breaking changes in minor versions.
How should this be handled? Should a "good" ecosystem vet every single change in every single library that gets updated? Or should there be no libraries and only stdlib instead (wait, I think I know a compiled language which did just that for quite a few years, let me think which one that was again...)
> How should this be handled? Should a "good" ecosystem vet every single change in every single library that gets updated? Or should there be no libraries and only stdlib instead (wait, I think I know a compiled language which did just that for quite a few years, let me think which one that was again...)
This isn’t really about Ruby vs all compiled languages, I mean, Brainfuck is a compiled language and that doesn’t mean I was endorsing Brainfuck to replace Ruby. However I would certainly endorse Go to replace Ruby. Rust too, although using Rust to do web development stuff feels like using a nuclear warhead to open a door.
Anyway, the reason why types is relevant to this discussion is because types are contracts that are statically enforced and the break that occurred happened due to a contract change. This change illustrates that even if you do have types it doesn’t guarantee you won’t have breaking contract changes. However, eliminating entire classes of accidental or intentional contract breaks is an obvious win/win. If it was as complex and obtuse as C++, nobody would bother. But C++ isn’t the only game in town anymore for fast compiled strongly typed languages.
Am I to believe that you hold every other language to this same standard? A single maintainer of a reasonably popular project makes one boneheaded decision in a release, and that's enough reason to denounce the entire ecosystem?
> That said, strong types can be a godsend for catching accidental breaks...
Ruby is a strongly typed language.
You probably mean static types, but even that wouldn't have helped here. This was a behavioral change and did not affect any of the (implicit, in Ruby) type signatures of the function. It would not have affected any of the explicit type signatures of the function in other languages. I say this as an enormous proponent of Rust and static typing.
You are tilting at windmills.
> Generally, a strongly typed language has stricter typing rules at compile time, which implies that errors and exceptions are more likely to happen during compilation. Most of these rules affect variable assignment, function return values, procedure arguments and function calling. Dynamically typed languages (where type checking happens at run time) can also be strongly typed. Note that in dynamically typed languages, values have types, not variables.
Just out of curiosity, exactly what do you even think weakly typed could mean by your own definition of it?
No need to address the other points, because we're talking about anecdotes and not scientific data. Yes, I'm using a single data point as an example about a feeling I have to justify my opinion.
But if a change like this doesn't cause some kind of uproar in the Ruby community, then the community has a problem.
Nitpick, but I don't think unit tests would have caught this. Integration or systems tests might have caught this however, but those can be much harder to create and maintain in practice, in this particular case.
The worst part is that breakage is often far from obvious, and spending good time wondering why everything breaks when it shouldn't is really really infuriating and makes me feel like the entire world of software is hopelessly broken.
Unless you have a way to stay up to date with the security of each and every dependency you have, that seems dangerous. There are static security scanners out there which can parse your dependency tree and alert you, but often they have a high noise ratio ( e.g. npm audit), so that only helps so much.
You get free, trivially applied security updates and a much lower chance of breakage. Optionally you can pay for support to have them fix problems in dependencies.
Granted, It'll be difficult with languages like Javascript where many popular dependencies aren't likely to be packaged.
(Edit: whoops, I misread you. I thought you were saying APIs are not in scope for SemVer. Now I think I realized you meant, you can have bugs that break things and that's not related to semver.)
Is the semver contract with respect to the end user activities or the API? For Flux and Helm for example, the APIs are supported with semver code (and the interfaces use Kubernetes API versioning)
In Helm, the CLI and user activities are explicitly not part of the semver contract as I understand it, only the API (at least with respect to experimental features like OCI charts for now?)
The flip side of most breaking changes that are released is some software developer who either wasn't experienced enough to imagine that the change would break someone or else they're dealing with a very hard problem that they're trying to solve and the break change was collateral damage in some edge condition they didn't consider. Many people's breaking changes are someone else's bugfix, and often users fluidly wind up on either side of that condition on different bugs -- screaming about bugs that haven't been fixed for years and then screaming about other bugfixes that broke them.
Update to new minor version. BAM, annotation no longer works, the classes have moved around and I'm staring at an ugly exception. To be clear: these were not private modules, methods, what have you. So we're not talking about the space bar issue here, it wasn't some undocumented or buggy behavior, things just stopped working as the API got shuffled around. At that point this was literally the third semver breakage in the same month, and yeah, this is unusual, but dependencies have stayed fixed since then, it's a headache and it makes me feel like everything is just built on a pile of shifting sand.
TANSTAAFL.
I don't think that's true. I've always thought a breaking change is defined in broad terms as "the public API surface, runtime, and output". Given that, each of these would be a SemVer major:
* remove a publicly-exported function * public function changes order of arguments * drop support for an old version of the language * a function changes behavior significantly (the issue the OP wrote about)
Relying on a specific major version, you should be able to assume none of the above will happen. The following would _not_ be a major version:
* renamed/removed private versions * add extra, optional arguments to public function * add support for new version of language * the xkcd example about CPU usage
In each of these cases, if you rely on non-guaranteed behavior, you should check every version upgrade closely- package maintainers don't make any assurances about those.
I'm not sure how mongoid is developed, but this smells like a bug to me. The `or` behavior started considering a thing it didn't used to, possibly unintentionally. Unfortunately, released mistakes are the one thing SemVer doesn't cover. Testing sufficiently about your own assumptions (in this case, that the user upgraded matches the one found in the query) is the best way to CYA.
That strikes me as exactly the sort of guarantee that's _not_ made by semver. You can rely on that behavior, but then you have to check against every patch version, because that's exactly the sort of thing that could change out from under you. Maintainers shouldn't worry about breaking this case- they never promised to support it in the first place.
I think a lot of those cases should be a major version change, and that's totally fine. We _should_ minimize the number of breaking changes to code people depend on, but sometimes the best fix to the (totally reasonable) situations you outlined above is to make a new major version. Users will have to make changes to upgrade (instead of just bumping the versions), but it won't change out from under them. Some docs about what's changing, why, and how to migrate your code go a long way here.
And you have to understand that the people making these decisions make dozens of them for every single release, and when they make category mistakes that is how you get breaking changes released early. Every single bug when looked at in isolation looks obvious what should have happened given hindsight bias.
And I just don't think open source software, that isn't corporate backed in one way or another, should ever go 1.0 and it should stay 0.x.y and go ahead and break compat every 3-6 months or so as necessary. The cost of SemVer and trying to get it continuously correct on every single patch is a large tax.
We do the same thing and have the exact same experience, unfortunately. Software is a messy business and it's hard to apply strict rules to human process.
I think ultimately SemVer can be used to to accurately communicate if you, a user, are safe to upgrade between versions if applied strictly. Whether or not your users will (or be happy about it) is another ballgame.
EDIT: just noticed the docs I found via Google are marked as “old” in the URL: https://mongoid.github.io/old/en/mongoid/
Are you suggesting they dropped semver in between these releases?
If this is the case, I kind of hope, for maximum irony, that they dropped it as part of a minor version bump.
Sure it was a mistake by OP in TFA but it's an honest mistake any of us could make. And it's a really Bad Move by the Mongoid devs. It'd be a bad move in 1.2->1.3, but by 7.3? I'd just be like "whelp, this is how this function behaves now in perpetuity, I guess just document that it has different semantics than AR" even if an 8.0 was released. This is the kind of change which causes really subtle bugs. It's rarely worth it. Just introduce .chain_or() or something.
It's really just as simple as going to the release notes and seeing if there's a mention of breaking changes or deprecations. If no such thing is mentioned then you're fine, otherwise you go and see if that change affects you at all and just take a little bit more time to test the upgrade. This has been standard practice at plenty of places that I've worked.
It also doesn't mean that it makes the change a particularly good one, but I don't think I can make a bunch of OSS maintainers responsible for my own failure to review an upgrade and test it before rolling it out. In those terms, there is some culpability both with the maintainers and with the library users.
Integration tests might've done the job here.
You'd be surprised how far people go to shoot themselves in the foot.
Yet they are in obvious violation of SemVer expectations, which they declare to follow [2]:
> Mongoid follows versioning guidelines as outlined by the Semantic Versioning Specification, so you can expect only backwards incompatible changes in major versions [sic]
[1] https://docs.mongodb.com/mongoid/current/tutorials/mongoid-u...
[2] https://mongoid.github.io/old/en/mongoid/docs/upgrading.html