| 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? |