Best Practices for a New Go Developer
This article was originally published on Satish Talim's personal blog, and with his kind permission, we are sharing it here for Codeship readers.
This year I had the privilege to organize GopherConIndia 2015 and also interview a number of Gophers. Read what Gophers from across the world had to say to the question:
“What best practices are most important for a new Go developer to learn and understand?”
New Go programmers tend to trip up on the build environment and package model. That’s what I stress on “How to Write Go Code” -- it can be deceptively simple, but once you understand how it works it’s just awesome.
My more general advice to new Gophers is don’t be afraid to write code. A lot of programmers have this mindset that everything and anything should be as abstract as possible; heaven forbid we write one more keystroke than is necessary. (I have certainly been there myself.) Go sometimes makes you write a little more “in the small” as a tradeoff against systemic complexity. At first it can seem painful, but over time you start to see the benefits.
I talked about this in detail in my GopherCon Keynote.
Semantics. When learning a new language, we don’t spend enough time in understanding the semantics of the language; we often dive straight in. That’s a sure-shot recipe for some nasty surprises down the line. I think the least that a new Go developer can do is to read “Effective Go” carefully before writing any serious programs in Go.
Go comes with a nice collection of tools that help as well, like
gofmt (a must),
godoc, , etc. Some investment in a good editor setup is very useful as well.
Do not complain about something you think is missing, wrong, or stupid. You are ignorant and new to the language, no matter how much of an expert you are on programming in general. Humble yourself, bite your tongue, and study. You will realize eventually that what you first thought was worth criticizing was actually a deep work of genius.
I’ve seen a lot of criticism of Go’s “shortcomings” from people who are true experts in many languages other than Go. I can’t recall similar criticism from someone who’s worked with Go at a very deep level for a year or two.
Take the time to work with, and understand, the conventions in Go. For example, use ‘gofmt’ as-is, no matter what you think of tabs versus spaces. Learn the standard library. Learn about how packages and import paths work. Go is not a TMTOWTDI ("There’s More Than One Way To Do It") language. There is a Go way. Learn how Go does things and do it that way. You’ll appreciate the benefits soon enough.
Learn about stack versus heap and recognize that Go treats the stack differently from other languages. You’ll work with the strengths of the languages more easily if you don’t try to do C-like things (allocation on the heap, pointers to everything) and treat the stack and pass-by-value as cheap operations for most purposes. I’ve seen big performance improvements just by simplifying heap usage and replacing it with stack usage.
Go is designed to do the right thing for programs that look very straightforward, so if you feel like you’re doing something clever, it’s potentially working against both you and the runtime.
gofmt your code. Don’t get carried away with concurrency. Make your code Go-gettable and play within the existing Go ecosystem (which means don’t use relative package names, etc.) Interfaces can simplify your code, both by reducing duplication and making testing easier.
Every new Go developer should read http://talks.golang.org/2012/splash.article. It’s important to understand where the language is coming from, so you can work within that philosophy, rather than fight it.
This may be a little controversial, but my advice is to avoid, at least initially, the concurrent aspects of Go: channels and goroutines.
This may sound strange as these are one of the headline features of the language. This is not to say that the concurrent aspects of the language are too hard for new programmers to learn; they are not. However, they are easy to overuse, and Go is a language which responds well to moderation.
If you were unfortunate enough to use the go.crypto/ssh library before Han-Wen Nienhuys and Jonathan Pittman cleaned up my egregious overuse of channels, you’ll know what I mean.
Some concrete advice might be:
Understand the power of interfaces. They are one of Go’s great gifts, potentially more important than channels or goroutines.
If you are coming from another language, be it a dynamic language like Python or Ruby, or a compiled language like Java or C#, leave your OO baggage at the door. Go is an object-oriented language, but it is not a class-based language and does not support inheritance.
By removing inheritance from the language, the opportunity to practice the mantra of composition over inheritance is made manifest, and fighting it will only lead to frustration.
If you’re waiting for generic types and functions to be added to the language, my advice is to stop holding your breath and learn to love the language we have today.
With the 5th point release done, and the 6th on the way in a few months, the possibilities of a new language feature or syntax tweak are remote. The focus from here on out is tools, reliability, and performance.
First, new Gophers shouldn’t get too hung up on static typing. Go is a strongly typed language, so it’s possible to build convoluted APIs based on pedantic type distinctions, but the end results will be fragile and ugly -- just like in Java or C++. That’s not really what Go’s about.
Because Go gives us interfaces and closures, we can write much more elegant, generic APIs with a flavor similar to Ruby or Lisp. This is the direction the language naturally wants us to take. Personally I like to use the empty interface for plumbing and only pin things down to specific interfaces or concrete types where I need to for performance or correctness.
Learning to use closures effectively is also a huge win as they can be used to write very elegant code. I believe this is the main reason Rubyists seem to take so readily to Go; they’re already predisposed to think in terms of blocks. For those without any background in Lisp/Scheme, I’d suggest taking a look at a good Ruby book (such as “The Well-Grounded Rubyist” by David Black) or one of the less theoretical introductions to functional programming as an easy way to pick up this style of coding, then experiment with applying it in Go.
Another area that newly fledged Gophers should study are the various blogs about using concurrency. Go puts the emphasis on communication between tasks where many popular languages are concerned with the threads of execution within tasks. Deprogramming the hideous anti-patterns that threading encourages can take some effort, but it’s definitely worth it to be freed from the conceptual tyranny of locks and mutexes.
In terms of best practices though, I’d say the number one win is to make testing central to your development cycle. Go has an excellent tool for unit testing and benchmarking, and regardless of how you choose to use this, you’ll find your code greatly improved.
I’m old school and like to sketch out code in some detail before writing tests, but I know other Gophers who follow TDD practices. The testing tool works equally well for both approaches. Because the test harness is itself a Go program, it’s very easy to roll your own test helpers or even a full-fledged testing framework with all the power of closures, direct call-stack access, and so forth.
Also take the time to write good benchmarks. These follow a very simple idiom and provide an additional window into code design that's lacking in many testing tools. I love the benchmarking tool so much that I wrote a series of micro-benchmarks (http://github.com/feyeleanor/GoSpeed) that give some fascinating insights into how different platforms perform and are occasionally helpful when I’m deciding how best to optimize code.
The consensus seems to be that “OO” modeling using interfaces and concurrency patterns are the two most important aspects of the language to controller. However, saying that probably serves to put a lot of people off learning Go, which is a real shame. It is perfectly possible to write large volumes of really useful Go without using either of these two quite complex aspects of the language.
So my advice is, start writing Go code using the easy parts of the language, and the complicated parts will make sense to you over time.
And if you wonder what good Go programs look like, the library code written by the core team sets the gold standard. If your code is less readable (as mine often is), you need to improve.
I think what is most crucial to newcomers, and is one of the steps most people omit or overlook, is learning what is considered to be idiomatic in this new community that you are joining.
Before jumping in and starting your new projects, read other people’s code and try to understand the concepts and conventions that are used in Go and how the language has been thought out. It is very easy to attempt to reproduce ideologies coming from other languages in Go, but it will eventually lead to frustration and failure. If you want to become a good Go developer, this is the most crucial step.
Every language is different and should be considered with a fresh perspective. The point here is to try to share the same view as the language’s creators and its core community.
Another essential thing to becoming a good Go developer, in my opinion, is striving to avoid the use of third-party libraries and/or frameworks, at least while you are learning. Go’s standard library has so much to offer, and while a framework or library may seem to make things easier in the beginning, it will only complicate them in the long run!
Go is very opinionated, and this will work in your favor when it’s time to get things done. For example, I resisted the recommended workspace configuration, as described in How to Write Go Code. Don’t bother, especially in the beginning. Instead, focus on writing code and learning the
go command line tools. If you’re really deeply concerned with managing application dependencies across a team,
godep is as good as it gets today.
Also, don’t get too excited about channels. They’re awesome but easily over leveraged. Focus instead on learning how to leverage composition and interfaces to create clean and robust code.
If you’re deeply interested in concurrency, study the standard library’s usage. I have learned a ton from the
net/http package. And don’t miss out on Rob Pike’s concurrency videos. He does a great job of setting the stage on concurrency and parallelism in Go.
Go adopts a unique programming paradigm, so be prepared to change the way you design and write code. For many (most?) situations, the Go standard library is sufficient. Learn and stick to the Go conventions, especially those related to documentation, packages, and naming. Use the Go tool chain. Write tests. Use interfaces. Take version numbers seriously. Document your code for godoc.
That sounds like a lot, but after just a few days of reading and a few weeks of tinkering, you can be somewhat proficient.
Don’t try to force your previous language’s approach into Go. Each language has different values, a different world view, and you can’t just mix and match based on what you are used to.
Error handling might seem weird at first, there's a lack of high-level collection abstractions, and generics can be frustrating. But instead of trying to force your way in, try to understand why these compromises were made, try to follow the philosophy, and see where it takes you.
I see so many Rubyists wanting to implement a generic map method on collections. You can make it work, but there is a reason why the Go team chose not to implement that. And, no, it’s not that they are lazy.
Composition over inheritance. Thinking in an inheritance-based OO way will hamper your ability to write idiomatic Go code. Make things small and compose them together, instead of trying to make inheritance hierarchies, and your life will be much easier.
Embrace the interface. If you are coming from Ruby, which I was, this may be the hardest one since Ruby doesn’t have explicit interfaces. If you think about abstractions and make interfaces for them, your code will be easier to compose and test.
Not everything is an object. In Ruby, everything has to be an object, so I found myself writing a lot of classes with one method when all I needed was a function. Yes I could use lambdas for that, but who really does that in Ruby these days? I found that if I thought about my functions first and then gathered them together into an “object” later, my code fit together better.
Sau Sheong Chang
I think the fundamental ideas around structs and interfaces are probably the most useful initially. What a beginner who comes from an object-oriented programming background (like myself) should be aware of is that Go is both an object-oriented language and also not an object-oriented language.
Use the testing, race-detector, and
go vet tools at all times.
Understand the performance and space cost of features. Many Go developers tend to start using goroutines a bit too liberally. Prof. John Ousterhout has a term called “classitis”: “classes are good, so more classes must be better.”
Goroutines are good and inexpensive, but they also result in independent failure modes and increased communication complexity. They are an appropriate tool to implement concurrent behaviors, but beginners make the mistake of assuming that goroutines allow you to do more things in parallel, which is not always the case.
Go as a language makes you think about your program in an organized way. You should embrace that instead of fighting it by having your own way. Use the go tools that come along and follow the conventions instead of trying to fight them. Making your program concurrent gets easier by a huge factor, but that doesn’t mean you abuse it by making a non-concurrent program concurrent.
I’ve just seen too many people using channels and go routines for stuff they don’t need to as well as trying to create a class hierarchy by using generics. DON’T. Use what Go gives you and learn to think the “Go” way instead of trying to write Ruby or Java code as a Go translation. It has to affect your thinking.
A language that doesn’t affect the way you think about programming is not worth knowing. — Alan Perlis
Don’t try to write a Java program in Go. And lastly, always format your program with the
go fmt utility.
Some other Gophers said:
Keep functions small, don’t go overboard with long variable names, and avoid creating garbage.
Briefly, don’t code Go like you would in Java (or any other OOPS). Go has some design patterns that need to be followed for coding optimally. Learning them is essential.
Spend some time understanding named and unnamed types. They are the basis of your type interchangeability. Next, spend some time understanding the laws of reflection. It’s a great source of confusion, but if you understand the DNA of types, they will save you a lot of time.
The best advice I can give as a newbie to Go myself is just to be open minded and write Go the way it wants to be written.
Learn how to structure your projects, especially if you are building binaries that you’ll be distributing. Also avoid package namespace bloat; it just makes things more confusing.
Interfaces are a very powerful and an important concept in Go programming. You may be tempted to use reflection as an early Go programmer, but 99 percent of the time you can use an interface instead of reflection. You will be much happier you did!
Write Go like Go. Don’t try to write it like some other language. It’s not Java, it’s not Python, it’s not Ruby. It’s Go. That means use small interfaces. Return an error if something can fail. Make your code simple, not clever. Be merciless in your push for simplicity.
One good way to learn best practices is to read existing source codes and try to reflect them on your projects. I started with reading source codes of
net/httpand “martini” and it helped me a lot with code organization and dividing my codebase into separate modules. Another best practice is to write as many test cases as possible. Go has a very decent testing package, and it helps a lot if you are writing code in production.
Write small things that compose well, document what you export, don’t be clever. Code for today, don’t guess the future. Write tests for confidence and take out time to refactor. Learn what idiomatic Go looks like. The best way is to read standard Go code.
The one thing that I feel is the most important thing to know about Go is that it’s not complicated. I think coming from other languages we are often trained to think of things in complicated ways. Every time I became confused about something I was always over-thinking it, trying to do a more complicated solution than needed. You see this all throughout Go.
The number one thing to understand is that Go is a new programming language. Try and avoid writing code like you would in your current language of choice. Spend some time to learn the “Go Way” of doing things. Also, avoid using concurrency for the sake of using concurrency. Start with simple functions and types, then level up to more advanced concepts like interfaces and concurrency.
When any developer learns a new design pattern or language feature, the tendency is to use it wherever possible. Go’s certainly no different, and it comes with lots of new tricks. I’d encourage new Gophers to remember that they’re still just programming, and that many times the same tried-and-true patterns are best even in Go. Sometimes a
sync.Mutexis better than a channel. Sometimes it’s not worth the extra goroutine. The standard library is full of examples of these and other choices made well.
If you don’t take the time to learn and truly understand Go’s interfaces, you’ll be doomed to write Go code that looks like your other favorite languages. I still catch myself writing Go code that looks like C or Ruby sometimes. Embrace interfaces and channels!
Interfaces and composition. That is the real magic sauce in Go. Once you really understand those two concepts inside and out, you will begin to write some amazingly readable code.
Simplicity, simplicity, and simplicity. And concurrency. But seriously, simplicity is the most important aspect of Go. Avoid over-engineering, and rather than having huge monolithic code bases, make simple pieces that play well together.
What are your thoughts? Do post them here.
Stay up to date
We'll never share your email address and you can opt out at any time, we promise.