
Go’s Major Versioning Sucks – From a Fanboy - lanecwagner
https://qvault.io/2020/09/15/gos-major-version-handling-sucks-from-a-fanboy/
======
donatj
I'm the author of the _other_ recent post that made it to the front page about
the trouble with Go's v2+ modules.

I disagree with much of this post.

> Go makes updating major versions so cumbersome that in the majority of
> cases, we have opted to just increment minor versions when we should
> increment major versions.

I disagree with this, wholeheartedly. I think Go's module system making
modules separate packages on major versions is actually _great_ once you
actually know how it works. The problem is simply that it's non-obvious and
not well understood. I think they could have made it more obvious requiring in
on v1 for starters.

Beyond that you should absolutely be tagging major versions for every breaking
change, and it _should_ be something a developer has to stop and think about.
Not something to be taken lightly, EVEN with regard to internal libraries.

You can learn how to code things preemptively so they are flexible for
additive changes and require fewer breaking changes. It's almost always worth
the effort in the long run.

To my posts point, the problem is just _people don 't_ know how it works, and
it's design makes it something you learn late into a project, when you're
tagging v2, rather than something you hit early.

FWIW, my post for anyone didn't catch it:

[https://donatstudios.com/Go-v2-Modules](https://donatstudios.com/Go-v2-Modules)

~~~
grey-area
No, that is not the problem, people are not just holding it wrong.

The problem is a mismatch between the Go team's perception of semver usage (or
perhaps how they think it should be used) and its real-world usage. Some
examples:

Major versions are used to communicate more than breaking changes.

Project identity doesn't change if a project changes, it is not a new package.

Hardly any projects use strict semver in the real world, and other package
managers allow a more flexible, looser semver.

Just what is a breaking change anyway? Real-world packages make small security
changes which break compatibility, sometimes minor changes do too, and that's
ok if breakage is very limited in scope.

In the current go modules, new major versions are not easily discoverable or
announced, and new importers will need to consult docs or know about go get
@latest to find the latest version. This could be fixed with tooling _if_ the
problem is recognised. There are possible solutions if they want to keep /v2
paths, but we shouldn't pretend there are no problems caused by this change.
It makes it easier to produce breaking changes and harder to version increment
just for new features (as most projects from linux to rails do).

Personally I think it condones breaking changes and will lead to more breaking
changes, not fewer - if you're forced to change import paths at higher
versions why not just delete that api you don't like? Within Google say there
are significant downsides to this and pressure to fix your own breakage,
outside, not so much, including in packages people are required to use, like
api client packages for popular services.

Significant breaking changes introduce real pain for consumers and in an open
ecosystem should be avoided at all costs.

~~~
Joker_vD
Okay, here's a real world example. Three months ago, stretchr/testify was
version 1.3 and supported Go 1.10. Yesterday, we tried to build some project,
and suddenly tests don't run because, you see, now testify is version 1.6 and
they support only Go 1.13 and up. That's a bloody breaking change, okay? And
it happens all the time with Go packages: they're constantly in the process of
dropping support for older Go versions without bumping major versions.

~~~
dullgiulio
And is Go so hard to upgrade for your application?

Go the language is really best in class when it comes to keeping backwards
compatibility.

~~~
Joker_vD
Well, in this particular case it was hard. The used version of one our
internal libraries didn't support Go 1.13, so we bumped its version, and that
required some more dependencies brought in and updated, and then it turned out
that the container with Go 1.13 was misconfigured so it literally had no
access to that internal library, at all (something with Gitlab deploy
keys...), so in a hurry we tried to vendor this internal library wholesale,
and then there were some problems with gomod-ed libs and those without go.mod,
and some linters had to be explicitly told to skip the 'vendor' directory
(wtf), and...

And at this point, I just stopped digging down that rabbit hole and instead
added

    
    
        TESTIFY := github.com/stretchr/testify
    

and

    
    
        mkdir -p "$(GOPATH)/src/$(TESTIFY)" ; git clone --depth 1 --branch v1.3.0 https://$(TESTIFY).git "$(GOPATH)/src/$(TESTIFY)"
    

before 'go install' in the build script, and ran it in the container with Go
1.10. _This_ change took 5 minutes to make and worked without any problems.

Isn't the one of the reasons we use semver in the first place so that after
you do some small change to the current application you're working on, you
don't suddenly find yourself having to update 2/3 of your the environment just
to compile it?

------
harikb
I think most of the frustration I have seen comes from people expecting
symmetry and uniformity in the vendor/package management solution. This is the
same way some looking at an architecture diagram expects clean straight lines
or aligned boundaries.

