Organizing Go code
David Crawshaw
David Crawshaw
All Go source is part of a package.
Every file begins with a package statement.
Programs start in package main.
package main import "fmt" func main() { fmt.Println("Hello, world!") }
For very small programs, main
is the only package you need to write.
The hello world program imports package fmt
.
The function Println
is defined in the fmt package.
// Package fmt implements formatted I/O. package fmt // Println formats using the default formats for its // operands and writes to standard output. func Println(a ...interface{}) (n int, err error) { ... } func newPrinter() *pp { ... }
The Println
function is exported. It starts with an upper case
letter, which means other packages are allowed to call it.
The newPrinter
function is unexported. It starts with a lower
case letter, so it can only be used inside the fmt package.
Packages collect related code.
They can be big or small,
and may be spread across multiple files.
All the files in a package live in a single directory.
The net/http
package exports more than 100 names. (18 files)
The errors
package exports just one. (1 file)
Keep package names short and meaningful.
Don't use underscores, they make package names long.
io/ioutil
not io/util
suffixarray
not suffix_array
Don't overgeneralize. A util
package could be anything.
The name of a package is part of its type and function names.
On its own, type Buffer
is ambiguous. But users see:
buf := new(bytes.Buffer)
Choose package names carefully.
Choose good names for users.
6
Tests are distinguished by file name. Test files end in _test.go
.
package fmt import "testing" var fmtTests = []fmtTest{ {"%d", 12345, "12345"}, {"%v", 12345, "12345"}, {"%t", true, "true"}, } func TestSprintf(t *testing.T) { for _, tt := range fmtTests { if s := Sprintf(tt.fmt, tt.val); s != tt.out { t.Errorf("...") } } }
Test well.
7Your Go code is kept in a workspace.
A workspace contains many source repositories (git, hg).
The Go tool understands the layout of a workspace.
You don't need a Makefile
. The file layout is everything.
Change the file layout, change the build.
$GOPATH/ src/ github.com/user/repo/ mypkg/ mysrc1.go mysrc2.go cmd/mycmd/ main.go bin/ mycmd
mkdir /tmp/gows GOPATH=/tmp/gows
The GOPATH
environment variable tells the Go tool where your workspace is located.
go get github.com/dsymonds/fixhub/cmd/fixhub
The go
get
command fetches source repositories from the internet and places them in your workspace.
Package paths matter to the Go tool. Using "github.com/..."
means the tool knows how to fetch your repository.
go install github.com/dsymonds/fixhub/cmd/fixhub
The go install command builds a binary and places it in $GOPATH/bin/fixhub
.
$GOPATH/ bin/fixhub # installed binary pkg/darwin_amd64/ # compiled archives code.google.com/p/goauth2/oauth.a github.com/... src/ # source repositories code.google.com/p/goauth2/ .hg oauth # used by package go-github ... github.com/ golang/lint/... # used by package fixhub .git google/go-github/... # used by package fixhub .git dsymonds/fixhub/ .git client.go cmd/fixhub/fixhub.go # package main
go
get
fetched many repositories.
go
install
built a binary out of them.
Using file layout for builds means less configuration.
In fact, it means no configuration.
No Makefile
, no build.xml
.
Less time configuring means more time programming.
Everyone in the community uses the same layout.
This makes it easier to share code.
The Go tool helps build the Go community.
12It is possible to have multiple workspaces, but most people just use one.
So where do you point your GOPATH
? A common preference:
This puts src
, bin
, and pkg
directories in your home directory.
(Convenient, because $HOME/bin
is probably already in your PATH
.)
Unix eschews typing:
CDPATH=$GOPATH/src/github.com:$GOPATH/src/code.google.com/p $ cd dsymonds/fixhub /tmp/gows/src/github.com/dsymonds/fixhub $ cd goauth2 /tmp/gows/src/code.google.com/p/goauth2 $
A shell function for your ~/.profile
:
gocd () { cd `go list -f '{{.Dir}}' $1` }
This lets you move around using the Go tool's path names:
$ gocd .../lint /tmp/gows/src/github.com/golang/lint $
$ go help Go is a tool for managing Go source code. Usage: go command [arguments] The commands are:
Worth exploring! Some highlights:
build compile packages and dependencies get download and install packages and dependencies install compile and install packages and dependencies test test packages
There are more useful subcommands. Check out vet
and fmt
.
go
get
always fetches the latest code, even if your build breaks.
That's fine when developing. It's not fine when releasing.
We need other tools.
My favorite technique: vendoring.
For building binaries, import the packages you care about
into a _vendor
workspace.
GOPATH=/tmp/gows/_vendor:/tmp/gows
For building libraries, import the packages you care about
into your repository. Rename the imports to:
import "github.com/you/proj/vendor/github.com/them/lib"
Long paths, but trivial to automate. Write a Go program!
Another technique: gopkg.in, provides versioned package paths:
gopkg.in/user/pkg.v3 -> github.com/user/pkg (branch/tag v3, v3.N, or v.3.N.M)
Programs are full of names. Names have costs and benefits.
Costs: space and time
Names need to be in short term memory when reading code.
You can only fit so many. Longer names take up more space.
Benefits: information
A good name is not only a referent, it conveys information.
Use the shortest name that carries the right amount of information in its context.
Devote time to naming.
20
Use camelCase
, not_underscores
.
Local variable names should be short, typically one or two characters.
Package names are usually one lowercase word.
Global variables should have longer names.
Don't stutter.
bytes.Buffer
not bytes.ByteBuffer
zip.Reader
not zip.ZipReader
errors.New
not errors.NewError
r
not bytesReader
i
not loopIterator
Doc comments precede the declaration of an exported identifier:
// Join concatenates the elements of elem to create a single string. // The separator string sep is placed between elements in the resulting string. func Join(elem []string, sep string) string {
The godoc tool extracts such comments and presents them on the web:
Doc comments should be English sentences and paragraphs.
They use no special formatting beyond indentation for preformatted text.
Doc comments should begin with the noun they describe.
// Join concatenates… good // This function… bad
Package docs go above the package declaration:
// Package fmt… package fmt
Read the world's Go docs on pkg.go.dev. E.g.
pkg.go.dev/golang.org/x/tools/txtar
23David Crawshaw