Skip to main content

Go

Recommendation
Updated
Moved
USE
2021-09-24

What is it

From the Go website:

  • Go is an open source programming language supported by Google
  • Easy to learn and get started with
  • Built-in concurrency and a robust standard library
  • Growing ecosystem of partners, communities, and tools

When to use it

Use Go as the primary programming language for backend services.

Look to the backend tech radar for best practices on how to production-grade Go systems and ship them to customers.

When not to use it

When there are specific requirements on integration with existing frameworks and tooling in other programming languages (such as specific machine learning and mathematical computation SDKs), consider using those languages, but also try to limit the scope and size of that system.

How to use it

Formatter

Format code with gofmt and imports with goimports.

Linter

Lint with GolangCI-Lint.

Style guide

Follow the Go core team's conventions:

For more detailed direction on achieving a consistent code style, see Dave Chene's Practical Go.

Naming conventions

Follow Andrew Gerrand's naming conventions.

The naming conventions favor short names, but also:

  • Think about context
  • Use your judgment

Remember to optimize for readability and ease of understanding. Do not optimize for brevity.

Directory layout

Follow Standard Go Project Layout, with the exception of avoiding introducing a pkg in SDK and library repositories. See this discussion.

Max 120 characters per line

The gofmt tool does not enforce line length, but for readability's sake, especially for GitHub code reviews, we aim to keep our lines below 120 characters.

Avoid package-level globals

Global variables lead to code that is harder to reason about, harder to test, and it makes an API implicitly non-thread safe.

More context:

Why are package scoped variables bad? Putting aside the problem of globally visible mutable state in a heavily concurrent language, package scoped variables are fundamentally singletons, used to smuggle state between unrelated concerns, encourage tight coupling and makes the code that relies on them hard to test.

Error handling

Generally error handling follows the recommendations in Error handling and Go.

Stay on the happy path

Optimize for readability by using early returns for error handling and other cases not on the happy path. Keep the happy path at a low indentation level.

See Mat Ryer's Medium post for more context.

// good
func foo() error {
if err := bar(); err != nil {
// off the happy path handle errors
return fmt.Errorf("foo: %w", err)
}
// on the happy path return successful results
return nil
}
// bad
func foo() error {
err := bar()
return err
}
// also bad
func foo() error {
err := bar()
if err != nil {
return fmt.Errorf("foo: %w", err)
} else {
return nil
}
}

Imports

Group imports into two groups: standard library imports and non-standard-library code.

Note that goimports will not remove single newlines between imports for us, since it cannot decide if the newline is placed there deliberately to separate the imports into two sematically different groups or if it is placed there accidentally.

// good
import (
"stdlib-pkg1"
"stdlib-pkg2"
"stdlib-pkg3"
"company-pkg1"

"thirdpartylib-pkg1"
"thirdpartylib-pkg2"
)

// bad (standard library imports are not all in the same group)
import (
"stdlib-pkg1"
"stdlib-pkg2"

"stdlib-pkg3"

"company-pkg1"
"thirdpartylib-pkg1"
"thirdpartylib-pkg2"
)

Multi-line function declarations

For both arguments and return values, place all items on individual lines.

// good
func A(
a int,
b int,
c int,
...
x int,
){
...
}

// bad
func A(
a, b int, // removing this line removes two parameters
c, d bool,
...
) {
...
}

Include the type for each name.

// bad
func A(
a,
b,
c string, // removing this line changes the type of a and b
...
x int,
){
...
}

Apply the same principle for return values, but prefer splitting parameters. Consider using a result struct instead if you find yourself splitting a line to fit many return values.

Multi-line call sites

Some arguments may in themselves exceed maximum line length, e.g. anonymous functions or inline struct initialization. To accommodate this elegantly, allow that all parameters start on the line where the previous ended.

// all good
logger.Info("Foo", zap.Any("bar", bar))

logger.Info("Foo", zap.Any(
"bar",
bar,
))

logger.Info(
"foo",
zap.Any("bar", bar),
)

logger.Info(
"Foo", zap.Any("bar", bar),
)
// all bad
logger.Info(
fmt.Sprintf(
"Foo %v",
foo,
), zap.Any("bar", bar), // removing this line doesn't compile
)

logger.Info(
"Foo",
zap.Any("bar", bar)) // removing this line doesn't compile

logger.Info("Foo",
zap.Any("bar", bar), // to remove this line, also need to compress the line above and below
)

How to learn it

Start with A Tour of Go.

Talks

  • Learn how it all started from The Go Programming Language talk by Rob Pike at Google TechTalks, 2009.

  • Learn more about the design of the Go language from The Evolution of Go talk by Robert Griesemer at GopherCon, 2015.

  • Learn more about the software design philosophy of the Go community from the Go Proverbs talk by Rob Pike at Gopherfest, 2015.

  • Learn more about one specific Go proverb in the Clear is better than clever talk by Dave Cheney at GopherCon Singapore, 2019.

  • Learn more about the Go module system from the Go with Versions talk by Russ Cox at GopherCon Singapore, 2018.

  • Learn more about testing best practices from the Advanced Testing with Go talk by Mitchell Hashimoto at GopherCon Denver, 2017.

  • Learn more about goroutines and how they are scheduled from The Scheduler Saga talk by Kavya Joshi at GopherCon, 2018.

  • Learn more about high-level best practices for writing production-grade Go code from the Best Practices for Industrial Programming talk by Peter Bourgon at GopherCon Europe, 2018.

  • Learn more about designing Go programs from SOLID Go Design by Dave Cheney at GopherCon UK, 2016.