That said, Go team should have made it clear right in the title (for all blog
posts) that they are attempting a unique non-intuitive solution to a hard
problem of transition from 'no-versioned' world of existing packages to
'semver' and the explicit desire for force people to avoid versioning if they
can (just like the old days). There is nothing wrong with weird solutions. We
don't have to follow Rust or Ruby or npm. Their situation is different.

Of course then the protobuf or grpc team (I forget which one) went and created
a new "v2" without the "v2" suffix and that pissed a few people off. Of course
that was still within the reasoning Russ explained (it was a reimplementation
and domain part of the URL already changed, so there was no need to add v2),
and Russ probably doesn't control the particular package's team. But that is
life! People still expect perfection from opensource projects.

~~~
nemothekid
What I dislike about Go's solution, and somewhat about semver in general is
that no one makes major version changes anymore _even if_ they break backwards
compatibility.

However if I use semver internally, like it's supposed to and make regular
major changes (let's say once a month), my code repo becomes a mess. After a 2
years, I'll have folders for v1 up to v24. And the fact that I have to copy
-R/grep|sed, means that people will be less likely to properly signal that a
backwards incompatible change has occured.

Disregarding uniformity, for the reasons above I don't think their design is a
good one. It treats major version changes as a rare event.

~~~
Skinney
If you're introducing breaking changes once per month, then your library
really isn't ready.

Tell people you have an unstable library. Breaking changes _will_ happen.
Then, when you've used your library in production for a while and are
reasonably sure it wont have to change, tell people it's stable. If you make
breaking changes after that, then start with a v2 suffix.

For some reason, semver has made people stop going through the alpha, beta and
rc stages of a release, even though the point of such things is to discover
breaking changes before you're commited to maintaining the version for the
time to come.

Major version changes should be a rare event. If it's not, you're really just
shipping around a (pre)alpha library, and should be explicit about that.

~~~
barumi
> If you're introducing breaking changes once per month, then your library
> really isn't ready.

The whole software industry moved away from waterfall to agile because it's
unreasonable to assume that no requirements will emerge. I would disagree, for
example, that Python3 is not ready just because they're introduced backward
incompatible changes in some minor releases.

~~~
tick_tock_tick
I mean python is basically the prime example of how to poorly manage version
upgrades with their failure of 2 => 3.

~~~
bigiain
They were just desperate to do a better job of screwing up than Perl with it's
5 => 6 updates...

<grin>

~~~
masklinn
As we can see from the Perl ecosystem having completely moved over to the
extent that Perl 5 is now out of support while Python is still mired in the
update.

~~~
takeda
That's funny, because I see the opposite. Perl 6 was a failure that had to be
spun into a separate language and people stayed with perl 5, recently work on
perl 7 started, but as far as I know perl 5 is still the version in use,
recently FreeBSD forced me to update perl to version 5.32.

Regarding Python, I no longer see 2.7 in the wild (I have no doubt it is still
in some places) all projects I'm working on are in Python 3. AWS Lambda
supports 3.8 (which is the latest stable version), Ubuntu comes with 3
installed by default. FreeBSD set 3.7 as default, depreciated and will remove
2.7 completely by the end of this year.

Many Python 3 packages were released as python 3 only, the packages that that
previously supported 2 dropped their support. Currently running 2.7 is a
liability. Not as much that it is EOL and there are no security fixes anymore,
but also packages dropping support (which is IMO a bigger deal).

~~~
masklinn
Yes my comment was ironic.

~~~
takeda
ah, feeling dumb now :)

------
exacube
I think for a package versioning system to work, the ecosystem has to adhere
to its principles. This is true for any package system.

I think this post is an example of a package not adhereing to its principle; a
major revision implies breaking changes, so it doesn't make sense to have a
tool "automatically update" things for you, because theres no garuntee that
it'll work.

I feel like the issue is that this post doesn't totally understand how the
package versioning is meant to work in the first place; if you're changing
your struct names and it has no external effects, i don't see why you /want/
to semver, because you're exactly getting much value from semver'ing other
than the vanity of pretty numbers. Maybe I'm missing something?

Im curious if an ideal package system that would work for this particular
project, but I don't think one does.

~~~
gouggoug
Exactly this.

The whole point of semver is to address the issue of backward-incompatible
changes breaking downstream dependencies (well, among important other things).

What Go is doing is forcing you to use Semver the way it is intended to be
used; from the semver specification:

\---

Given a version number MAJOR.MINOR.PATCH, increment the:

    
    
        MAJOR version when you make incompatible API changes,
        MINOR version when you add functionality in a backwards compatible manner, and
        PATCH version when you make backwards compatible bug fixes.
    

