You Can Change Public APIs 🔗

First published . Last modified .

The other night, I published a rant about how Go's internal packages feature is abused (in my view) to create software that is harder to integrate with and use in creative ways, which I find to be not in keeping with the spirit of free software/open source. I made a critical mistake though: I titled the rant "Don't Write Internal Packages in Go," which was a stupid thing to do, because I basically sensationalized the title of my own article, like a news editor might do to bait readers. In this, I completely taken away from the point of my text (which, I must admit, wasn't that good in expressing that point anyway).

This fact caused people to believe that I was making the claim that everything in your code should be exposed to outside consumers: functions, types, variables, whatever. And they were quick to angrily respond to it on Reddit. Of course, it was not my intention to make such a ridiculous claim, but I can't blame people for thinking I did (although, in my defence, it's quite clear that several of those who commented did not bother to read the article and simply reacted to my terrible title).

This, in turn, put me in a weird position where I was forced to defend claims I did not (intend to) make. Some of the comments were quite good, I should say, and shared valid insight gained by real development experience. A bunch of people were quite angry with me, because in their mind, once you exposed something in your code, you were bound to maintain it forever and could never change/remove it.

Here's what I wrote to one of the commenters:

"Exposing something publicly doesn't mean you can't change it. Reality doesn't support this claim."

To which I received responses like this:

"Oh, it does, it does:"
"That's exactly what it means."

"What? Do you have any idea how many X1, X2, XMerge, XPleaseJustUseThisAlready functions there are out there that are public and shouldn't have been made public? :) Even if you release the change with a warning and deprecation period it WILL break someone's workflow."

Related XKCD...

That's funny, I guess I must have imagined all those countless software projects that did exactly this. Microsoft must still be supporting Internet Explorer 6 to this day! The OpenSSH project never deprecated the "ssh-rsa" algorithm, and never broke countless workflows. No website ever shutoff its HTTP endpoint and moved to HTTPS only. Every REST API ever still supports every parameter that it ever supported.

The thing is, while Hyrum's Observation (no, it is not a law) and that xkcd comic represent valid insight, they are just observations. They are not governing laws; they are not necessarily true for every project; and reaching the conclusion that nothing public can be changed is simply ridiculous. In fact, removing/modifying public interfaces is an extremely common thing in the software world. And guess what? We're all still here. We made it out alive!

More than that, breaking changes are what allows software projects to evolve and improve. Do breaking changes force consumers to update their workflows? Assuming they upgrade to the version with the breaking changes, yes, but so what? We're not talking about safety-critical machinery here (I'm looking at you Tesla). And just as people in the comment section threw in my face when they thought I was an entitled asshole, these consumers are free to stick with the previous version, update their workflow, fork the project, use an alternative, or utilize the fact that "copy/paste exists."

Many software projects have and do successfully navigate the problem of changing public interfaces. Granted, it's not always easy, and it might require you to be an organized developer—something many developers struggle with—but it is routinely done. Some of the myriad of ways software projects achieve this impressive feat are:

  • Issuing deprecation notices several versions in advance.
  • Setting EOL timelines for major channels.
  • Providing/documenting alternatives.
  • Branching to a separate release channel.
  • Making the change and letting consumers deal with it.

Different projects by different developers (individuals, communities, companies, etc.) in different scenarios (open-source, commercial, hobby, etc.) will utilize whatever strategy they can utilize. If it's a commercial project, the strategy will depend on the actual contracts and guarantees given to customers.

Note that I'm not trying to advocate for the "move fast and break things" strategy here. I'm just trying to calmly and politely tell you that you're full of shit. This kind of all-or-nothing, burying your head in the sand attitude is one of the things that some days make me wish I never got into software engineering in the first place.

Which brings me back to my original rant about making your entire codebase technically private by moving all code inside the internal directory. It's true that this doesn't make your project not open-source. It's true that it's your choice what kind of software to write and which usage patterns to support (if any). It's true that you are free to ignore my thoughts about it. It's probably true that restricting consumers in such a way prevents a horde of angry consumers from opening issues in your GitHub repository when you break compatibility, because there's no compatibility to break.

It's a design choice, however, that I would never make, because it ruins what I love about writing software, and why I've been writing and using open-source software for over two decades: I love using software in creative ways, and I love it even more when people use my software in creative ways. In fact, learning how users actually use your software is extremely important for any software project, particularly those of commercial entities. That doesn't mean I expect to receive free support, nor does it mean that I will have to provide free support. It means that I get to be creative and enjoy my craft, and if a new version of something I use breaks my workflow, that's fine, I'll deal with it like I dealt with it many times before.