13 ms·
The cargo cult of versioning
- jondubois 9y agoI have to agree that semantic versioning is definitely more of an art than a science. I wrote an article about it a while ago https://hackernoon.com/its-not-a-bug-it-s-a-feature-the-problem-with-semver-cb067de7843c https://hackernoon.com/its-not-a-bug-it-s-a-feature-the-prob...
- edibleEnergy 9y agoWhat this article arrives at is basically the perl/CPAN model. Don’t break backwards compatibility unless you absolutely have to; as in cases of security or when the original functionality never worked in the first place.
- akkartik 9y agoThat's interesting. Could you point me at a good link for learning more? I spent a little time with CPAN back in 2001/2002 and ran screaming. Perhaps it's gotten better since.
- edibleEnergy 9y agoSure, https://perldoc.perl.org/perlpolicy.html#BACKWARD-COMPATIBILITY-AND-DEPRECATION https://perldoc.perl.org/perlpolicy.html#BACKWARD-COMPATIBIL... that’s the policy for the language itself, but as it mentions there it’s considered generally a “community” virtue. I wasn’t programming back in 02 so I’m sure it was extremely different (and the source I’ve read from modules from that era is pretty frightening. Any major framework or generally used module will try to maintain backwards compatibility at least as far as documented behavior. I’ve never come across the kind of stuff I see all the time with node and go and python; package maintainers changing parameters and such because they weren’t happy with the original API.
- albinofrenchy 9y ago> Rich Hickey pointed out last year that the convention of bumping the major version of a library to indicate incompatibility conveys no more actionable information than just changing the name of the library. Unless, you know, you care about the purpose or authorship of the library. I don't think pinning dependencies at package levels is a bad idea - ubuntu does this for a lot of packages but this relies on properly followed semantic versioning to work.
- hyperpape 9y agoThe point about renaming major versions is baffling. First of all, if your suggestion is that you use Rails 4 vs. Rails 5, that's a version number, one that may be handled a little differently by your package manager (or it may not, if you already pin to major versions). But the premise that a major version conveys no more information than a full rename is simply wrong. Is libfoo n + 1 more similar to libfoo n, or libbaz m? In extremely rare cases, it will be more similar to libbaz m than libfoo n, but in 99% of cases, that's not true. Even if that is not perfectly reliable, that probability conveys information. P.S. I'm not commenting on the other ideas raised by the article.
- jacobush 9y agoThe whole thing made me think of "A plan for the improvement of spelling in the English language" http://www.plainlanguage.gov/examples/humor/marktwain.cfm http://www.plainlanguage.gov/examples/humor/marktwain.cfm
- hyperpape 9y agoTo the best of my knowledge, that's not a joke, even though I assumed it was the first time I read it. Mark Twain believed in spelling reform (https://en.wikipedia.org/wiki/English-language_spelling_reform https://en.wikipedia.org/wiki/English-language_spelling_refo...). But I find claims that he didn't write it: http://grammar.ccc.commnet.edu/grammar/twain.htm http://grammar.ccc.commnet.edu/grammar/twain.htm
- Avshalom 9y agoThough of course 'th' should be replace by 'y' not 'x', which has historic precedent.
- abritinthebay 9y agoNot actually true. It’s a common misconception that gave us fake pseudoarchaic old English like “ye olde whatever”. That comes due to the confusion of the old thorn character Þ or þ which got all mangled and misread.
- spenczar5 9y ago"We're all on the internet, and we're all constantly running npm install or equivalent." This is not at all accurate, at least in my work. I use version pinning to be resilient to changes in the greater internet: builds don't use the internet, and instead use internally pinned and vendored sources for third party libraries. If you rely upon the internet to build and deploy your code, then someone else's outage (like github's) can become your outage, or make yours worse. If you work on a product which has high uptime requirements, this is unacceptable. Just as important, suppose any one of those dependencies breaks the social contract described in the posted article: they make a backwards-incompatible change, and your code no longer compiles or builds or (much worse!) subtly breaks. Now you're relying very strongly upon the good intentions and competency of all third party maintainers. Worst of all, suppose any of your dependencies gets compromised and now has a security vulnerability. What do you do? This is not a nitpick; I think it's central. Because I need network-free, reproducible builds of known-good code, I need to maintain a copy of third-party code somewhere. When I start doing that, I need to keep track of what version (which could be a commit, sure) I'm storing to know when I'm out of date. Then, when my cached copy is out of date, I need to know how safe it is to update to match the remote version: should I expect builds to break, or not? Semantic versioning is a way for package maintainers to signal their expectations. It's not something you can rely completely on - in these systems I'm describing, you still need automated tests to give you more confidence that upgrading is safe. But semantic versioning improves your confidence which helps avoid wasting work on an upgrade that will certainly fail, and helps identify problems in advance. It's also helpful to have minor and patch versions so applications can concisely describe which features of third-party dependencies are required to build and run your code. This makes it easier to integrate new applications - you can clearly see whether your cached copy of the third-party code is up-to-date enough.
- state_less 9y agoAgreed, plus version pinning isn't mutually exclusive to running latest, head, or whatever you call now. You can still resolve now to some version. For example, Debian stable resolves to version 9. Stable points to the dependency tree called version 9, but you can also just reference it directly if you want. You can go back in time and reference dependency tree 8 if 9 was a doozy. It's foolish to think every version is going to better than the previous, in my experience. Sometimes you have to make the present the past until the future gets its act together.
- parasubvert 9y agoI once wrote a fairly extensive post on REST API versioning techniques with similar points (lost my backup so wayback machine): http://web.archive.org/web/20160407080723/http://stucharlton.com/blog/archives/2010/03/versioning-restful-web-resources---a-survey.html http://web.archive.org/web/20160407080723/http://stucharlton... In short, we really seem to have forgotten the power of backward and forward compatibility because we assume a very short lifespan for our services (months, not decades).
- pfranz 9y agoIt's interesting to discuss this as an observation or as a way to break cargo culting (as the title suggests), but I feel like this completely ignores the people aspect. Engineers want to work on interesting problems, not to be slaves to mistakes they made years ago when designing something in their free time. Semver at least seems like a compromise instead of everybody using something different from a graveyard of unmaintained LeftPads. Of course, personally, I want every dependency I pick to be consistent and stable forever. But I want to break things and work on new and interesting problems. I'm also using an open source toolchain and aren't willing to pay for support or maintenance. Since I follow this stuff online, I'm also interested in new ways of solving old problems.
- Volt 9y ago>Engineers want to work on interesting problems, not to be slaves to mistakes they made years ago Maybe there should be a sense of responsibility. That seems to be lacking in this "engineering" climate.
- littlestymaar 9y ago> Next, move the major version to part of the name of a package. "Rails 5.1.4" becomes "Rails-5 (1, 4)". And what if a random person decided to register the package named "Rails-6" ? This system is way too prone to malicious package name squatting.
- troymc 9y agoI guess you could just skip over Rails-6 and go from Rails-5 to Rails-7. Windows 8 went straight to Windows 10. The idea is that backwards incompatibility should cause a name change, so really any untaken name would do, e.g. Rails-McTavish, Rails-Ocelot, Rails-馄, Rails-0x389, Rails-3.14159, ...
- TAForObvReasons 9y ago> Windows 8 went straight to Windows 10. The alleged reason for skipping windows 9: many software packages were checking for older Windows versions (namely 95/98) by checking if the Windows version string starts with "Windows 9" https://www.reddit.com/r/technology/comments/2hwlrk/new_windows_version_will_be_called_windows_10/ckwq83x/ https://www.reddit.com/r/technology/comments/2hwlrk/new_wind...
- half-kh-hacker 9y agoSo what you're saying is that 'Windows 9' as a viable name was, in a sense, taken?
- gruez 9y ago>https://www.reddit.com/r/technology/comments/2hwlrk/new_wind.. https://www.reddit.com/r/technology/comments/2hwlrk/new_wind.... source: msft employee who isn't even close to the windows team. that explanation ignores the fact that windows spoofs the windows version number for all applications by default (defaults to 8.1 i think), unless you have a special entry in your application manifest. so old applications will still work by default. not to mention that windows has compatibility shims to deal with this exact issue.
- 9y ago
- chrisfosterelli 9y ago> NPM for Node.js defaults to version tagged "latest". Again, if you go with the default version your project is liable to go boom at some future date. I don't understand this criticism. The "default" in NPM is to use `npm install --save`, which adds the version of the package it installs to your `package.json` so future uses of `npm install` will automatically use a semver compatible version of that package.
- bsimpson 9y agoMoreover, barring total ecosystem overhauls (Python 2v3, AngularJS vs Angular), people don't spend any effort wondering which version of a package they should use. In my experience, you install the latest. You may pin (or let yarn.lock do it for you) to avoid implicitly consuming a breaking change the next time you install from scratch, but that's to be able to explicitly test if the upgrades break your application. It's not likely because you presume the newer version is worse than the one you already have.
- slobotron 9y agoI've seen even point releases break something. For proper ci/cd you always lock to exact version, and run upgrades purposefully (say on a schedule). Last thing one needs is to hunt for breakage when a dependency changes unexpectedly. You wouldn't let your teammates commit code without review, why trust a 3rd party... (Not saying need to look at diffs of third party libraries)
- Footkerchief 9y agoIt's similar for Bundler (the de facto official Ruby package manager). After the initial install, you'll only get new package versions if you run the update command. The complaint seems to be that the update command is not sufficiently safe by default (though the `--conservative`, `--minor` and `--strict` flags help there), which is fair enough, but why not just fix the default behavior?
- akkartik 9y agoThat's exactly right. (Author of OP here.)
- mfontani 9y agoand that is exactly like Perl 5 versions are, nowadays... "5.22.1" is "perl 5 version 22, patch release 1"
- nishs 9y agoThe article makes good points, but they feel impractical and/or risky to follow. > At this point you could even get rid of the version altogether and just use the commit hash Right. Until someone brazenly force pushes to a repository, and makes it hell for your package manager. You would now have to manually hunt your dependency's repository for the closest commit hash to the one that was removed. :| > Package managers should provide no options for version pinning. A package manager that followed such a proposal would foster an eco-system with greater care for introducing incompatibility. We all want to live in this ideal world. But this requires developers to trust the authors of upstream dependencies. Sometimes this is hard to do, especially for critical projects. I just want my shit to continue working, and not be subject to the whims or genuine human errors of upstream authors.
- niftich 9y agoNames for libraries serve a dual purpose: they must be resolvable by computers (i.e. package managers, linkers, etc) but also meaningful to humans -- the programmers who supply the name as user input in the first place. Version numbers allow one to trace the provenance of later artifacts back to earlier ones somewhat linearly, instead of having to hunt for comparable artifacts unconnected by graph edges. If libraries upon major version changes were renamed instead, the programmer would have to maintain a mental mapping between the predecessor library and the successor, and all code and configuration references to the other artifact would have to be updated 'by hand', without assistance to be gained from the package manager. Including multiple versions of a library in your project is unsupported in many environments, so if the library author ever envisions a situation where the two 'versions' will be used concurrently, renaming makes sense. In the Java ecosystem, this approach was used for Apache Commons Lang, Apache HTTP Client, and Jackson. Semantic versioning has good intentions, but adherence and accuracy of the signalling is variable. Absent external enforcement, that's just the way it is -- it's a self-asserted string that no one bothers to validate and everyone hopes they can rely on. If package repositories were more than just storage, perhaps this would be a different story. It's strange to extoll the virtues of Go's dependency management, because by convention it binds the artifact identity to a resolvable external URL without any indirection. Meatspace maintenance changes like changing the hosting location of the artifact will cause Go to treat it as a different package, and inability to directly communicate externally will also break your build. Every other packaging solution has independently come to the conclusion that abstracting away artifact identity from artifact location is a good thing, but to each their own.
- zanny 9y agoOP brought up Rails, NPM, and Go but not Rust. The Rust ecosystem might be a demonstration of the inverse problem. Pinning versions is trivial in Rust because its default. You will never be pushed over a major version boundary without intentionally doing it (and "always get the latest" is just the "" version). The problem then becomes that crates never reach 1.0. Flask is probably the oldest piece of software I use that similarly never reached 1.0 - albeit its been pretty close ever since the project was reorganized last year - but the entire Rust ecosystem is buried in Flasks. Software that is 99% of the way to what the creator wants 1.0 to be, but the last 1% is something nobody wants to do. When you are 99% of the way there, the software works in 99% of use cases, and that last 1% is... the last 1% anyone wanted to do. Which means they didn't want to do it. Probably the most blatant example of this in Rust is regex, which is blessed crate that is still only at 0.2, among many others (just from my hobby Rocket project are base64, chrono (and time), cookie, dotenv, envy, error-chain, lazy_static, num-traits, rand, redis, and uuid all still in version 0.x. That being said, the impl period going on right now and this entire year of direction from the core team was explicit in bringing as much of these fundamentals to 1.0, so we will see how successful the effort is. Probably the greatest problem with semantic versioning in that* context becomes feature creep. It is scary to go to 1.0 - you feel free in 0.x land where you can make major breaking changes in a .x release whereas you need to increment that scary major version number to do it later on. And besides the fear there is a lot of logistical headache properly maintaining semantically versioned software - you should expect if you never release a 2.0 to have someone trying to contribute 1.x bugfixes down the road, with the need to release 1.x.y bugfixes for all time. Because 2.0 is, like the article says, a new library - its a different API, you changed the meaning. So you are now maintaining two libraries. There is also one final fear with the way Rust setup its crates ecosystem - if you are bold and break things but don't end up delaying 1.0 for way too long, you might end up incrementing the major version a bit. And there is a cultural and subconscious aversion to anything on crates.io you see at version 3.0 or heavens forbid 4.0 or more. That software is unreliable, the developer is changing it all the time!. But then you go and use a 0.15 crate that is having the same problem anyway, just without saying "this probably does its job" like a 1.0 can. In the end, versioning truly is almost meaningless, even in an enforced semantic versioning system the intent breaks down and meaning is lost just because different people release software differently. But that is a real deep almost - because its still more information than not having it, and in Rust right now at least it gives more helpful information than not. I'd call it a success at that point - way more than say Linux, where the major version is incremented whenever Linus wants to run a Google+ poll...
- quickthrower2 9y agoI think no pinning and just taking the latest package every time is a really bad idea. Bugs can creep into software even with the best of intentions, and it is useful for consumers of packages to have a choice whether to upgrade immediately or maybe wait until they have done some risk analysis. Also, the ability to roll back is invaluable. I don't think forcing everyone to upgrade is going to force package maintainers to make zero mistakes. Also the package-3, package-4 thing seems odd to me. I use Nuget and have used NPM / Gem / Haskell Stack on spare time projects. I would find it annoying to use this scheme because you have to hunt around searching in a different way to look for major upgrades compared to what you do for minor ones. But most of all: I want my build to be reproducible, going back into history. Having the pinned version numbers committed to source control in ensures this (As you keep an onsite cache of the packages, or you trust the package repo to stay available and for published versions to be immutable).
- grzm 9y agoI agree that taking the latest package every time you're building for production deployment is a bad idea. And version pinning in and of itself doesn't really solve this problem either: if you're relying on an external source for reproducible builds, you've got a potential point of failure. To have a reproducible build you need to have access at the time of the build to the specific artifact you want to include in the build, which at the end of the day means having that library at hand locally as part of your build system. We don't have great stories yet about how to maintain these artifacts locally: setting up your own repos for many ecosystems isn't a walk in the park. Specifying a version does allow you to know which artifact to include, but that's at the artifact level, which often is conflated with the particular library that artifact provides. A build number or other included in the artifact gives you the same selection properties, yet is independent of the code the artifact actually provides. Currently code and artifact are labelled similarly, but that's more a purposeful accident rather than a guarantee or deterministic property. As for the package-3, package-4 thing you mention, I agree, it's kind of a mess. I think that's more a result of this lack of separation between the code and the artifact (if I'm understanding you correctly), and module (what is referenced in the code itself namespacing and aliasing. I don't think it's a solved problem.
- hp 9y agoLinux distributions have been including the major version in the name for a long time; when we started doing this at Red Hat I wrote down the rationale at http://ometer.com/parallel.html http://ometer.com/parallel.html in order to convince upstream projects to do this upstream, as many now do. For non-system package managers like npm, always using a lockfile is the best current option IMO, for those package managers that support it. https://blog.ometer.com/2017/01/10/dear-package-managers-dependency-resolution-results-should-be-in-version-control/ https://blog.ometer.com/2017/01/10/dear-package-managers-dep...
- akkartik 9y agoThanks for these links, they're super interesting. However, I should clarify that my post is about how we upgrade dependencies in dev, not how we deploy them in prod. I'm talking about version numbers in Gemfile, and I think you're talking about them in Gemfile.lock. Am I understanding you right?
- Anderkent 9y agoNo version pinning means no repeatable builds. A build from a month ago when ran today should use exactly the same version of any libraries it references, not something that was published in the meantime.
- nickjj 9y ago> Result: best practice of pinning major version using the twiddle-waka or pessimistic operator all over the place, and Steve Losh shaking head sadly. I'm not sure why this is potentially seen as a bad thing. Call me crazy but I enjoy version locking every dependency because it lets me know exactly what I'm using and it's something commit to version control so other people know as well. It becomes a form of documentation.
- akkartik 9y agoAuthor here. I'd love to have you read the two links I refer to at the top of my post. They articulate why it's a bad thing. Incidentally, version locking every dependency is identical to just inlining every dependency in the repo. And I actually like this approach a lot. See my example of "exemplary library use" at http://arclanguage.org/item?id=20221 http://arclanguage.org/item?id=20221. OP was an attempt to reconcile my priorities with the mainstream.
- bsimpson 9y agoThe problem with this article, and with semver, is that every change is either a new feature or a breaking change. Some new features are also breaking changes. A bug is encoded in the behavior of the program. "Fixing" a bug is changing the behavior of the program - breaking it if someone downstream was implicitly or explicitly relying on the bug. Most people want the bug fixed, and would tag the change as 1.0.1 rather than 2.0.0; but in someone's dependency tree is version 1.0.0 of your project, and upgrading to 1.0.1 will break them, even though semver tells us that ..1 is a minor change for most consumers. Incidentally, this is also why evolving the web platform is so hard. Among the uncountable sites on the internet, even if the current behavior seems totally broken, someone is shipping code that relies on it.
- everyone 9y agohttps://xkcd.com/1172/ https://xkcd.com/1172/
- ismail-s 9y agoAssuming you have documentation, then any deviation in actual behaviour from what your documentation says is a bug. If you're relying on undocumented behaviour in your programs, then you inevitably will find no/less value in semver. And if your program isn't documented, then your code implicitly fills the role of documentation, which does mean that fixing any bugs can maybe be considered a breaking change. Or something like that.
- marcosdumay 9y agoIf the behavior is the one your documentation said it would be, than it's a bugfix. If the behavior is different from what the documentation previously said it was, then it's a change. If any program that used the previous behavior is still correct, then it's a non-breaking change. Despite what you say, this is a very common thing. Adding a new function, for example, usually fits here. But that is always verified orm the point of view of documented behavior, never looking for actual behavior. If people are relying on undocumented behavior, they will have problems upgrading their libraries. They should be expecting it, if they are not, it's their problem not the library maintainer's. Wether to depend on undocumented behavior is a decision made by the library users at their discretion by weighting their opotions, it shouldn't impact upstream.
- saidajigumi 9y agoThese proposals feel like shuffling the deck chairs around on the Titanic. I get the goal of attempting to increase accountability via tweaking expectations of package managers, but that ignores the deeper problems limiting software quality that cause this whole situation in the first place. Consider this quote: Packages that wantonly broke their consumers would "gain a reputation" and get out-competed by packages that didn't [...] This quote illustrates a host of fallacious assumptions in the article: 1. That there's a large enough competitive field for any given package that one that publishes a "bad" release can be "punished" by being overtaken by competitor(s). This is weird on so many levels. The costs to switch to a competing library can often be quite high, more than just fixing the current one. Related, there's often only one viable library choice in a given domain, which implies both zero competitors and a high cost of using an alternative (whole-cloth rewrite). 2. QA is expensive: time to maintain automated tests, time to run any needed ad-hoc testing, time and experience to develop the library in a really robust way. But this proposal seeks to put in a competitive incentive that would fragment efforts to solve the same problem. So there's this assumption that the publisher is only ever just incompetent, not starved for resources to implement great software quality. A major success of open source packages is that they enable consolidation of effort (i.e. a community of contributors). 3. Sometimes a library is used in contexts that weren't foreseen by its authors, so a "breaking" change for some consumer is accidentally introduced. This may have happened before a package consumer strongly adopted it. This dashes up against an implication in the article that breakage is only ever introduced, vs. already existing. Packages are never in some pristine state of function and concept. Except for some kinds of simple packages, they're often ongoing, living exercises in understanding the real-world problem domain and applying software techniques to the solution. Overall, I think saying that modern package managers "default" to unpinned is a straw-man: no sane package manager is intended to be used this way except for a few minutes after setting up a new project. "bundle install" that first time and now you've got a Gemfile.lock. The next time, you get the same packages as the prior install. Likewise, "bundle update" is an operation that should be dirtying your source tree, requiring the usual passes of building, testing, and other software quality process before it lands. AFAICT, nothing in this proposal would ever change these steps: initial package adoption, and software change evaluation. If there are problems with upstream package quality, those root at deeper issues of "why software quality is hard" rather than being the fault of current package managers. It's difficult to see how our package managers could influence this real-world social problem at all.
- rayascott 9y ago“To begin with, it's weird that versions are strings.” - what else would they be? An audio tone at a particular frequency? A vibration pattern? It’s written communication. Perhaps he’s just confusing data types with how they are communicated. But then, you’re free to use any data type you wish. If I’m not mistaken, Ubuntu & Jetbrains both use date fields in their versioning. Still communicated as a readable string. He proposes tuples, but doesn’t care for semver. So what would the tuple mean? As a human race, I’m sure we can do better with versioning semantics. Perhaps I’ll put some more spare personal time into that semantic schema kit I was designing. Ultimately it’s about data types, containment and meaning. And his proposal ignores most of that.
- akkartik 9y agoAuthor here. I said tuples right after that sentence you quoted, and then I showed that tuples too could be dropped. So it's unclear to me why you feel the need for snark, and it's making it hard to understand you. Can you elaborate on what I am ignoring?
- rayascott 9y agoCommenter here. Firstly my comments are designed to highlight confusion in your argument, or with your understanding of versioning as a whole. With regard to version identifiers, they aren’t simply strings, they are complex data types made up of iterable data types. Even a custom enumeration that holds values such as “alpha”, “beta”, “rc”, isnt just a string, but an enumerated type, meant to convey MEANING. The fact that they are serialised into a format that happens to be readable and parsable, is besides the point. You write “Let's just make them a tuple. Instead of "3.0.2", we'll say "(3, 0, 2)".” Why bother? You’ll end up treating them the exact same way. Or does the tuple have some structure that is different from the containment found in semver? Semver is a heirarchy. And what would it mean? Absolutely nothing if you were hoping to understand the difference between 2 versions simply marked like “Product-1 21” the “Product-2 13”. If you think every open source developer’s life is going get easier with strings that hold no meaning, you are sorely mistaken. I’m truly baffled by your reasoning. Others have commented, and I agree, after 20 years in the industry, pinning is vital. The Go community isn’t wrong. The request to pin is borne of experience. You are ignoring the fact that working with multiple dependency’s that are produced by coders you have no control over isn’t all rainbows & unicorns.
- jwilk 9y ago> All software comes with a version Nitpicking, but this is not true. Lots of software is never formally released, and therefore doesn't have any version. Sometimes such software is useful enough to make its way into a software distribution, in which case distro maintainer has to invent a version number for it. Ugh.
- everyone 9y agoPeople using semantic versioning when its totally unnecessary is a peeve of mine. Maybe if youre releasing an api or something it makes sense (I dunno, I dont do that) I make games and just number my releases / versions 1,2,3,4,5,6 etc.
- kibwen 9y agoYou can also do this with semantic versioning; I have indeed seen people who, when forced to use semver, just do 1.0.0, 2.0.0, 3.0.0, 4.0.0... It's completely valid, because you, as the package author, are given ultimate freedom by semver in determining when major version bumps are appropriate.
- pornel 9y agoIt's not as bad as the article makes it sound. Package managers like npm5/yarn and Cargo have lock files that pin exact versions by default, for all dependencies, recursively. If you keep that file (commit it), your project will be immune to unexpected breaking updates. Semver-based package managers also don't just naively always upgrade to the very latest version like the article suggests. The default behavior is to limit upgrades to semver-compatible versions. There are tools like greenkeeper which use your project's unit test to try out packages before upgrading, adding extra level of assurance. While none of this is a perfect guarantee, it's IMHO working well enough.
- kilroy123 9y agoAgreed, but we've noticed some weird issues at work lately. When someone is on an older version of npm or on a different system, (linux / mac os) things get fucky with the lock file. The lock file is changed and they just blindly commit it. So kinks still need to be ironed out with npm, IMO.
- sambe 9y agoBugs exist. That isn’t a design problem. Neither is carelessness. I don’t think either are the thrust of the article.
- akkartik 9y agoI wasn't aware of greenkeeper, thanks. Can you give some examples of "Semver-based package managers"? I'm curious what you think of the two posts I was basing mine on: "Spec-ulation" by Rich Hickey: https://www.youtube.com/watch?v=oyLBGkS5ICk https://www.youtube.com/watch?v=oyLBGkS5ICk "Volatile Software" by Steve Losh: http://stevelosh.com/blog/2012/04/volatile-software http://stevelosh.com/blog/2012/04/volatile-software I think there may partly be a "universes colliding" effect here, and partly just the future being non-uniformly distributed.
- hobofan 9y agoBased on pornels' example, "Semver-based package managers" are most modern ones (npm, bundler, cargo) that have the concept of Semver embedded in the version constraints for dependencies. E.g. the tilde (~) and caret (^) operators in npm[0] allow you to specify version constraints on dependencies, that allow them to be resolved/pinned to higher minor/patch versions of that package, but not to a higher major version since that will by the Semver definition contain breaking changes and those might impact your project. [0]: https://docs.npmjs.com/misc/semver#tilde-ranges-123-12-1 https://docs.npmjs.com/misc/semver#tilde-ranges-123-12-1
- egeozcan 9y agoWe should give up on reproducible builds because version numbers do not give enough information? What? Version numbers are what you expose to the real world, something which semantic versioning tries to standardize. I shouldn't have to care if you use git, hg or copy-paste-versioning internally.
- akkartik 9y agoReproducible builds are great! I was talking about the flow for upgrading at development time, not deployment. You have your versions fixed in production but want to periodically upgrade your dependencies to pick up security fixes and bugfixes. Today that process is far from smooth. OP was an attempt at thinking through how one might design an eco-system to improve things. Pinning versions in Gemfile.lock is totally fine. Pinning versions in Gemfile is a bad idea.
- muzani 9y agoThe article does a good job of highlighting some problems of versioning but I don't think the solution it proposes is any better. The format of Product A Version B.c seems to be most actionable as long as it's consistent. A could be a major shift in the product. So something like Windows 98, Windows 2000, iPhone 3 and 4, Android Gingerbread and ICS. Version B would update whenever it's unstable and possibly breaking. So a version 2.0 or 3.0 would introduce lots of new things, but new things tend to be unstable, even after extensive testing. Version .c is more stable as the number increases. So a version 4.88 is more stable than version 4.81 I don't see how more dots (e.g. version 4.81.2 of 4.81c) are actionable. As the article brings up, the problem is when the defaults highlight to a new shiny unstable version instead of a stable older one. So the defaults should point to the highest c number, just a version below B. If the latest build is Version 7.2, the stable build might be 6.214.
- taeric 9y agoVersioning is a mess of a problem area where the majority of the difficult problems that exist today are dealing with previous solutions. This is not to say there have been bad ideas. Nor that there have been nothing but bad solutions. Indeed, I'd wager most solutions were perfectly fine, in isolation. They typically don't play well with other solutions, though. :(
- rdiddly 9y agoWell look at it this way: If we all switched to speaking German, would that rid the world of bad ideas? [Granted there's some evidence that language does affect thought patterns both for good and for ill, but overall I'm going to say my answer is...] No. Someone expressing a bad idea in German is still an idiot, except he's more of a Dummkopf. So I think the problem with this piece is that the title and part of the argument seem to assert that versioning doesn't guarantee anything about package quality or "breaking-ness" (which is true). But then it goes on to assert that this other versioning system would help change that. I doubt it. Naming and versioning are just descriptors of a thing, and may not accurately describe the thing. In other words, a turd by any other name still smells like a turd. The package itself is the thing. The only way to know anything about it is to test it with your own stuff before you migrate to it. Therefore always pin the last version that works, and when you become aware of a newer version, test with it, read the changelog, make a decision. Treat it like your own software, in other words. Because you're making it part of your own software - the software you'll be held responsible for.
- drdrey 9y agoNo mention of Java, but I feel like this works reasonably well: https://github.com/nebula-plugins/gradle-dependency-lock-plugin https://github.com/nebula-plugins/gradle-dependency-lock-plu... You specify what version constraint you want for each dependency (latest.release, major.+, major.minor.+, a specific version number...). The tool does the work of resolving those constraints to specific artifact references, which you commit to your repo in the form of a dependency.lock file that gives you reproducible builds. You can then resolve new artifact versions with `gradle generateLock`, or revert it to a previous commit if there is a breakage. It's not clear to me what problem OP is trying to solve here.
- akkartik 9y agoAuthor here. Unfortunately, I seem to have failed utterly at communicating. Guess I'll have to try again at some point. Perhaps my two links at the start will be better uses of your time. Particularly http://stevelosh.com/blog/2012/04/volatile-software http://stevelosh.com/blog/2012/04/volatile-software.
- drdrey 9y agoI think I see your point, but this is where we disagree fundamentally: > the fact that manually specifying version numbers to avoid running newer code is commonplace, expected, and a “best practice” horrifies me I think version pinning is not only a "best practice", I think it is absolutely necessary. I demand reproducible builds.
- akkartik 9y agoI agree that reproducible builds are absolutely necessary. Nobody's proposing taking those away. There's a distinction here between how you pick a version in production, and what happens when you do an `npm update` or `gem update`. I'm getting the sense since my last comment that in the Java world the second flow doesn't actually exist. What is your typical workflow for updating your libraries (fixed in the Gradle lockfile) to newer versions? In the Rails world you specify your dependencies in a file called Gemfile, and running `bundle install` fixes the versions chosen for them in a file called Gemfile.lock. In this context, Steve Losh is complaining about the former. Versions in Gemfile.lock are absolutely fine. It's auto-generated after all. Versions in the manually managed Gemfile are a smell. Does this make sense? I think we have a misunderstanding rather than a fundamental disagreement. I'm actually kinda glad to hear that you had this confusion even after reading the OP (and thanks for doing so!). It makes me feel better about my own writing.
- jitl 9y agoHere’s the post’s core thesis: > At this point you could even get rid of the version altogether and just use the commit hash on the rare occasions when we need a version identifier. We're all on the internet, and we're all constantly running npm install or equivalent. Just say "Leftpad-17", it's cleaner. > And that's it. Package managers should provide no options for version pinning. > A package manager that followed such a proposal would foster an eco-system with greater care for introducing incompatibility. Packages that wantonly broke their consumers would "gain a reputation" and get out-competed by packages that didn't, rather than gaining a "temporary" pinning that serves only to perpetuate them. Pinning is absolutely crucial when building software from some sources out of your control, especially with larger teams or larger software. You cannot rely on the responsibility of others, be they 100s of upstream library authors, or 100s of other peer developers on your product. One among those people may cause a dependency to break your build, either by releasing a bad version, or foolishly adding a dependency without carefully checking its “reputation” for frequent breaking changes. In either case, without pinning, your build is non-deterministic and sometimes starts failing it’s tests without any diff. You can’t bisect that. Your only remediation is manual debugging and analysis. Work stops for N engineers because everyone’s branches are broken. I don’t think any level of community fostering is worth that kind of risk.
- deleted 9y ago[deleted]
- 0x445442 9y agoPreach it brother. I work primarily in the enterprise Java world and I'd never just blindly compile in a dependency of LATEST and keep my fingers crossed. Any version bumps in my POM dependencies block are intentional. The transitive dependencies are hard enough to deal with already without having to worry if my dependent binary footprint changed significantly from the last build.
- chiefalchemist 9y agoWithin a given team new dev always breeds bugs. Yet some persume that's somehow not true for upstream dependencies? I can't see making that assumption. I would think the best approch is "trust but verify" ANY update to a dependency. A dependency might save you some time but it's not a free pass to be irresponsible. There's not such thing as FOSS.
- eeZah7Ux 9y agoPointless article. What we need is good, language independent, tooling to automatically select which versions to use, how risky the update is going to be, what problems are being fixed. Tracking and correlating successful and failed updates centrally - call it "distributed CI". Tentatively upgrading dependencies, running tests and rolling back on error. Don't forget that even minor, bugfix releases introduce bugs or break code by actually fixing bugs that people were inadvertently relying upon. Lack of automated tools only encourages the "vendorize, ship and forget" model that is so popular.
- CMCDragonkai 9y agoThe NixOS community is building towards this with hydra, ipfs, nixpkgs.
- pjc50 9y agoThe main reason for trying to jam everything into three numbers of "semver" is that otherwise you get total freeform chaos. Just look at Debian/Ubuntu; you get things like 30~pre9-5ubuntu2 1:7.6+12ubuntu2 1.1.1-1 5.1.1alpha+20110809-3 1.5.0-1~webupd8~precise Ultimately the important things to the user are "is this version greater than this other version (implying better)", "will upgrading to this break anything" (extremely hard to answer), and "do I have to upgrade this to remain secure" (which may also break everything).
- numbsafari 9y agoI just use major+sha. Major changes mean you are breaking the API. The sha is like, what code built it (not the git sha, But a hash tree of the actual source). I disagree with the “change the name” business. Sure, if your math routines turn into string routines, you probably need a name change. But if you are sticking to generally the same problem domain, but clients can expect breakage, then keep the name. The name is important.
- weeksie 9y agoI'm not totally sold on the heavy BDSM type systems, but one of their clear advantages comes from trivially handling this problem. Elm's package manager simply won't let you publish an incompatible update without bumping the version, all handled by type check.
- Animats 9y agoThe trouble with version pinning is that, over time, technical debt builds up. Software is built using out of date versions. There are version clashes when A needs C version 1, and B needs C version 2, and A and B are needed by D. That's the key take-away item. When you pin a version, you begin to accrue technical debt. This has long been a curse of ROS, the Robot Operating System, which is a collection of vaguely related packages which speak the same interprocess protocol. Installing ROS tends to create package clashes in Ubuntu, even when using the recommended versions of everything. This gives us a sense of what version pinning does to you like after a decade. Tools for computing the total technical debt from version pinning in a package would be useful. You should be able to get a list of packages being used which have later versions, with info about how far behind you are in time and number of updates. Then at least you can audit version pinning technical debt. You can assign someone to bringing the technical debt down, updating packages to the latest version and running regression tests.
- specialist 9y agoSemver is marketing, enumerating builds is engineering. Sure, semver should be associated (pinned?) to build numbers. Invariant. Immutable. Never changing. Semver is the public face. But internally, I only care what build number someone is talking about.
- hvindin 9y agoThere is one key part of this article which I absolutely support wholeheartedly (the rest of the article is kind of moot if we understand that version management is non trivial) When a major change is made to something, as in a breaking change, where you as the developer are making the conscious decision that you will cause other people's shit to break if they keep pulling "latest" I absolutely think that the major version number should just be part of the naming scheme, not the versioning scheme. We've actually implemented a similar system where I'm currently working. For versioning APIs, if you want to bump a major version then we force developers to start working with a whole new git repository, a whole new pipeline etc. If we have to patch some defect fix back to the old version, we can just cherry pick across forks, but most of the time we want to manage the life cycle of two bits of development that no longer do the same thing as being exactly that: bits of developed code that do different things
- masklinn 9y ago> When a major change is made to something, as in a breaking change, where you as the developer are making the conscious decision that you will cause other people's shit to break And what if that decision is not conscious (breaking changes due to minor changes happen all the time), or what if the breakage is conscious but necessary to fix something considered a bug? What then?
- masklinn 9y agoChrist on a motorboat, this entire article is a poster boy for Chesterton's Fence. > To begin with, it's weird that versions are strings. Parsing versions is non-trivial. Let's just make them a tuple. Instead of "3.0.2", we'll say "(3, 0, 2)". 3.0.2 alpha 6 says what? > Next, move the major version to part of the name of a package. "Rails 5.1.4" becomes "Rails-5 (1, 4)". By following Rich Hickey's suggestion above, we also sidestep the question of what the default version should be. There's just no way to refer to a package without its major version. And maintainers, being already loath to bump the major, start bumping only the minor on breaking changes. > And that's it. Package managers should provide no options for version pinning. And now if the maintainer breaks the package (either wilfully or without noticing) the user is shit out of luck and jolly well fucked. > A package manager that followed such a proposal would foster an eco-system with greater care for introducing incompatibility. I've got this very nice bridge you may be interested in. > Packages that wantonly broke their consumers would "gain a reputation" and get out-competed by packages that didn't There is literally no reason for that to happen any more than it currently does. > The occasional unintentional breakage would necessitate people downstream cloning repositories and changing dependency URLs So pinning would still exist except you'd have to fork the project you depend on, and would forget to update it ever after? Yeah that doesn't sound like a recipe for complete disaster. > As a result, breaking changes wouldn't live so long that they gain new users. Call me back about the bridge thing, you'd love it. > if you change behavior, rename. Unless you don't care, or don't notice, then don't, and the whole edifice falls down once again.
- stesch 9y agoproject is liable to go boom at some future date I've seen so many people come and go at my last job and at other companies, I don't think anybody cares.
- imsofuture 9y agoNone of this is a good idea.
- microcolonel 9y agoAm I the only one who doesn't feel like versioning has failed in some way? Sure, there are people who violate the constraints, or don't follow any discernible convention, but in my experience they are now the minority. If you want to be absolutely anal about versioning, do your commit-based pinning all you want, just don't drag us all into it.
- throw2016 9y agoThe whole situation with language package managers like Ruby and Node make them completely inappropriate for end users and deployment. Most apps merrily pull in hundreds of dependencies. Some deps need a complete build environment, others may require specific versions that quickly lead to fragility, dependency hell and thousands of wasted hours. These kind of package managers only make sense and see maximum use in SAAS type apps or dev centric environments explaining perhaps why some devs do not realise the issue. But this just doesn't make sense for deployment.
- shurcooL 9y ago[naive wish mode] I'm really fond of how Google does versioning in their internal codebase (from the public information about it that I know). They have a monorepo containing a single version of every library. When updating something, you're supposed to take into account all the places it's being used, and avoid breakages. Tests and tooling play an important role in making it possible. I really want something like that for the open-source world, instead of having to deal with maintaining multiple versions, and dealing with the complexity of interactions between the all the permutations of different versions. [/naive wish mode]
- Conan_Kudo 9y agoAt least in the Linux world, this is part of the value that Linux distributions can offer. Distributions like Fedora and openSUSE tend to lead the pack here by actively testing updates before they're merged into the distribution repositories, and developing tooling for supporting continuous verification of everything working together. c.f. Koji+Bodhi for Fedora, Open Build Service for openSUSE, and the OpenQA instances for both distributions.
- jmull 9y agoIt looks like this article is nonsense. The author is confusing the (sometimes bad) behavior of package managers with version numbers. Version numbers signal the intent of the release, but don’t guarantee anything. Any change in a dependency might break your project so no update is safe, regardless of what the version number says. That’s why people pin version (or, at least should be why.) It’s not a problem of version numbers at all. That doesn’t make version numbers useless, though. Understanding the intent of an update is important info. It lets you estimate the cost/risk of taking an update. So the author suggests that changing versioning will somehow improve things, when what we actually want is for package managers to be conservative by default. He actually suggests that the latest version with the same major version be taken. That would work as long as... (1) none of your dependencies ever introduces bugs or accidental compatibility changes in a non-major update; and (2) all contributors update dependencies in lockstep (since any new code contribution could be dependent on a feature or behavior of a newer version of a dependency). In other words, that doesn’t work at all. What we want is for package managers to lock the dependencies by default and for package repositories to require a version update when committing a dependency update. In fact, better to take the version number out of it: lock dependencies to the commit hash and leave version numbers for what they are good for, to communicate intent.
- bakhy 9y agoWhy is parsing "1.23.4" more difficult than "(1, 23, 4)"? :)
- mcguire 9y ago"Rich Hickey pointed out last year that the convention of bumping the major version of a library to indicate incompatibility conveys no more actionable information than just changing the name of the library." Except that it's not. Sure, as far as halfway-guaranteed compatibility goes, it's true, but the name identifies the project: * Roughly speaking, the purpose of the software doesn't change with major versions. * Also, roughly speaking, the people working on the software don't change with major versions. Both of which are, arguably, more important than raw compatibility. "I recently encountered this post from 2012 by Steve Losh, pointing out that if version numbers were any good, we'd almost always be looking to use the latest version number." No, we wouldn't. When I build a piece of software, I build it with a particular version of its dependencies. Then, I test it with those versions. After I've tested it, I absolutely do not need changes in the underlying software changing how things work. Compatibility is never perfect. "In particular, Semantic Versioning is misguided, an attempt to fix something that is broken beyond repair. The correct way to practice semantic versioning is without any version strings at all, just Rich Hickey's directive: if you change behavior, rename." Semantic versioning includes a major number, a minor number, and a micro number; they have different semantics. Mapping that scheme to Java, a major number change means no compatibility is guaranteed. But minor and micro changes do make partial compatibility guarantees. Specifically, if you add a method specification to an interface, everything that implements that interface has to change to match it, but methods that accept that interface as a parameter do not need to change. If user software implements an interface, changing that interface requires a change to at least the minor number. If user software uses, but does not implement, an interface, changing that interface may require only a micro number change.
- Lazare 9y agoIt's interesting that an almost identical copy of his proposals have already been implemented in Golang, where they are 1) widely reviled 2) seen to be a failure and 3) are in the process of being abandoned. If the author is serious about the proposals, he should really do some work to figure out why they only people to have actually tried them hate them so much, and not just relegate it to an offhand comment in a footnote.
- keypusher 9y agoIt would be interesting to rethink versioning, but this proposal is not the answer.
- luord 9y agoI fail to see any actual advantage in the approach proposed by this article; it would have the exact same problems, or very similar ones, since, versioning or not, everything ultimately comes down to trusting the package's authors. Nobody can guarantee that they won't introduce breaking changes, specially by accident. Versions offer something that, at least, resembles a deterministic way to know what code one's actually shipping. On another note, I always understood that using the latest version by default was sensible. For me, it means that, in a new project, one should start with the latest version of whatever third-party packages one wants to use. Once development actually gets underway, then the latest versions at that time should be pinned.
- Phrodo_00 9y ago> RubyGems defaults to newest version available. So if you don't specify a version for a dependency and they create a breaking v3.0 No it doesn't. RubyGems allows you to say "I want to depend on the latest 2.x", and if your dependencies use semantic versioning it works pretty well. You still want to pin your dependencies versions for deploying the versions you tested, but you should be able to safely upgrade using that scheme unless your dependencies screw up.
- deathanatos 9y ago> Let's just make them a tuple. Instead of "3.0.2", we'll say "(3, 0, 2)". And how do you encode development versions? Alphas/betas/rcs? Versions are structurally more complex than a simple tuple, and they're strings b/c that's a convenient and easy representation. And you can't just tell people to not have alphas, etc. Sometimes I need to test stuff on branches, so it can't have a normal, mainline version number; I need to signal that this is a proposed, test version, etc. Ignoring the human consequences here defeats the point of having a version number. > package managers uniformly fail to provide the sane default of "give me the latest compatible version, excluding breaking changes." Cargo? NPM? (And yes, I realize the author lists this one!) As best as I can tell, the author's complaint seems to be that you have to actually notate enough information for the package manager to determine "latest compatible version, excluding breaking changes", which both Cargo and NPM are very capable of. (E.g., in Cargo, you'd need to say something like ^1.2.) But being able to pull down the absolute latest is useful in a new project; I'll often use "dep" to start, get comfortable, and then restrict that to dep = "^3.2" or whatever. > Since we always want to provide the latest version by default, the distinction between minor versions and patch levels is moot. The distinction is a human, social level communication construct. > At this point you could even get rid of the version altogether and just use the commit hash And is 9583af87a8b newer or older than ab8a61fe9ac? > And that's it. Package managers should provide no options for version pinning. I can't tell if this article is advocating that a package's name always refers to a single, unchanging version of the software (which effectively removes any notion of version handling from the package manager entirely) or if we're just putting the major in the name, and always running the latest version for that major. This latter stance seems to imply that the version does carry useful information. But not being able to pin is nuts: if some breakage is introduced, you should just screw everyone? How would reproducible builds work? > In particular, Semantic Versioning is misguided The article doesn't convince me that it understands the reasons behind semver: communication between humans as to what types of changes a potential upgrade contains. The tooling supports this so that I can pull in changes that shouldn't break the build, test them to see if that actually is correct, and then deploy them automatically, with minimal risk. Between security fixes and bug patches, I have a need to know what types of updates I'm looking at, and to ensure that the updates that aren't breaking changes get applied diligently — and that's why we have the notion of semver: so that we can all communicate this the same way, and so that our tools can understand it, and act accordingly. Now, just because semver says that 1.3 should be compatible w/ 1.2 doesn't necessarily mean it is. Mistakes happen. That's what tests are for, and it doesn't mean you need to blindly upgrade; this is why Cargo has a separate notions of the versions that the project should be compatible with, such as ^1.2 recorded in a Cargo.toml, and what versions the project is actually using, exactly, which are contained in the lock file, so that the project can be rebuilt exactly as a previous build was, but can also be trivially upgraded where and when possible. The article doesn't acknowledge the very real problems that semver tries to solve, and doesn't tell us how its proposal (whatever that is) would solve these issues. Instead, we get "if you change behavior, rename"; the very real outcome of this proposal is that either people would never apply another security patch again, and that dependency would rot, or they'd start reinventing the wheel named semver.
- DanHulton 9y agoWhat a terrible idea. If my package is named "LeftPad-17", what's to stop someone else from creating a package named "LeftPad-18", which innocent folk may assume is the latest version of LeftPad. A project name is semantically different from the version number of that code.
- CMCDragonkai 9y agoI've examined the same issue between reproducible builds vs semantic versioning while relating it to Nix. This post also shows how to use Git submodules to achieve something similar. https://matrix.ai/2016/04/04/content-addressed-dependencies-vs-semantic-versioning/ https://matrix.ai/2016/04/04/content-addressed-dependencies-...
- CMCDragonkai 9y agoNote that Dhall language has an interesting take on dependencies where each module can be a content addressed code supplied via ipfs https://github.com/dhall-lang/dhall-lang https://github.com/dhall-lang/dhall-lang
- paulddraper 9y agoIt's true that using version ranges is incredibly dangerous. My take on it: https://www.lucidchart.com/techblog/2017/03/15/package-management-stop-using-version-ranges/ https://www.lucidchart.com/techblog/2017/03/15/package-manag... I don't love lockfiles, but you need some way deterministic dependency resolution.
- Walkman 9y agoThe author seems to not understand how people work; we all make mistakes.
- realguess 9y agoIt's a time problem. Container packaging eliminates the "works on my machine" problem, which is a space problem (environment pinning). It can be used to eliminate the time problem (version pinning). SemVer is like language (e.g., English), more people speak the common language, easier to communicate. But doesn't prevent others who might speak EmojiVer to communicate. Certainly won't stop people using packages from both worlds.