Organizing Go code David Crawshaw crawshaw@golang.org * Packages * Go programs are made up of packages All Go source is part of a package. Every file begins with a package statement. Programs start in package main. .play organizeio/hello.go 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. * An example package: fmt // 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. * The shape of a 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) * The name of a package 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. * The testing of a package 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. * Code organization * Introducing workspaces Your 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 * Let's make a workspace 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`. * Our workspace $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. * Why prescribe file layout? 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. * Where's your workspace? It is possible to have multiple workspaces, but most people just use one. So where do you point your `GOPATH`? A common preference: .image organizeio/home.png This puts `src`, `bin`, and `pkg` directories in your home directory. (Convenient, because `$HOME/bin` is probably already in your `PATH`.) * Working with workspaces 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 $ * The Go tool's many talents $ 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`. * Dependency management * In production, versions matter. `go` `get` always fetches the latest code, even if your build breaks. .image organizeio/gogetversion.png That's fine when developing. It's not fine when releasing. We need other tools. * Versioning 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: [[http://gopkg.in][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) * Naming * Names matter 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. * Name style 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 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: .image organizeio/godoc.png * Writing doc comments 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 [[https://pkg.go.dev]]. E.g. [[https://pkg.go.dev/golang.org/x/tools/txtar]] * Questions?