Before you read this, please note: I’ve worked with people making quirky code decisions, which pretty much sums up every programmer that has worked in a team. I’ve made bad decisions, some of them today (probably). More than anything, most of the below text is meant to be lighthearted and amusing, not to point fingers.

Go, the programming language, is very simple. It’s imperative, meaning it works like you expect. It does not have many reserved keywords, so you don’t have to memorize much. There are no subtle control flow detours, like macros or decorators. Apart from channels and pointers, everything is pretty much straightforward.

So why am I writing this?

The issue with Go, in my humble opinion, is that its simplicity invites the developer to bring in their own baggage. Every developer has it, unless they are starting out with Go. And if they are starting out, they just adopt their senior’s baggage like their own.

Object Oriented (cof Java) folk will desperately try to create struct taxonomies and fail and be underwhelmed. They will attempt the next best thing and create factories and interface the whole world, because why call it Dog when you can call it DogImpl? A dog factory is a puppy mill and they are terrible things. So don’t interface it unless you have to.

Meanwhile, people accustomed to passing around functions like joints at a reggae concert will be very happy with the functional options pattern. But why stop with functional options on the struct initializer? Why not use it also to define the behavior of your object? What if you can both use method chaining and functional options at the same time?

// This dog is cursed;
dog := NewDog(
  WithDogTraits(
    WithTraitBark(
      PointerBecauseWhyNot(
        NewBarker().Loud().AsTrait(),
      ),
    ),
  ),
  WithDogAdministrativeInformation(
    WithNameFunc(func() (string, error) {
      return "Fido", nil
    }),
  ),
)

// This is a happy dog
otherDog := &Dog{
  Name: "Ludo",
  Barker: &StdBarker{},  // TODO: delete barker, turns out all dogs actually bark the same, why on earth did we interface this...
}

It’s not like you’re just obfuscating code and using a function value to initialize something that would be perfectly reasonable to just have a field directly set. Or creating a NewDog() *Dog when Dog is perfectly valid with all its zero values and no private fields. Allow dogs to be simple creatures/structs.

Then come the optimizers. The people that have traded their desk rubber ducky for a crystal ball to predict escape analysis and say that every time you make an unnecessary heap allocation, a new Ruby developer is born. Why is it that we are returning *Dog? Why are you abandoning it in the Heap, do you want to give the garbage collector something to pet? Should you be passing the Dog into the function as a value or a pointer? Is the Dog big? Small? Did you benchmark the Dog in the race track? To the optimizer, nanoseconds wasted are nanoseconds your server is never getting back, and that’s why you use a switch and not a map. Never mind that 10 lines above that, you made a call to a service that takes 100 milliseconds to respond.

How is this different from other programming languages? After all, just like in a college dorm, wether microwave, oven or kettle, everybody can make spaghetti.

The big difference is about perception. No one expects a python code base to be perfect. No one questions that generations of founders created a kernel of truth in Java 5 that hordes of archeologists painfully migrated to a somewhat bearable codebase in Java 8 (if you’re lucky). I hear all codebases in Rust are perfect though. But somehow Go code should be readable? Why would it be any different? Why would you not have to

  • Select a method call
  • Go to definition, Go to implementation
  • Scroll
  • Go to definition, Go to implementation
  • Scroll
  • Go to definition, Go to implementation
  • Scroll
  • Follow to a different microservice
  • Go to definition, Go to implementation
  • Wonder what kind of person decided to bury the truth beneath another interface

… and then open your own microservice and do the exact same thing because you realize that complexity is sometimes born out of necessity. Which is, of course, copium. In reality you just really like to look smart by making super flexible future-proof™ services, or create your own reflection based code generator that takes only 0.9ms to make a To Do App.