One day at work, we were discussing the Go programming language in our work chatroom. At one point, I commented on a co-worker's slide, saying something along the lines of:
"I think that's like stage three in the seven stages of becoming a Go programmer."
Naturally, my co-workers wanted to know the rest of the stages, so I briefly outlined them. Here, expanded with more context, are the seven stages of becoming a Go programmer; see if you can see yourself on this pathway.
Stage 1: You believe you can make Go do object oriented programming
After your initial run on A Tour of Go, you start thinking "Now, how can I make this language behave more like an object oriented language...?" After all, you are used to that stuff. You want to make robust code. You want polymorphism.
"There has to be a way!" You say, and you find struct embedding. It allows you to cleverly delegate methods from the enclosing object to the embedded object without having to duplicate code. Great!
Of course, this is not true. Struct embedding only allows you to delegate method calls. Even if it looks like you are doing polymorphic method dispatch, the relationship is not IS-A. It's HAS-A, so the receiver of the method call is not the enclosing object: The receiver is always the embedded object to which the method call was delegated to.
You DO NOT do object oriented programming in Go. Period.
Stage 2: You believe goroutines will solve all of your problems
You were lured to Go by the promise that it will allow you to easily run concurrent code, which, it does via goroutines! All you need to do is use the go keyword, and you can make pretty much any function or method call run concurrently. It is only natural, then, that you want to maximize your code's efficiency by making as much code to run in parallel. And because you hid this fact by making your function calls to create goroutines automatically, the caller does not even need to be aware of this.
Yeah, so it might make your code a bit more complicated but look, now everything runs concurrently!
Go allows you to create millions of goroutines without sacrificing much efficiency, but you really should not use goroutines just because you can. Concurrent code is harder to maintain and debug than code that just flows in a single thread. I mean, have you given serious thought to whether your shared objects are really synchronized properly when accessed from multiple goroutines at once? Are you sure the order of execution is absolutely correct? Have you really checked if those goroutines actually exit when they're no longer needed?
Goroutines are best used only when they are necessary, and unless your requirements dictate that you do everything in memory or some such, you should never abandon the use of good old multi-process models.
And finally, try NOT to spawn goroutines behind your users' back, especially if you are writing a library. Explicit calls to use go calls usually give the user more flexibility and power.
Goroutines take you only so far. Use them only when it really makes sense.
Stage 3: You believe that instead of object oriented programming, interfaces will solve all of your problems
After being disillusioned that you cannot make your objects behave in a polymorphic manner, you suddenly realize the capabilities offered by interfaces. Interfaces allow you to describe APIs; there has to be a way to use this to write more robust code.
So now when you write libraries, you define interfaces for everything. You only export the interfaces and have private structs so that encapsulation is perrrrfect. It also should give you more flexibility on switching the underlying implementation, because now you have successfully decoupled the API from its implementation.
Interfaces do give you a lot of power, but it's not an end-all solution. It still does not provide true polymorphism in the sense of object oriented programming. You are also limited by the fact that interfaces can only define the API, and you cannot associate any data with it.
Also, although there are legitimate cases where exporting only interfaces instead of concrete structs make sense, it really should not be your default mode of operation. Interfaces are best when they are small-ish (as opposed to describing an entire list of methods defined for an object). Also, if you are not careful, you will have to either write a lot of extra code to fulfill the interface or to write code that requires a lot of type-assertions.
To make most out of using interfaces, you should only use them when you want to make certain types interchangeable.
Stage 4: You believe channels will solve all of your problems
After you spent a lot of time pondering how to bend Go to work your way, you are now looking for that missing piece that will make everything work your way. "Wait, there's still channels!"
Channels implicitly handle concurrent access correctly. You believe you should be able to solve many of the obstacles so far by cleverly using channels to handle synchronization, returning values (a la future/promises), and flow control with select statements with various channels.
Again, channels are extremely useful, but they are only as useful as their initial purpose, which is to provide a primitive to pass values between goroutines.
I'm sure you will find great many Go idioms using channels: for timeouts, blocking I/O, synchronization tricks, etc. But again, because channels are concurency constructs, abusing them will lead to more complicated, hard to debug code.
Stage 5: You now believe Go is not as powerful as people claim it to be
"Why?! Why is it so painful to write Go code? It doesn't allow me to write code the way I have been doing."
You are frustrated. No polymorphism. Concurrency is hard. Channels don't solve your problems. You don't even understand why Go exists. You feel like you've been stripped of all the nice tools and constructs that other languages provide.
You believe that more powerful tools to express abstract ideas are absolutely necessary. Go just doesn't cut it.
Go is decidedly opinionated. I come from a Perl background, and for a while I could not believe how limiting Go was. So yes, I understand if you become frustrated.
But is it because the language is really limiting, or is it because you were trying to make the language work the way you thought it should, without considering what the language authors intended you to do?
Stage 6: You realize that stages 1-5 were all just your imagination
At some point, you grudgingly decide to write Go code according to how most of the standard library is written. You also give up trying to be clever, and start writing straightforward code.
Then it comes to you: You just didn't want to accept the Go way.
Everything starts to make sense.
In all seriousness, learning Go does require some unlearning. I had to unlearn object oriented programming a bit, as well as embrace the fact that no matter how many useful tools the language may give you, writing concurrent code is just too hard for mere mortals. I also had to unlearn to use exceptions.
I did not check with Go's authors, so this is just my opinion, but I believe that the focus of the language is to make it harder for developers to write complex code. It gives you enough to write code that performs complex tasks, but by taking away certain key tools, the code you end up writing is simpler and harder to mess up.
Once I decided to accept the functionalities and constructs as they are, writing Go code became much easier, and definitely much more fun.
Stage 7: You are now at peace
You have accepted the Go way. You now write everything, including what you would've normally used Perl/Ruby/Python for, in Go. You realize if err != nil no longer bothers you. You only use goroutines and channels when you must.
You are one with the Gopher. You feel its glorious chi, and cry when you realize its mercy for allowing your to write code in such a majestic language.
Congratulations. Now you are a Go programmer.
Even though this sounds a bit tongue-in-cheek, these were actual issues that I felt or experienced when I was getting accustomed to Go. Maybe you agree, maybe you don't, but stage 6 was actually like that for me. I finally gave up trying to make Go work like I wanted it to and decided to write how Go was telling me to. It sounds silly, but after that, things really started to make sense.
Here's to hoping that new Gophers waste less time wondering how to bend the language and getting frustrated. Have fun hacking!