BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Programming Patterns in Go

Programming Patterns in Go

This item in japanese

Bookmarks

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:

go-patterns-repo

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.

Rate this Article

Adoption
Style

BT