Additional labels for pre-release and build metadata are available as
extensions to the MAJOR.MINOR.PATCH format.

\---

If you don't want to adhere to this, just cut PATCH versions and MINOR
versions.

It's an absolute terrible idea though.

As annoying as it may seem, there's a lot of benefits to using semver the way
it is intended to: it forces you, the developer, to think a little more about
how you design your code.

The small amount of productivity you lose getting used to do the proper thing
(i.e. cut MAJOR version, or spend more time designing your code so that it
more forward thinking) will pay off in the long term.

The tiny amount of productivity you gain by not doing the right thing will
come back to haunt you and make you wish you had been more rigorous.

His startup might be small right now, but if it becomes successful, their
number of internal users might explode overnight and they'll suddenly wish
they had used semver properly all along.

~~~
kortex
Personally, there is no "too small for semver". Feels like every time I've
done the lazy thing and failed to bump a major, even if it's small package Foo
written only by me used in Bar, also only by me, there comes a time a few
weeks later of digging through to figure out what broke.

When you are crunching for a deadline, the last thing you want is "oops the
tool automatically bumped all the major versions on you" when you didn't
explicitly intend to. Having a dedicated flag would be nicer than grep/sed
though.

------
dilap
The major reason to have separate import paths is to support multiple major
versions of the same module, which is important to not end up stuck in
situations where package A wants dep D1 and package B wants dep D2, and you
can't fix it.

Of course, you could handle this w/ more complexity in the package
manager/build system, but having separate import paths makes it very clear,
and is a very "Go"ish solution.

Since you're only doing this when you've got breaking changes coming in
_anyway_ , i.e., you've already got to fix the code, is it really such a big
deal to update the import paths while you're at it? It's like a 2 second
grep/replace...

That said, having Go tell you about major version upgrades available, and also
an option to fix the code to use the upgraded versions import path, are good
ideas.

~~~
toast0
> Since you're only doing this when you've got breaking changes coming in
> anyway, i.e., you've already got to fix the code

Just because the module broke compat doesn't mean you were using the things
that broke though. Maybe they dropped or changed functions that you didn't
use.

~~~
xh-dude
Then you don’t have breaking changes, though?

~~~
srtjstjsj
Breaking and Naming is a property of the whole package, not the subset one
client uses.

------
srtjstjsj
I'll never understand HN's love for arbitrary ill-informed blogposts about
topics that have already been debated at length by experts on the mailing
lists.

Writing about a complaint in a blog post and saying Tweet me is a tacit
admission that the author isn't willing to face criticism from experts and
just wants to get attention from the casual unininformed masses.

~~~
larrik
That's kind of an elitist viewpoint, though. I imagine the "experts on the
mailing lists" are largely contributors, and really only represent a certain
class of users of the language at large.

I'm not a Go user at all, but it reminds me a bit of like back when Gnome3
took a direction completely different than what I loved about Gnome2 (Apple
worship, largely), which eventually lead to projects like MATE and Cinnamon.

~~~
srtjstjsj
The go-nuts mailing list is open. Anyone can (and many do) write in to discuss
concerns.

You don't have to defer completely to the authority. But if you want to be
treated as one, you should be willing to engage and consult with them.

------
tomc1985
So basically this is a complaint about having to manually upgrade packages in
the manifest because his small company makes lots of regular, breaking
changes?

If that is the case do you have to follow rules for semver? (edit- that is
indeed what they do) Your users are all internal, its not like they are going
to rat you out to the police for agreeing to do something different
internally. It's just a couple of numbers...

I don't do go but, as described, the version system seems fair and reasonable
on paper, for a system designed to avoid suddenly breaking code that depends
on it.

