4 ms·
>I agree with the sentiment that we should only increment major versions when making breaking changes, but more often than not breaking changes are really easy
by paskozdilar 5y ago
>I agree with the sentiment that we should only increment major versions when making breaking changes, but more often than not breaking changes are really easy to accommodate for.
Maybe on a small scale breaking changes don't cause issues, but in large-scale development, you simply can't afford to have a bunch of random CI/CD pipelines fail because a developer decided to "accommodate for" a backwards-incompatible change without separating it to a new import path.
>Using different paths for different major versions makes more sense in situations where we may require two different versions of the same package, you know, diamond imports and all that. This is the exception, not the rule, and it seems strange to tap dance around a problem that doesn’t exist in most codebases.
Diamond imports are not as rare as you think - it's just that you never run into it in your own code, but in the code of your dependencies. And in Go, the problem is already solved for you so you never even see the solution at work.
Let's say you use two libraries for, say, backend and frontend - libbackend and libfrontend. Let's say they both depend on some parser library, say libparser, version 1.0.
Now assume that libbackend upgrades the libparser dependency to 2.0, but the import path stays the same and your application - the one that has been successfully auto-updated on a CI/CD server for years and nobody remembers how it works anymore - breaks, and you have no idea why, compiler reports some weird API-incompatibility errors, and now you have to spend two weeks getting back into the codebase and debugging the issue, until you realize you can't really fix it, because the guy developing the language decided that diamond imports are "a problem that doesn’t exist in most codebases".
- jchw 5y agoHonestly, my only complaint about go mod major versioning is just how unintuitive versions after v1 are. While some may disagree about the tradeoffs, I think the reasoning behind it is totally solid, but there are definitely valid complaints about the exact behavior of modules being somewhat confusing. I recall confusion with trying to update a library with no tagged versions, too, for example. That said, I think people greatly undersell how well Go has done packaging. There’s, again, tradeoffs that are at least arguable; but, I hate when people say things like “Didn’t (npm|apt|cargo|…) already solve this?” Honestly, often times the answer is at best “kind of?” — a lot of these packaging and versioning problems remain without complete solutions. Meanwhile, Go has some novel design choices that set it apart. The module proxy is a slightly unfortunate tradeoff, but Go remains one of the only languages with a good story for mostly decentralized package management. The module proxy is more of a hack that gets you some of the advantages of centralization without strictly depending on it. But beneath that, it’s nice that you can pull packages from basically anywhere with several VCSes.
- fourseventy 5y agoYa I spent an hour once trying to figure out why I couldn't get a dependency to upgrade to the latest version then it finally hit me and I was like "oooooh ya that stupid major versioning path change thing". I find it very unintuitive.
- deleted 5y ago[deleted]
- tikkabhuna 5y agoIt would be great to be able to declare a dependency with an alias at the go.mod level. Rather than requiring the publisher of a dependency to update their paths you can choose. That way if a dependency does a minor upgrade that breaks your code but you also need some new functionality, you could depend on the old code in a particular package and new code in another.
- paskozdilar 5y agokardianos commented on May 23, 2018: One goal of vgo is to not make the source go files dependent on the module file. If the vgo file is removed, the worset result is a fresh build gets the latest in major version series. This proposal appears to break that principal. Source: https://github.com/golang/go/issues/25518#issuecomment-391348144 https://github.com/golang/go/issues/25518#issuecomment-39134...
- iainmerrick 5y agoIsn’t a reasonable solution there simply to wait until libfrontend has also updated to libparser 2 before updating your app’s libbackend dependency? Why would you need to auto-update libbackend on its own?
- S1ngleM4lt 5y agoIt’s not too hard to imagine in a larger code base, where maybe libfrontend is relatively stable but libbackend has active development. Now if I want to update libbackend (maybe a new feature was added, maybe there’s been a critical bug fix) I shouldn’t have to wait for the libfrontend maintainers to update their dependencies, sort out any breaking changes, and verify there were no regressions to do so. Prior to go modules this was even worse with third party packages. I’ve had cases where my app has integrations with a few google services like maps, sheets, and text-to-speech. It turns out the libraries for all of these services, understandably, also have certain dependencies in common. If you run in to the unfortunate situation where you need to update one of the google libraries, and that updated library now relies on an updated version of a dependency required by others, you’ll end up needing to update all of the other libraries. Granted it’s usually a good idea to update, but it turns a theoretical 1 hour task (update library to use new feature) in to a headache that might take several days depending on how big the changes were to the other libraries
- tsimionescu 5y ago> Granted it’s usually a good idea to update, but it turns a theoretical 1 hour task (update library to use new feature) in to a headache that might take several days depending on how big the changes were to the other libraries Well, you will anyway have to spend a few days, often. You either spend it taking a new version of several libraries, or you spend it digging through bizarre issues because your upgrade of maps broke your integration with sheets, since your older sheets was never tested with the newer shared library version and has a bug.
- tsimionescu 5y ago> Now assume that libbackend upgrades the libparser dependency to 2.0, but the import path stays the same and your application - the one that has been successfully auto-updated on a CI/CD server for years and nobody remembers how it works anymore - breaks, and you have no idea why, compiler reports some weird API-incompatibility errors, and now you have to spend two weeks getting back into the codebase and debugging the issue, until you realize you can't really fix it, because the guy developing the language decided that diamond imports are "a problem that doesn’t exist in most codebases". The same scenario is very likely to happen, but much worse, if libparser was upgraded from 1.1.1 in 1.2.3 for libbackend. This will mean libfrontend, in your app, now also uses libparser 1.2.3. But, libparser 1.2.3 has a bug that affects libfrontend, but not libbackend. But of course your CI/CD won't tell you that, because Go is "smart" (and so are NPM, Cargo, etc) and instead of letting you know you have a depe dependency mismatch and can't upgrade libbbackend, they let you have fun debugging a runtime issue. Odds are good you will spend a good part of the day before even realizing that libfrontend, which says right there that it uses libparser 1.1.1, is actually being compiled with a different version because something else changed. The moral of the story: SemVer is a lie. If you want a long standing project to keep running, you pin specific dependency versions for every library, and always allocate a good chunk of time for any upgrade. "Automatic upgrades" of dependencies are a nice dream, just like code that has no bugs.
- dthul 5y agoJust want to mention that Cargo (don't know about others like NPM) handles it a bit differently. You are quite flexible in specifying the versions of your direct dependencies and you can fix major, minor or patch versions or do even more complex stuff with version ranges (even though I see the latter rarely used in practice). In addition, different versions of the same crate can coexist (but their types are considered distinct). So if I directly use libparser 1.1.1 but one of my dependencies uses libparser 1.2.3 that's totally fine. The version of my direct dependency won't silently be upgraded (assuming you fixed the minor or even the patch version when specifying that dependency).
- room271 5y agoAbsolutely agree. Speaking as someone who has recently had to upgrade multiple medium-sized Scala/Java apps. Diamond imports are absolutely a big problem. Many of these programs simply couldn't be built on newer Scala versions because of dependency conflicts and required portions to be rewritten. Incredibly painful!