Go
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.