------
maxmcd
Not defending the import versioning strategy, but I found it helpful to read
Russ Cox’s writeup on the topic: [https://research.swtch.com/vgo-
import](https://research.swtch.com/vgo-import)

~~~
meddlepal
Helpful? Yes, sort of. Should you have to read that to understand how the
module versioning system works? Absolutely not.

The Go module system is a debacle. It's not intuitive and it's badly broken
right now.

~~~
lacker
It does kind of suck. But it’s better than npm... or rubygems... or the python
ecosystem... hmm, what language’s package management system _doesn’t_ suck?

~~~
bpodgursky
I like Maven. Fight me.

~~~
macromagnon
My day job is on a modular monolith that uses mvn. I'm no expert but I've set
up some IT's, setup build pipeline from jenkins, configure some plugins on
various steps of the lifecycle, etc. and I guess it's ok, xml takes a lot of
space though and it gets ridiculous on big code bases.

I've been working on an android app and use gradle with the kotlin dsl. I
haven't really done any programming with the build steps but this is pretty
sexy and doesn't need three open/close tags every plugin.

    
    
      dependencies {
        implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
        implementation("com.google.android.material:material:1.2.0")
    
    

Gradle used to use groovy dsl, but with kotlin the syntax is more consistent
and auto complete is actually useful.

~~~
meddlepal
I've done a ton of work with both Maven and Gradle and I feel like eventually
everyone just ends up back to wanting Maven.

The problem with Gradle is that it's flexibility is... dangerous and without
some constraint can quickly lead to a lot of pain.

------
kissgyorgy
The author doesn't seem to understand the idea behind this, which I very much
agree with, that a new major version should be handled as a new module. You
are breaking contract with your existing users, upgrading will have
difficulties for them.

Ross Cox explains the concept in this talk from 9:20:
[https://youtu.be/F8nrpe0XWRg?t=560](https://youtu.be/F8nrpe0XWRg?t=560)

~~~
JamesSwift
You are breaking contract with _some of_ your existing users, upgrading will
have difficulties for _some of_ them.

I regularly update across major versions of packages in other languages and
don't touch a thing other than the package version. I appreciate the version
bump so I can do my due diligence to research the changes, but I just don't
use the entire API of libraries in most cases so I'm not always affected by
changes.

------
hunterloftis
HN seems to be beginning a transition I recognize from proggit: thoughtful
discussion by experts devolving into superficial critiques for the social
media masses.

Perhaps I’m yelling at clouds, but it’s disappointing to find an article that
opens with “fanboy” and closes with “suck it” making a debut on the first page
of HN.

~~~
yobert
I interpreted those phrases as just a humorous writing style.

------
juped
You're always going to have trouble if you try to "enforce" semantic
versioning, because it's a social commitment by project maintainers, not a
theorem that can't ever possibly exist about "breaking changes" (which has a
fuzzy, social definition).

The best option for projects is probably to loudly declare that you are not
using semantic versioning, and then use it anyway, on the good-faith basis
that is the only actually possible way to use it. Declaring that you don't use
it will head off trouble from people who expect it to be an impossible magic
constraint, while using it will help people make sense of your version
numbers.

~~~
JamesSwift
100% agree. Semver is a _social_ construct not a _technical_ one. There is
Semver the spec as originally designed, and Semver as it is currently
observed. Semver has become something other than it was originally intended,
likely because the idea of "breaking changes" ends up being way more
encompassing than people wanted to admit [1]. To ignore that reality is a
futile fight.

[1] -
[https://www.youtube.com/watch?v=tISy7EJQPzI](https://www.youtube.com/watch?v=tISy7EJQPzI)

------
room271
It feels like there are some inconsistencies in this article:

* we're using projects internally and don't want the hassle of rewriting import paths when making backwards-incompatible changes

* we don't want to just give up on semver and publish breaking changing on the v0/1 numbering scheme

I feel like you have to pick one. If it's the first, then it's actually a
positive thing that explicit import path rewrites are needed in my mind at
least.

------
GrigoriyMikh
I have some experience developing multi-version
modules([https://github.com/GrigoriyMikhalkin/sqlboiler-
paginate](https://github.com/GrigoriyMikhalkin/sqlboiler-paginate)). And i
quite disagree with this article.

> new copy of the entire codebase

What i have done in my module, and what i think is a common way -- is to move
common codebase to common submodule, which will be used by all other versions.

> Allow package maintainers to specify the major version simply by updating
> git tags

Wouldn't it cause problems if developers would need to maintain multiple
versions in parallel?

> command has no way to automatically update to a new major version

To be honest, i don't see why there should be auto update to new major
versions, since, usually new major version means breaking changes to API.

------
jerrysievert
this seems to be an overall google engineering problem.

go's not alone in breaking changes between minor versions, and "what worked
yesterday no longer works" problems. v8 is very similar, where a very large
breaking change can occur from one minor version to another.

even without a loose "agreement" like semver, it feels like there is no
thought to "outside the bubble".

------
deklerk
> Package-Side Problems > I also find it problematic because it breaks (in my
> mind) one of the most useful things about module names – they reflect the
> file path.

Sorry, this is incorrect: [https://golang.org/ref/mod#module-
path](https://golang.org/ref/mod#module-path). A module path describes where
to find it, starting with its _repository root_ (github.com, golang.org, etc)
and then the subdirectory in the repository that the module is defined in (if
not the root).

So, if a module lives in golang.org/username/reponame/go.mod, its module path
is likely golang.org/username/reponame. If a module lives in
golang.org/username/reponame/dirname/go.mod, its module path is likely
golang.org/username/reponame/dirname. (and so on with a /v2 folder, etc)

I mention this because it appears that OP's major gripe in "package side
problems" is that the /v2 dir "breaks" the (mis)conception that a module path
describes _only_ the repo root.

(see also: multi module repositories)

> In other words, we should only increment major versions when making breaking
> changes

No, you can increment major versions whenever you want (though it's painful to
your users). But, you _should_ increment a major version when you make a
breaking change.

> I think a simple console warning would have been a better solution than
> forcing a cumbersome updating strategy on the community.

Could you elaborate on how a console warning solves the problem of users
becoming broken when module authors make incompatible changes within a major
version?

> Another problem on the client-side is that we don’t only need to update
> go.mod, but we actually need to grep through our codebase and change each
> import statement to point to the new major version:

What if you need to use v2 and v4 of golang.org/foo/bar? How would you import
them both without one having a /v2 suffix and a /v4 suffix? Are you proposing
that users should only be able to use one major version of a library at a
time?

(I assume you are talking about a user upgrading to a new major, not the
package author bumping to a new major. If the latter, a grep and replace is
quite approachable and is shown in the blog you linked :) )

> Go makes updating major versions so cumbersome that in the majority of
> cases, we have opted to just increment minor versions when we should
> increment major versions.

I'm reminded of when the "unused imports not allowed" rule was lambasted, and
then goimports was released and the conversation was snuffed out. This
situation feels analagous.

You praise the toolchain and the good decisions in modules, but then hinge
your thesis against it on "it's cumbersome". That's a valid concern, but it's
likely that a tool that makes major version upgrades easier will resolve your
issue. A wholly different design certainly is not needed _.

Check out
[https://godoc.org/golang.org/x/exp/cmd/gorelease](https://godoc.org/golang.org/x/exp/cmd/gorelease)
for one tool that's under development aimed at helping version bumps. It
sounds like you also need something that will create the v2 branch/directory,
change all the self-referencing import paths in that branch/directory, and
change the go.mod path. That sounds like an easy tool to write - I expect
something like that should come out quite quickly if it doesn't already exist.

\-------------------

_ Side note: In my opinion, dependency management is a rat's nest of choices
that seem good at the outset and end up with terrible consequences later on.
Go modules make super well thought out decisions, and is a very very simple
design, built to last for a long time without regrets. Sometimes the right
answer is to work around a small problem with some tooling or documentation
rather than go a totally different direction that will have large, sad
consequences later.

That is: all choices have downsides, but it's good to choose the best choice
whose downsides can largely be tackled with easy solutions like tools and
docs. Decisions like "we should build a SAT solver" have sad, sad, sad
consequences that can't be tool'd and doc'd away, for example.

------
thayne
Another problem is that if a branch is used for the new version (for example
as hashicorps hcl/v2 does), then the master or main branch is no longer the
active development branch, and developers have to know to look at a different
branch. Not to mention that github search only works on the master branch.

------
BugWatch
Well, misreading the title as "God's" and clicking the link left me feeling
somewhat disappointed.

------
bigiain
Go's versioning sucks?

Welcome to the Disillusioned Fanboy Club, there's a bunch of Perl guys who
eventually gave up and moved to Python waiting at the bar with some war
stories for you... (I'm the one alternating beers and bourbons...)

------
fiatjaf
Wait, don't we have versions already and they written in go.mod and matched
with git tags or specific commits? Aren't they committed to go.sum?

Why does that have to change if you move from v1 to v2?

~~~
snuxoll
Because v2 requires you change your import paths.

------
ankurpatel
Why aren't you using Ruby?
[https://www.youtube.com/watch?v=0D3KfnbTdWw](https://www.youtube.com/watch?v=0D3KfnbTdWw)

------
MisterTea
I like how even the programming languages of today suffer the same technical
treadmill anxiety as the frameworks and libraries they are used to author.

------
02020202
i guess i am still unaware of these issues since i am still using glide.

------
m00x
Can this stop being posted? The same user posted it 12h ago.

~~~
thegeekpirate
Hey dang, you may want to look into this.

OP seems to have a history of repeatedly spamming their company's posts
[https://news.ycombinator.com/submitted?id=lanecwagner](https://news.ycombinator.com/submitted?id=lanecwagner)

~~~
mplanchard
Might be worth shooting them an email, in case dang doesn’t see this message

