Organizing Go code

David Crawshaw

Packages

2

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.

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.

3

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.

4

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)

5

The name of a package

Keep package names short and meaningful.
Don't use underscores, they make package names long.

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

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.

7

Code organization

8

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
9

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.

10

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.

11

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.

12

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:

This puts src, bin, and pkg directories in your home directory.

(Convenient, because $HOME/bin is probably already in your PATH.)

13

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
$
14

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.

15

Dependency management

16

In production, versions matter.

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.

17

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: 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)
18

Naming

19

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.

20

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.

21

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:

22

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 pkg.go.dev. E.g.

pkg.go.dev/golang.org/x/tools/txtar

23

Questions?

24

Thank you

David Crawshaw

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)