Conventions in Go (a refresher)
Good conventions make reading, sharing, and talking about code much easier.
Recently, I posted a quick tip about using the Must
prefix to name a function that panics if receiving an illegal input. The reactions showed me that there is a great interest in learning and using conventions for writing code.
So here are a few Go conventions that are easy to stick with and have a great impact on readability.
Functions that panic should have “Must” in their name
If a function
- is guaranteed to succeed for valid input, and
- panics if it receives invalid input
its function name should start with “Must”.
This naming convention ensures that everyone knows that this function expects to receive valid input and panics if it doesn't.
As an example,
regexp.Regexp.MustCompile()
panics when receiving an invalid regular expression. Given that most regular expressions are designed at coding time and therefore are a static string, any syntax error is caught at the first test.
You can also find functions whose name is just “Must()”. Typically, these are wrapper functions that receive a function as input and turn the error returned by that function into a panic.
In multi-value returns, put the error value last.
If you write a function that returns one or more values plus an error value, put the error value last in the list of return values:
func No(yes bool)(bool, err) {…}
Don't use naked returns.
If you name return arguments, you don't need to use them in the return directive. Do it anyway!
// bad
func naked()(first, middle, last, email string) {
…
return
}
// good
func proper()(first, middle, last, email string) {
…
return first, middle, last, email
}
Return early
Implement error handling as if err != nil
rather than if err == nil
. The former is much more common, and using the latter can trick a reader into thinking that the if
block handles an error whereas it is the “happy path”.
So test for err != nil
and return inside this if
block if the error cannot be handled. Doing this,
- you minimize indenting, because if you used
err == nil
instead, every subsequent error check would add another level ofif
nesting - you keep the “happy path” to the left of the screen, for easy reading.
Package names
Name your packages so that the name relates to what the package proviedes. Avoid util
, helper
, common
like the plague.
Avoid stutter
If your package name is vectordb
, don't create functions like NewVectorDB
or OpenVectorDB
. Remember they will be used along with the package name: vectordb.NewVectorDB()
, vectordb.OpenVectorDB()
. Avoid this stutter! New()
or Open()
is perfectly fine.
Interfaces are Doers, and they are small
Interface names are kind of special in Go. Where other languages prepend a capital I to the name, Go chooses names ending in -er, such as Reader, Writer, or Closer.
Besides that, interfaces are also small—a single function per interface is quite common.
These two conventions ensure maximum composability.
Getters without Get
Methods that read a value should not be prepended with “Get”. For example, if your method returns a URL, name it URL()
rather than GetURL()
.
Gofmt
Finally, the convention that is probably the one with the farthest and most visible impact is “Use gofmt
.” Go's code formatter ends all debates about where to put an opening brace. As a Go proverb says, “Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.” So add Gofmt to your favorite IDE or editor if you haven't. Usually, you would add the Go Language Server gopls
that comes with gofmt
out of the box.
More
Conventions are weaker than hard rules; yet, following the various conventions in Go always pays out. You might argue that some (or all) of these conventions are “nothing but” styling rules. I would not disagree; there is a lot of overlap in the meaning of the words. In fact, you find many of the above conventions in the Google Go Style Guide, along with many other styling rules and recommendations. Worth a read!