A few Go idioms
Go is known for its simplicity. But that ease can sometimes lead to code that works but isn’t as readable as it could be. This post is a living document where I note Go idioms I’ve applied in production codebases to improve clarity and maintainability.
Use GOOS and GOARCH in file names for build constraints
When targeting specific operating systems or architectures, Go lets us name files like foo_linux.go
or foo_windows_amd64.go
. This naming convention acts as an implicit build constraint, meaning we don’t need to add //go:build linux
or //go:build windows && amd64
manually.
A Go source file is considered to have an implicit build constraint if its name (after stripping .go and an optional _test suffix) matches any of these patterns: *_GOOS, *_GOARCH, and *_GOOS_GOARCH (e.g., source_windows_amd64.go). This idiom is widely used in the Go standard library, e.g., sys_linux.go. Learn more about build constrains.
Use the new build constraint syntax: go:build
Go 1.17 introduced the //go:build
and prefers them over // +build
.
In old code, you might see a mix of both styles -- which can confuse readers.
To standardize your codebase, run:
1go fix -fix=buildtag ./...
This converts +build to go:build. Learn more about Go fix
Use "go test -args" to pass arguments to tests
We don't call flag.Parse() in Go test files. Instead, go test handles flag parsing for us.
If a flag isn't recognized as a testing flag (e.g., -v, -run, -count),
it is passed to the test binary. For clarity and prevent surprises of implicit argument passing, we can use -args
to pass arguments to the test binary explicitly. For example:
1var foo = flag.Int("foo", 0, "test flag")
2var count = flag.Int("count", 0, "test flag")
3
4func TestFoo(t *testing.T) {
5 assert.Equal(t, 1, *foo)
6}
7
8func TestCount(t *testing.T) {
9 assert.Equal(t, 1, *count)
10}
1# PASS: -foo is not a testing flag and thus passed to TestFoo implicitly.
2go test -run TestFoo -count=1 -v -foo=1
3
4# PASS: -foo is passed to TestFoo explicitly.
5go test -run TestFoo -count=1 -v -args -foo=1
6
7# PASS: -count is passed to TestCount explicitly.
8go test -run TestCount -count=1 -v -args -count=1
9
10# FAIL. -count is a testing flag that sets number of times to run each test.
11go test -run TestCount -count=1 -v -count=1
The "-args" is a flag for the "go test" command. If we compile the test binary and run it manually, then "-args" doesn't apply.
And testing flags must be prefixed with -test.
.
1go test -c -o hellotest
2./hellotest -test.v -test.count=2 -foo=1 -count=1
3=== RUN TestFoo
4--- PASS: TestFoo (0.00s)
5=== RUN TestCount
6--- PASS: TestCount (0.00s)
7=== RUN TestFoo
8--- PASS: TestFoo (0.00s)
9=== RUN TestCount
10--- PASS: TestCount (0.00s)
11PASS