Golang - My love/hate relationship.
9 minute read


Bad things:

  • Go has some concepts that are not very newcomer-friendly
  • Go does not do as much “new stuff” as you might think
  • Go is not well designed
  • Go is a step back from all the stuff we learned about programming languages in the last 10 years

Good things:

  • Go uses concepts from awesome languages like Limbo and Newsqueak
  • Go is extremely well documented
  • Go is one of the few languages with awesome language-level support for parallelism and concurrency
  • Will that damn stdlib ever stop growing?!

Project Structure

This one might confuse you as a newcomer.
In my opinion it’s equally one of Go’s best features and worst weaknesses.

If you want to use the go <...> command-line tools, you are REQUIRED to organize all your code inside of a special place called GOPATH.
It’s, without doubt, a very drastic approach to forcing users into a specific code style - but in the end it kinda works.
Things like node_modules, virtualenv, RVM, Bundler, and friends will never exist in the “perfect Go world” because your imported packages are always inside of $GOPATH. If not, go get them once and you’re done. But apart from being confusing to newcomers this approach has various other problems.

More on that topic below.

Dependencies are “meh”

The problem with Go’s approach of organizing packages inside of $GOPATH is that go get does not know how version locking works - and that’s pretty hilarious given that it has bindings to the Version-Control systems GIT, SVN, Bazaar and Mercurial.

When you go get a package, it gets downloaded to $GOPATH/src/<url>. That’s it. There is no (vanilla) way to check for updates either. You have to use go get -u if you think “hey there should be an update by now” after some time.

You might have already guessed it by now but this approach raises several hard-to-debug and hard-to-replicate problems in an environment with multiple people working on multiple computers.

Imagine you were to create a really big project.
Imagine that project would be something that cannot exist without a working version of that one very important dependency called PKG. Now the author of PKG decides make a breaking change and pushes the code to GitHub.
What will happen now?
Since your GOPATH is still storing the old version of PKG everything will work just fine. The new intern in your company will have problems and errors all over the place because his GOPATH did not contain PKG yet, so he was forced to go get the newer version.

Basically the same as doing this:

So how do we solve that?

Gustavo Niemeyer decided to build gopkg.in for that.

The gopkg.in service provides versioned URLs that offer the proper metadata for redirecting the go tool onto well defined GitHub repositories.
Developers that choose to use this service are strongly encouraged to not make any backwards incompatible changes without also changing the version in the package URL. This convention improves the chances that dependent code will continue to work while depended upon packages evolve.

So, yeah. We try to hide the problems by changing the url (and thus the import path) every time we make a backwards-incompatible change.
Sounds like a bad joke right?
The sad truth is that this is the ONLY way to build somehow safe packages while keeping your project “go gettable”. To be honest: I’d rather have duplicated source files all over the place (cough like nodejs cough) instead of using this bullsh*t.

Vendoring? Vendoring!

Guess what: Between 2014 and 2015 (4 years after the first release) the Go maintainers found out that dependencies are a thing. YAY!

With the release of Go 1.5 they made it possible to create a vendor/ folder in your projects. Basically a little private GOPATH inside of your GOPATH.

Sounds too good to be true right?
Don’t worry they prepared a catch for you.

Vendoring was a huge step into the right direction, but go get still ignores versions. That means you have to check the vendor/ folder into your version control system. In case you didn’t know: That’s a bad thing. Never do that. Dependencies are not part of the “stuff you created” and thus - by convention - don’t belong into your repo.

Your only chance to get version locking right now is using third-party tools. Sadly those make your project un-“go gettable” and you will have to write some kind of “getting started” guide for developing your software.

Another problem that arises from those third-party tools is the well-known “software war”. At the time of writing there are at least six (!) completely different dependency managers available.

  • Glide
  • GB
  • GPM
  • Gom
  • Godep
  • “dep”

How do you know which one is the best?
Best logo?
Most passed tests?
Best codestyle?

Even big and successful projects like Docker decided to trash every single VCS best-practice regarding dependencies, just because there is no vanilla way to manage them without confusing contributors.

That’s pretty sad imho.

More Dependency Sadness

Yeah we’re not done yet with that topic 👀.

Imagine you’re me.
Imagine you’re working on your final project and suddenly this error pops up:

panic: interface conversion:
    interface {} is slack.MessageEvent, not slack.MessageEvent

I had to read it five times and even sent it to a few discord friends. HOW ON EARTH could A not be the same as A? It took me a few hours and some random debugging clues from Gogland, to notice that this was a project that recently switched from "go get and pray" to vendoring. Apparently the Go maintainers kept in mind that src/github.com/user/repo2 might not be the same as src/github.com/user/repo1/vendor/github.com/user/repo2.

