I hate Go, and I think it's really cool

2018/08/01

My first impression of Go was, ironically, “kinda generic”. It’s an imperative language from Google inspired by Python (a little generic) and Java (reigning king of generic languages,) so some amount of “recognition” is due. Go wears its influences proudly on its sleeve, which is a little weird, because it’s not a particularly noteworthy heritage.

The interesting thing (to me at least), is that this is all on purpose. Boring on purpose is an engineering feature, because excessively complicated languages are harder to work on, and produce harder to maintain code. They are more expressive: it’s more efficient for writing code, but maintaining intricate code can be challenging even with sufficient comments. Go is designed to be a slightly boring language that just gets the job done.

“Anecdata”

Allow me to throw a quick case study at you. I was tasked with writing a link checker: this program would run periodically against the company’s website, parse the HTML for links of all sorts, and then make sure those links return 200 or 30* (I also had to interpret redirect loops as 404s; I gave them 10 redirects before calling their bluff). A relatively simple task with nothing particularly exceptional. Additionally, because the task is IO bound, this seemed like a great opportunity to make use of Go’s lightweight threading runtime environment.

The input of the program was a list of URLs to check, and the output was a report of all the links on those pages, and whether they worked. So I needed workers to download the URLs, but a separate pool of workers to check the links. I figured using Go’s channels to distribute these links to workers was a good idea, so this is what my pipeline looked like:

urls -> [downloader/parser] -> links -> [link check] -> link:bool pairs -> report

The bracketed terms are fan-out workers in separate goroutines. Channels distributed work, and the [downloader/parser] emitted links directly into the links channel for [link check] workers to pick up and check. This had the main benefit of allowing link checks to happen while more and more links were being added to the queue. Worker startup and teardown was managed by waitgroups, because there is such a thing as too many channels.

So I had a little extra time to implement the project, and I was looking at the code that set up this pipeline, and noticed a lot of boilerplate. I had non-concurrent url “generation” (feeding an array into a channel), and non-concurrent report generation (for simplicity: I have no idea if you can just feed channels into the go templating engine.) But 2 different concurrent channel-worker-output setups that were very similar. In fact, one of the bugs I encountered was that the boilerplate for setting those up was wrong in one of those, so the workers weren’t getting fed correctly, or wouldn’t shutdown properly.

I’m no fan of boilerplate, especially boilerplate you can mess up, so I figured I’d extract the boilerplate into a language construct: a struct with methods, or something. Something like concurrent map operation over a channel, with the option of producing multiple values for each input. And I immediately hit a wall: although there were strong similarities between the setup and shutdown code for the two types of workers, they operated on different types. Go doesn’t have generics. The only way I can express a multi-output concurrent map in this way is with channels of interface{}, then requiring the mapping function to manually type-cast each item, breaking the nice compile time type checks that Go has, and opening the system up to nasty runtime panics.

What?

I was immediately struck. I’ve run into languages that weren’t quite as expressive as I hoped, but this felt extreme. This was an incredibly simple project, the abstraction I was trying to write was quite simple, and Go couldn’t even do that. I had error prone boilerplate I wanted to get rid of, and I couldn’t! I had to choose between boilerplate and runtime type errors! I was floored.

I hate go. I have yet to use a language so lacking in expressiveness. The link checker was practically a toy, and that was too much! Maybe I’ve sent too much time in more expressive languages, but I just can’t imagine writing large scale software with so little access to any kind of abstraction at all.

Ultimately, I still find Go pretty compelling, and its benefits for large scale software engineering management and maintenance are pretty interesting, but it’s just not the language for me: I hate it too much.


code language golang