blob: 55d87d02658cd76ae2239de91955bf6dab9177b0 [file] [log] [blame]
Go for gophers
GopherCon closing keynote
25 Apr 2014
Andrew Gerrand
* About me
.image go4gophers/gopherswim.jpg
I joined Google and the Go team in February 2010.
Had to re-think some of my preconceptions about programming.
Let me share what I have learned since.
* Interfaces
* Interfaces: first impressions
I used to think about classes and types.
Go resists this:
- No inheritance.
- No subtype polymorphism.
- No generics.
It instead emphasizes _interfaces_.
* Interfaces: the Go way
Go interfaces are small.
type Stringer interface {
String() string
A `Stringer` can pretty print itself.
Anything that implements `String` is a `Stringer`.
* An interface example
An `io.Reader` value emits a stream of binary data.
type Reader interface {
Read([]byte) (int, error)
Like a UNIX pipe.
* Implementing interfaces
.code go4gophers/reader.go /ByteReader/,/^}/
* Wrapping interfaces
.code go4gophers/reader.go /LogReader/,/STOP/
Wrapping a `ByteReader` with a `LogReader`:
.play go4gophers/reader.go /START/,/STOP/
By wrapping we compose interface _values_.
* Chaining interfaces
Wrapping wrappers to build chains:
.code go4gophers/chain.go /START/,/STOP/
More succinctly:
.play go4gophers/chain.go /LogReader{io/
Implement complex behavior by composing small pieces.
* Programming with interfaces
Interfaces separate data from behavior.
With interfaces, functions can operate on _behavior:_
// Copy copies from src to dst until either EOF is reached
// on src or an error occurs. It returns the number of bytes
// copied and the first error encountered while copying, if any.
func Copy(dst Writer, src Reader) (written int64, err error) {
.play go4gophers/chain.go /LogReader{io/
`Copy` can't know about the underlying data structures.
* A larger interface
`sort.Interface` describes the operations required to sort a collection:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
`IntSlice` can sort a slice of ints:
type IntSlice []int
func (p IntSlice) Len() int { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
`sort.Sort` uses can sort a `[]int` with `IntSlice`:
.play go4gophers/sort.go /START/,/STOP/
* Another interface example
The `Organ` type describes a body part and can print itself:
.play go4gophers/organs.go /type Organ/,$
* Sorting organs
The `Organs` type knows how to describe and mutate a slice of organs:
.code go4gophers/organs2.go /PART1/,/PART2/
The `ByName` and `ByWeight` types embed `Organs` to sort by different fields:
.code go4gophers/organs2.go /PART2/,/PART3/
With embedding we compose _types_.
* Sorting organs (continued)
To sort a `[]*Organ`, wrap it with `ByName` or `ByWeight` and pass it to `sort.Sort`:
.play go4gophers/organs2.go /START/,/STOP/
* Another wrapper
The `Reverse` function takes a `sort.Interface` and
returns a `sort.Interface` with an inverted `Less` method:
.code go4gophers/organs3.go /func Reverse/,$
To sort the organs in descending order, compose our sort types with `Reverse`:
.play go4gophers/organs3.go /START/,/STOP/
* Interfaces: why they work
These are not just cool tricks.
This is how we structure programs in Go.
* Interfaces: Sigourney
Sigourney is a modular audio synthesizer I wrote in Go.
.image go4gophers/sigourney.png
Audio is generated by a chain of `Processors`:
type Processor interface {
Process(buffer []Sample)
* Interfaces: Roshi
Roshi is a time-series event store written by Peter Bourgon. It provides this API:
Insert(key, timestamp, value)
Delete(key, timestamp, value)
Select(key, offset, limit) []TimestampValue
The same API is implemented by the `farm` and `cluster` parts of the system.
.image go4gophers/roshi.png
An elegant design that exhibits composition.
* Interfaces: why they work (continued)
Interfaces are _the_ generic programming mechanism.
This gives all Go code a familiar shape.
Less is more.
* Interfaces: why they work (continued)
It's all about composition.
Interfaces—by design and convention—encourage us to write composable code.
* Interfaces: why they work (continued)
Interfaces types are just types
and interface values are just values.
They are orthogonal to the rest of the language.
* Interfaces: why they work (continued)
Interfaces separate data from behavior. (Classes conflate them.)
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
* Interfaces: what I learned
Think about composition.
Better to have many small simple things than one big complex thing.
Also: what I thought of as small is pretty big.
Some repetition in the small is okay when it benefits "the large".
* Concurrency
* Concurrency: first impressions
My first exposure to concurrency was in C, Java, and Python.
Later: event-driven models in Python and JavaScript.
When I saw Go I saw:
"The performance of an event-driven model without callback hell."
But I had questions: "Why can't I wait on or kill a goroutine?"
* Concurrency: the Go way
Goroutines provide concurrent execution.
Channels express the communication and synchronization of independent processes.
Select enables computation on channel operations.
.image go4gophers/gopherflag.png
* A concurrency example
The binary tree comparison exercise from the Go Tour.
"Implement a function
func Same(t1, t2 *tree.Tree) bool
that compares the contents of two binary trees."
.image go4gophers/tree.png
* Walking a tree
type Tree struct {
Left, Right *Tree
Value int
A simple depth-first tree traversal:
.play go4gophers/tree-walk.go /func Walk/,$
* Comparing trees (1/2)
A concurrent walker:
.code go4gophers/tree-thread.go /func Walk/,/STOP/
* Comparing trees (2/2)
Walking two trees concurrently:
.play go4gophers/tree-thread.go /func Same/,$
* Comparing trees without channels (1/3)
.code go4gophers/tree-nothread.go /func Same/,/^}/
The `Walk` function has nearly the same signature:
.code go4gophers/tree-nothread.go /func Walk/
.code go4gophers/tree-nothread.go /func.+Next/
(We call `Next` instead of the channel receive.)
* Comparing trees without channels (2/3)
But the implementation is much more complex:
.code go4gophers/tree-nothread.go /func Walk/,/CUT/
* Comparing trees without channels (3/3)
.code go4gophers/tree-nothread.go /CUT/,/STOP/
* Another look at the channel version
.code go4gophers/tree-thread.go /func Walk/,/STOP/
But there's a problem: when an inequality is found,
a goroutine might be left blocked sending to `ch`.
* Stopping early
Add a `quit` channel to the walker so we can stop it mid-stride.
.code go4gophers/tree-select.go /func Walk/,/STOP/
* Stopping early (continued)
Create a `quit` channel and pass it to each walker.
By closing `quit` when the `Same` exits, any running walkers are terminated.
.code go4gophers/tree-select.go /func Same/,/^}/
* Why not just kill the goroutines?
Goroutines are invisible to Go code. They can't be killed or waited on.
You have to build that yourself.
There's a reason:
As soon as Go code knows in which thread it runs you get thread-locality.
Thread-locality defeats the concurrency model.
* Concurrency: why it works
The model makes concurrent code easy to read and write.
(Makes concurrency is *accessible*.)
This encourages the decomposition of independent computations.
* Concurrency: why it works (continued)
The simplicity of the concurrency model makes it flexible.
Channels are just values; they fit right into the type system.
Goroutines are invisible to Go code; this gives you concurrency anywhere.
Less is more.
* Concurrency: what I learned
Concurrency is not just for doing more things faster.
It's for writing better code.
* Syntax
* Syntax: first impressions
At first, Go syntax felt a bit inflexible and verbose.
It affords few of the conveniences to which I was accustomed.
For instance:
- No getters/setters on fields.
- No map/filter/reduce/zip.
- No optional arguments.
* Syntax: the Go way
Favor readability above all.
Offer enough sugar to be productive, but not too much.
* Getters and setters (or "properties")
Getters and setters turn assignments and reads into function calls.
This leads to surprising hidden behavior.
In Go, just write (and call) the methods.
The control flow cannot be obscured.
* Map/filter/reduce/zip
Map/filter/reduce/zip are useful in Python.
a = [1, 2, 3, 4]
b = map(lambda x: x+1, a)
In Go, you just write the loops.
a := []int{1, 2, 3, 4}
b := make([]int, len(a))
for i, x := range a {
b[i] = x+1
This is a little more verbose,
but makes the performance characteristics obvious.
It's easy code to write, and you get more control.
* Optional arguments
Go functions can't have optional arguments.
Instead, use variations of the function:
func NewWriter(w io.Writer) *Writer
func NewWriterLevel(w io.Writer, level int) (*Writer, error)
Or an options struct:
func New(o *Options) (*Jar, error)
type Options struct {
PublicSuffixList PublicSuffixList
Or a variadic list of options.
Create many small simple things, not one big complex thing.
* Syntax: why it works
The language resists convoluted code.
With obvious control flow, it's easy to navigate unfamiliar code.
Instead we create more small things that are easy to document and understand.
So Go code is easy to read.
(And with gofmt, it's easy to write readable code.)
* Syntax: what I learned
I was often too clever for my own good.
I appreciate the consistency, clarity, and _transparency_ of Go code.
I sometimes miss the conveniences, but rarely.
* Error handling
* Error handling: first impressions
I had previously used exceptions to handle errors.
Go's error handling model felt verbose by comparison.
I was immediately tired of typing this:
if err != nil {
return err
* Error handling: the Go way
Go codifies errors with the built-in `error` interface:
type error interface {
Error() string
Error values are used just like any other value.
func doSomething() error
err := doSomething()
if err != nil {
log.Println("An error occurred:", err)
Error handling code is just code.
(Started as a convention (`os.Error`). We made it built in for Go 1.)
* Error handling: why it works
Error handling is important.
Go makes error handling as important as any other code.
* Error handling: why it works (continued)
Errors are just values; they fit easily into the rest of the language
(interfaces, channels, and so on).
Result: Go code handles errors correctly and elegantly.
* Error handling: why it works (continued)
We use the same language for errors as everything else.
Lack of hidden control flow (throw/try/catch/finally) improves readability.
Less is more.
* Error handling: what I learned
To write good code we must think about errors.
Exceptions make it easy to avoid thinking about errors.
(Errors shouldn't be "exceptional!")
Go encourages us to consider every error condition.
My Go programs are far more robust than my programs in other languages.
I don't miss exceptions at all.
* Packages
* Packages: first impressions
I found the capital-letter-visibility rule weird;
"Let me use my own naming scheme!"
I didn't like "package per directory";
"Let me use my own structure!"
I was disappointed by lack of monkey patching.
* Packages: the Go way
Go packages are a name space for types, functions, variables, and constants.
* Visibility
Visibility is at the package level.
Names are "exported" when they begin with a capital letter.
package zip
func NewReader(r io.ReaderAt, size int64) (*Reader, error) // exported
type Reader struct { // exported
File []*File // exported
Comment string // exported
r io.ReaderAt // unexported
func (f *File) Open() (rc io.ReadCloser, err error) // exported
func (f *File) findBodyOffset() (int64, error) // unexported
func readDirectoryHeader(f *File, r io.Reader) error // unexported
Good for readability: easy to see whether a name is part of the public interface.
Good for design: couples naming decisions with interface decisions.
* Package structure
Packages can be spread across multiple files.
Permits shared private implementation and informal code organization.
Packages files must live in a directory unique to the package.
The path to that directory determines the package's import path.
The build system locates dependencies from the source alone.
* "Monkey patching"
Go forbids modifying package declarations from outside the package.
But we can get similar behavior using global variables:
package flag
var Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
Or registration functions:
package http
func Handle(pattern string, handler Handler)
This gives the flexibility of monkey patching but on the package author's terms.
(This depends on Go's initialization semantics.)
* Packages: why they work
The loose organization of packages lets us write and refactor code quickly.
But packages encourage the programmer to consider the public interface.
This leads to good names and simpler interfaces.
With the source as the single source of truth,
there are no makefiles to get out of sync.
(This design enables great tools like [[][]] and goimports.)
Predictable semantics make packages easy to read, understand, and use.
* Packages: what I learned
Go's package system taught me to prioritize the consumer of my code.
(Even if that consumer is me.)
It also stopped me from doing gross stuff.
Packages are rigid where it matters, and loose where it doesn't.
It just feels right.
Probably my favorite part of the language.
* Documentation
* Documentation: first impressions
Godoc reads documentation from Go source code, like `pydoc` or `javadoc`.
But unlike those two, it doesn't support complex formatting or other meta data.
* Documentation: the Go way
Godoc comments precede the declaration of an exported identifier:
// Join concatenates the elements of a to create a single string.
// The separator string sep is placed between elements in the resulting string.
func Join(a []string, sep string) string {
It extracts the comments and presents them:
$ godoc strings Join
func Join(a []string, sep string) string
Join concatenates the elements of a to create a single string. The
separator string sep is placed between elements in the resulting string.
Also integrated with the testing framework to provide testable example functions.
func ExampleJoin() {
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", "))
// Output: foo, bar, baz
* Documentation: the Go way (continued)
.image go4gophers/godoc.png
* Documentation: why it works
Godoc wants you to write good comments, so the source looks great:
// ValidMove reports whether the specified move is valid.
func ValidMove(from, to Position) bool
Javadoc just wants to produce pretty documentation, so the source is hideous:
* Validates a chess move.
* @param fromPos position from which a piece is being moved
* @param toPos position to which a piece is being moved
* @return true if the move is valid, otherwise false
boolean isValidMove(Position fromPos, Position toPos)
(Also a grep for `"ValidMove"` will return the first line of documentation.)
* Documentation: what I learned
Godoc taught me to write documentation _as_I_code._
Writing documentation _improves_the_code_ I write.
* More
There are many more examples.
The overriding theme:
- At first, something seemed weird or lacking.
- I realized it was a design decision.
Those decisions make the language—and Go code—better.
Sometimes you have to live with the language a while to see it.
* Lessons
* Code is communication
Be articulate:
- Choose good names.
- Design simple interfaces.
- Write precise documentation.
- Don't be too clever.
* Less is exponentially more
New features can weaken existing features.
Features multiply complexity.
Complexity defeats orthogonality.
Orthogonality is vital: it enables composition.
* Composition is key
Don't solve problems by building _a_ thing.
Instead, combine simple tools and compose them.
* Design good interfaces
.image go4gophers/gophertraining.png
.html go4gophers/gophertraining.html
* Simplicity is hard
Invest the time to find the simple solution.
* Go's effect on me
These lessons were all things I already "knew".
Go helped me internalize them.
.image go4gophers/gopherhat.jpg
Go made me a better programmer.
* A message for gophers everywhere
Let's build small, simple, and beautiful things together.
.image go4gophers/gopherswrench.jpg