Peter Bourgon has recently presented Successful Go Program Design, 6 Years On at QCon London 2016, discussing patterns to use or anti-patterns to avoid when programming in Go. We are covering here in brief many of his suggestions addressed to Go developers.
GOPATH. Put GOPATH/bin
in the PATH environment variable to make all related binaries easily accessible. Bourgon recommends using a single global GOPATH
, which works well for most cases. Those who want to make a clear separation between their own code and external dependencies prefer creating two GOPATH
entries. There is also the option of building each project separately with gb, without having to set the environment variable.
Repository Structure. The structure of a repository depends on the project. The user may choose any structure (s)he likes if the project is private and will never be made public. If the project is open source, then it should follow the guidance for Remote Packages that can be easily fetched with go get
. Bourgon recommends creating a base directory with the main artifacts of the program, and subdirectories for helper packages, as shown in the following image:
Code format. Bourgon stresses the importance of respecting Go’s canonical code format style because makes the code more readable once used to it. In his opinion the community treats unformatted code as written by a newbie. gofmt
could be run on each save to format the code. In this context he pointed out to Go Code Review guidelines which provide a common language and practices for reviewers and owners. He also supports Andrew Gerrand’s recommendations for creating names for variables, functions, exports, etc. in Go, because “everyone will thank you if you do.”
Configuration. Bourgon advises for the configuration settings to be “explicit and well documented.” He still uses package flag
from the standard library but he wishes it was “less esoteric.” He underlines the importance of making the configuration domain explicit. Passing configuration through environment variables does not provide enough information for the user to understand the parametrization of the system, and he considers necessary to include configuration information in the help.
Package Names. Packages should be names based on what they provide rather what they contain. A package containing a HelloWorld
message should not be called common
or consts
but rather greetings
, to show what it does, not what is contained within.
Dot Import. Bourgon suggests against using “Dot Import”, the ability to prefix a package name by a dot enabling the developer to access variables defined in the respective package without mentioning the package name. This makes code less readable for someone new to it, having to figure out what package a variable belongs to. Go “always favors explicitness over implicitness.”
Flags. Bourgon does not consider a good idea to initialize flags within the init()
function but rather the main()
one because it prevents one to call on the global scope to access something that is a dependency, helping with testing.
Constructors. When calling a constructor, his advice is to inline an initialized struct
as parameter to avoid passing objects with invalid or incomplete state, as show in the following example:
foo := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
})
Usable defaults. Instead of initializing a variable of field with nil
, which requires a nil
test every time it is used, it is better to initialize the variable with a no-operation value that does nothing. For example, for an output
variable use ioutil.Discard
.
Cross-referential components. There is the case when two components contain references to each other. Constructing one of them requires constructing the other one, but the later needs the first one when constructed, as would be the case of the following two structs
:
type bar struct {
baz *baz
}
type baz struct {
bar *bar
}
Bourgon presented three variants to deal with this issue:
1. Combine. Two objects that are so related to each other could sometimes be combined into one, in this case a barbaz
struct.
2. Split. If the two components need to remain separated, then another strategy can be applied depicted in the following snippet:
type bar struct {
a *atom
monad
}
type baz struct {
atom
m *monad
}
a := &atom{...}
m := newMonad(...)
bar := newBar(a, m, ...)
baz := newBaz(a, m, ...)
3. Communication. Yet another approach to use when the previous two are not appropriate is to pass messages to each other.
type bar struct {
toBaz chan<- event
}
type baz struct {
fromBar <-chan event
}
c := make(chan event)
bar := newBar(c, ...)
baz := newBaz(c, ...)
Dependencies. One of the top tips shared by Bourgon was “Make dependencies explicit.” For example
func (f *foo) process() {
log.Printf("bar: %v", result) // ...
}
should be replaced with:
func (f *foo) process() {
f.Logger.Printf("bar: %v", result) // ...
}
log.Printf
actually calls Logger
, hiding this dependency. To make it explicit, one needs to create a Logger
in the construction phase, initialized to ioutil.Discard
if its value is nil
.
Channels. Bourgon recommends to use a mutex when sharing memory between goroutines and a channel to orchestrate goroutines.
Logging. Logging can be expensive and can become the bottleneck of an application. As a result, the advise is to log only what is absolutely necessary, information that is read by humans or consumed by machines. Log only info and debug information.
Instrumentation. Bourgon considers instrumentation cheap and recommends using Prometheus to monitor every resource.
Global State. Eliminate implicit global dependencies and global state.
Testing. Perform package testing. Design for testing by writing using a functional style which implies making dependencies explicit as parameters, avoid depending on global state, and use interfaces.
Dependency management. Copy dependencies in the project’s repository and use them when building the binaries. Bourgon suggests using gvt, vendetta, glide or gb depending on one’s needs.
Build. Use go install
instead of go build
because the former caches dependencies and places them in GOPATH/bin
making them easier to invoke.
These recommendations and others have been applied in creating Go kit, a distributed programming toolkit for building microservices.
Bourgon has used Go from 2009 at SoundCloud and Weaveworks, and has developed Roshi, a time series event database, and Go kit.
The session Successful Go Program Design, 6 Years On presented at QCon London 2016 will be made available for the public later this year.