That’s a good thing. Thanks Go maintainers!
But PLEASE write some more expressive error messages FFS.

While writing this paragraph a friend sent me this picture:

There you have it. This error seems to happen quite often and without knowing someone who faced it before, you might think that the universe simply does not want you to learn Go.
It is possible to force Go into printing the whole prefix/import-path in error messages which in turn makes this error extremely obvious and easy to solve - but nobody knows that you ever need to do that.

So @Google and @Jetbrains.
If one of you reads this: Send help.
Either fix this confusing error message, make Gogland print the whole import path in such ambiguous error popups or add an entry to golang.org/doc/faq.

Forking is “meh”

Go’s approach of using urls as your import path might seem like a nice thing at first glance, but if you happen to contribute to an open-source project things will get a bit difficult.

To contribute to a project the “08/15” GitHub user clicks “Fork” and expects that everything still works.
In Go that is not the case.
Forking the repository changes the url and thus all import paths.
(Unless the project uses vanity imports).

The only “solution” is to create the folder structure of the original project in your GOPATH and then git clone your fork inside of that folder. And yes you read that right. It means you cannot have your fork and the original program installed at the same time.



Go’s interfaces are a pretty cool language construct but it took me a while to get a grasp on them and most newcomers will probably know that feeling when skimming through the stdlib or some advanced examples.

One of the nasty functions in the stdlib is:

func ServeHTTP(res http.ResponseWriter, req *http.Request) {

Why is res passed by value, when you could use a pointer like req?
Quoting from “effective go”:

Interfaces in Go provide a way to specify the behavior of an object:
"If something can do XYZ, then it can be used here."

Interfaces do not care if one passes references or concrete objects. They only check if the requested methods are present - not more, not less.

Don’t get me wrong: That’s a cool thing but rather confusing at the start.

Optional Types / Pointers

Go supports some kind of optional typing. This approach uses the value-type or return values to determine the variable type. Imagine you’re a newcomer and look at this piece of code:

x := doStuff()
y := x

x.Foo = 1

If doStuff() returns a pointer, this example would modify y.
If it returns a value, this example would print the initial value of x.Foo.

I know, I know. This can be solved rather easy when using an IDE, but it’s kinda confusing at first glance and shows why stripping types from the declaration and implicit pointer dereferencing is a bad idea (in terms of readability).

Termination Consistency

Import statements, const declarations and variable declarations do not need trailing commas when you span them to multiple lines:

import (

const (
    One int = iota

var (
    Life int = 42

After designing those, Rob Pike probably thought:
“Wew so much convenience. Let’s add some commas…”

… because EVERY other kind of multiline statement requires you to throw commas all over the place. Also every comma-terminated statement REQUIRES trailing commas at the end.

test := []string{
    "world", // <- Don't you dare forget that comma!!1!!1!

I mean come on Go maintainers.
Explain that.
Even coffeescript has optional commas in array/object declarations.

Reflections all over the place

That’s one of the caveats of Go’s decision to implement an extremely strict type system while neglecting method overloading and proper generics. Every method that wants to be remotely generic has to use the interface{} type and reflection.

The Community 👀

(No offense)

The attitude of an enthusiastic Go developer is almost the same as the one of a JavaScript developer. Both tend to repeat specific buzzwords over and over again.

I’d like to start a drinking game with you.
Go on GitHub, enter the advanced search, filter for Go Projects.
Then take a shot every time the words “safe”, “fast” or “efficient” pop up.


I will continue to use Go. It has it’s problems, but after all it’s a nice language for a wide variety of tasks. It is a lot easier to write fast and parallel programs in Go than in most other languages, and the lack of “classical” OOP is a really nice feature too.


So should you, as a newcomer, learn Go?
I say yes.
Even though you might not like some aspects of the language and will eventually have to write a blog post about it’s bad sides (like me oO), there’s always hope. Go’s community strives to make the language better and big companies like Google support them and their mission.

You still don’t like it?
Just look at this comprehensive list of awesome features then:

  • Go is not C#
  • Go is not Java
  • Go is not JavaScript/NodeJS
  • Go is not Python

(Or to sum it up: Go is not that shitty compared to other popular languages).
For me that would be reason enough to try Go 👀.

If you like when I write words, you can fund future blogging (and other cool things) by throwing some spare money into my dev-fund.
It's very much appreciated.

comments powered by Disqus