blob: 30a8c73199e81b7471f9a0d1d3f52a0761239ae2 [file] [log] [blame]
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?