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 of if 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!