Go Versions and the Open-Closed Principle

By: Jeremy W. Sherman. Published: . Categories: go-lang.

People aren’t happy about Go’s approach to managing software versions:

aren’t different API versions supposed to live at different import paths in Go? This works great if you have a proprietary codebase, are using a monorepo, and don’t support the sharing culture of open source. And, it doesn’t address the issue of minor or patch versions.

Hello, Open-Closed Principle

The funny thing is that Go’s official version management approach is effectively a strict reading of the open-closed principle as applied to libraries rather than classes.

The “fork it and rename it” approach was actually the way the principle was originally introduced for classes.

You want to change how a class works?

Fine, subclass it and make your changes.

Dependents can adopt MyVeryOwnFooV35 at their convenience, rather than you just stomping on the one and only MyVeryOwnFoo class in the project.

But That’s Crazy Talk!

Yeah, it didn’t much catch on in object-oriented programming, either, in spite of being enshrined in the SOLID acronym.

Apparently Gophers think it’s equally crazy for libraries (ibid):

Can you imagine that every time a library needs to increment a major version it needs to create a new repo on GitHub? Yeah, no one does that. The path for major API version is a Go thing. It’s not intuitive. Someone had to tell me. And, many Go developers just don’t do it. If they did there would be no reason for gopkg.in.

People Actually Do That

I can imagine it, and people actually do it. Check out the Creating Stable Releases section of the Collective Code Construction Contract. This is the social contract that governs development of ZeroMQ, amongst a few other projects.

Every time they want to make a stable version, they shard off a new repo for that version, with its own steward.

Thus, every time ZeroMQ needs to increment a major, or minor, or patch version, they need to fork a new repo. Mainline development continues on the main repo, and the stable release repo gets its own repo, its own maintenance patches, and its own name in the form of repo URL.

Why Don’t We Do That in OOP?

I think we don’t do this for OOP precisely because we find ourselves in the monorepo scenario that let Google avoid introducing package management into go. Most object-oriented projects live in one repo, so we can readily coordinate changes across the codebase: we don’t need to fork a new subclass, because we can just update all callers to play ball with the new version.