This is a creation in Article, where the information may have evolved or changed.
Note: This article is written by Andrew Gerrand in Gophercon closing keynote
Speech on April 2014, the original address is Go for Gophers
Note: This is the video collection Watch the talk on YouTube, like the great Great Wall, need to turn over the wall inginging.
Interfaces
Interfaces: First Impressions
I used to be interested in classes and types.
Go against these:
- No inheritance
- No subtype polymorphism
- No generics
Instead, it emphasizes interfaces.
The way of Interfaces:go
Go interfaces is small.
type Stringer interface { String() string}
Stringer can print it perfectly.
Anything that implements the String is a Stringer.
A interface example
An IO. The value of Reader emits a binary data stream.
type Reader interface { Read([]byte) (int, error)}
Like a UNIX pipeline.
Implement interfaces
// ByteReader implements an io.Reader that emits a stream of its byte value.type ByteReader bytefunc (b ByteReader) Read(buf []byte) (int, error) { for i := range buf { buf[i] = byte(b) } return len(buf), nil}
Package interfaces
type LogReader struct { io.Reader}func (r LogReader) Read(b []byte) (int, error) { n, err := r.Reader.Read(b) log.Printf("read %d bytes, error: %v", n, err) return n, err}
Use a logreader to encapsulate a single bytereader
r := LogReader{ByteReader('A')}b := make([]byte, 10)r.Read(b)fmt.Printf("b: %q", b)
By encapsulating we make up the value of the interface.
Chaining interfaces
Package wrappers to build chains:
var r io.Reader = ByteReader('A')r = io.LimitReader(r, 1e6)r = LogReader{r}io.Copy(ioutil.Discard, r)
More Concise:
io.Copy(ioutil.Discard, LogReader{io.LimitReader(ByteReader('A'), 1e6)})
Complex behavior is achieved by combining small fragments.
Programming with interfaces
Interfaces separates data from behavior.
interfaces, functions can differentiate from performance:
// 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) {
io.Copy(ioutil.Discard, LogReader{io.LimitReader(ByteReader('A'), 1e6)})
Copy does not know the underlying data structure.
A bigger interface.
Sort. Interface describes an operation that requires ordering a collection.
type Interface interface { Len() int Less(i, j int) bool Swap(i, j int)}
Intslice can sort a ints slice:
type IntSlice []intfunc (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 can be sorted using Intslice a []int:
s := []int{7, 5, 3, 11, 2}sort.Sort(IntSlice(s))fmt.Println(s)
Another example of a interface
The Organ type describes a body part and it can print itself.
type Organ struct { Name string Weight Grams}func (o *Organ) String() string { return fmt.Sprintf("%v (%v)", o.Name, o.Weight) }type Grams intfunc (g Grams) String() string { return fmt.Sprintf("%dg", int(g)) }func main() { s := []*Organ{{"brain", 1340}, {"heart", 290}, {"liver", 1494}, {"pancreas", 131}, {"spleen", 162}} for _, o := range s { fmt.Println(o) }}
Sort organs
Organs type how to describe and change a organs slice.
type Organs []*Organfunc (s Organs) Len() int { return len(s) }func (s Organs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
ByName and Byweight types are sorted by embedding organs with different attributes.
type ByName struct{ Organs }func (s ByName) Less(i, j int) bool { return s.Organs[i].Name < s.Organs[j].Name }type ByWeight struct{ Organs }func (s ByWeight) Less(i, j int) bool { return s.Organs[i].Weight < s.Organs[j].Weight }
By embedding we combine the types.
To sort []*organ, use byname or byweight to encapsulate it and pass it to sort. Sort:
s := []*Organ{ {"brain", 1340}, {"heart", 290}, {"liver", 1494}, {"pancreas", 131}, {"spleen", 162}, } sort.Sort(ByWeight{s}) printOrgans("Organs by weight", s) sort.Sort(ByName{s}) printOrgans("Organs by name", s)
Another package
The Reverse function gets a sort. Interface and returns a sort using a inverted less method. Interface:
func Reverse(data sort.Interface) sort.Interface { return &reverse{data}}type reverse struct{ sort.Interface }func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i)}
To use the descending sort organs, use Reverse to combine our sort types.
sort.Sort(Reverse(ByWeight{s})) printOrgans("Organs by weight (descending)", s) sort.Sort(Reverse(ByName{s})) printOrgans("Organs by name (descending)", s)
Interfaces: Why do you do this?
They are not just very cool tricks.
This is how we structured programming in Go.
Interfaces:sigourney
Sigourney is a modular audio synthesizer that I wrote with Go.
Audio is generated by a series of Processor.
type Processor interface { Process(buffer []Sample)}
(Github.com/nf/sigourney)
Interfaces:roshi
Roshi is a time series event store written by Peter Bourgon, which provides the 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:
An elegant design of the presentation combination:
(Github.com/soundcloud/roshi)
Interfaces: Why do you do this?
Interfaces is a generic programming mechanism.
They gave Go a familiar form.
Less is more.
It's all made up.
Interfaces-through design and specification-encourages us to write composable code.
The Interfaces type is only a type.
The interface value is just a value.
For other languages, they are orthogonal.
Interfaces distinguishes data from behavior. (Classes merge them).
type HandlerFunc func(ResponseWriter, *Request)func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r)}
Interfaces: What did I learn?
Multi-thinking combination.
Doing a lot of small things is better than doing a big and complicated thing.
And: I think the small is also quite large.
When large is beneficial, some repetition is also good.
Concurrency
Concurrency: First Impressions
My first contact with concurrency is in: C, Java, and Python.
Then: Touch the event-driven model in Python and JavaScript.
When I see Go, what I see is:
"An efficient event-driven model without callbacks."
But I still have a question: "Why can't I Wait or kill a goroutine?" ”
The way of Concurrency:go
Goroutines provides concurrent execution.
Channels indicates that communication and synchronization are using separate processes.
Select makes operations on channel operations.
A concurrency example
Comparison of the two-fork tree from the Go tour is performed.
"Implement a function
func Same(t1, t2 *tree.Tree) bool
To compare the contents of two binary trees "
Walking a tree
type Tree struct { Left, Right *Tree Value int}
Traversal of a simple depth-first tree:
func Walk(t *tree.Tree) { if t.Left != nil { Walk(t.Left) } fmt.Println(t.Value) if t.Right != nil { Walk(t.Right) }}func main() { Walk(tree.New(1))}
A concurrent Walker:
func Walk(root *tree.Tree) chan int { ch := make(chan int) go func() { walk(root, ch) close(ch) }() return ch}func walk(t *tree.Tree, ch chan int) { if t.Left != nil { walk(t.Left, ch) } ch <- t.Value if t.Right != nil { walk(t.Right, ch) }}
Concurrent Walking Two trees:
func Same(t1, t2 *tree.Tree) bool { w1, w2 := Walk(t1), Walk(t2) for { v1, ok1 := <-w1 v2, ok2 := <-w2 if v1 != v2 || ok1 != ok2 { return false } if !ok1 { return true } }}func main() { fmt.Println(Same(tree.New(3), tree.New(3))) fmt.Println(Same(tree.New(1), tree.New(2)))}
Do not use channels to compare trees
func Same(t1, t2 *tree.Tree) bool { w1, w2 := Walk(t1), Walk(t2) for { v1, ok1 := w1.Next() v2, ok2 := w2.Next() if v1 != v2 || ok1 != ok2 { return false } if !ok1 { return true } }}
The Walk function has almost identical signatures:
func Walk(root *tree.Tree) *Walker {func (w *Walker) Next() (int, bool) {
(I can call Next instead of channel receive)
But the implementation is more complex:
func Walk(root *tree.Tree) *Walker { return &Walker{stack: []*frame{{t: root}}}}type Walker struct { stack []*frame}type frame struct { t *tree.Tree pc int}func (w *Walker) Next() (int, bool) { if len(w.stack) == 0 { return 0, false } // continued next slide ... f := w.stack[len(w.stack)-1] if f.pc == 0 { f.pc++ if l := f.t.Left; l != nil { w.stack = append(w.stack, &frame{t: l}) return w.Next() } } if f.pc == 1 { f.pc++ return f.t.Value, true } if f.pc == 2 { f.pc++ if r := f.t.Right; r != nil { w.stack = append(w.stack, &frame{t: r}) return w.Next() } } w.stack = w.stack[:len(w.stack)-1] return w.Next()}
Another channel version
func Walk(root *tree.Tree) chan int { ch := make(chan int) go func() { walk(root, ch) close(ch) }() return ch}func walk(t *tree.Tree, ch chan int) { if t.Left != nil { walk(t.Left, ch) } ch <- t.Value if t.Right != nil { walk(t.Right, ch) }}
But there is a problem: When inequality is found, a goroutine sent to Ch may be blocked.
Stopping early
Add a quit channel to Walker so we can stop it.
func Walk(root *tree.Tree, quit chan struct{}) chan int { ch := make(chan int) go func() { walk(root, ch, quit) close(ch) }() return ch}func walk(t *tree.Tree, ch chan int, quit chan struct{}) { if t.Left != nil { walk(t.Left, ch, quit) } select { case ch <- t.Value: case <-quit: return } if t.Right != nil { walk(t.Right, ch, quit) }}
Create a quit channel and pass it to each walker.
When same exits, any running walkers will be interrupted by closing quit.
func Same(t1, t2 *tree.Tree) bool { quit := make(chan struct{}) defer close(quit) w1, w2 := Walk(t1, quit), Walk(t2, quit) for { v1, ok1 := <-w1 v2, ok2 := <-w2 if v1 != v2 || ok1 != ok2 { return false } if !ok1 { return true } }}
Why not just kill goroutines?
Goroutines is not visible in the Go code. Can't kill it or wait.
You've built it yourself.
Here is the reason:
Once the Go code knows which thread it is running, you can get thread-locality.
Thread-locality causes the concurrency model to fail.
Concurrency:why It works
This model makes the concurrent code readable and writable.
(which makes concurrency understandable.)
Encourage decomposition of independent calculations.
The simple concurrency model makes it flexible enough.
Channels are only values that are suitable for the correct type system.
Goroutines is not visible in the Go code, which allows you to concurrency from anywhere.
Less is more.
Concurrency: What did I learn?
Concurrency is not just doing more things faster.
Write better code.
Grammar
Syntax: First Impressions
First, the grammar of Go is not rigid and lengthy.
I'm used to the convenience it offers.
For example:
- There is no getters/setters in the attribute
- No map/filter/reduce/zip.
- No optional parameters
The way of Syntax:go
Readability is better than anything else.
Providing enough syntactic sugar makes it efficient, but not too much.
Getters and Setters (or "properties")
Getters and setters make assignments and reads into function calls.
This can lead to surprising hidden behavior.
In Go, only the write (and call) method.
Control flow is not obscured.
Map/filter/reduce/zip
Map/filter/reduce/zip is very useful in Python:
a = [1, 2, 3, 4]b = map(lambda x: x+1, a)
In Go, you can only write loops.
a := []int{1, 2, 3, 4}b := make([]int, len(a))for i, x := range a { b[i] = x+1}
It's a bit verbose.
But it makes the performance characteristic more obvious.
It's easy to write code, and you get more control.
Optional parameters
The Go function has no optional parameters.
Use function changes instead:
func NewWriter(w io.Writer) *Writerfunc NewWriterLevel(w io.Writer, level int) (*Writer, error)
Or use an options struct:
func New(o *Options) (*Jar, error)type Options struct { PublicSuffixList PublicSuffixList}
Or a variable list of options.
Create small and simple things, not big and complex things.
Syntax:why It works
The language rejects complex code.
With the obvious control flow, it is very easy to get into unfamiliar code.
Instead, we create more things that make it very easy to record documents and understand them.
So the Go code is very easy to read.
(using GOFMT will make the code more readable.)
Syntax: What did I learn?
I am very smart for myself.
I appreciate the consistency, clarity and transparency of the Go code.
I sometimes lose convenience, but rarely.
Error handling
Error handling: First Impressions
I've used exceptions to handle errors before.
By comparison, the error handling model of Go is very lengthy.
I was immediately disgusted to type this:
if err != nil { return err}
The way to Error Handling:go
The Go uses built-in error interface coding errors:
type error interface { Error() string}
The value of Error is used just like any other value.
func doSomething() errorerr := doSomething()if err != nil { log.Println("An error occurred:", err)}
The code for error handling is just code.
(with a convention (OS). Error), in Go 1 is built-in.
Error handling:why It works
Error handling is introduced.
Go makes error handling as important as any other code.
Errors are just values, and they are easily incorporated into other parts of the language (interfaces, channels, etc.).
The result: the Go code processing error is correct and elegant.
We use the same language for errors.
There is no hidden control flow (throw/try/catch/finally) that improves readability.
Less is more.
Error handling: What have I learned
In order to write better code, you must consider error handling.
Exceptions makes it very easy to avoid thinking errors.
(the error should not be an exception)
Go encourages us to consider every kind of error situation.
My Go program is more robust than my other programs.
(I won't miss the mistake at all.) )
Packages
Packages: First Impressions
I found the rules of capital-letter-visibility very strange;
"Let me use my own naming scheme!" ”
I don't like one package per directory;
"Let me use my own structure!" ”
I am very disappointed with the lack of monkey patching.
The way of Packages:go
Go packages is a namespace for types, functions, variables, and constants.
Visibility
Visibility at the package level.
When they use a capital letter, the Names is exported.
package zipfunc NewReader(r io.ReaderAt, size int64) (*Reader, error) // exportedtype Reader struct { // exported File []*File // exported Comment string // exported r io.ReaderAt // unexported}func (f *File) Open() (rc io.ReadCloser, err error) // exportedfunc (f *File) findBodyOffset() (int64, error) // unexportedfunc readDirectoryHeader(f *File, r io.Reader) error // unexported
Good readability: It's easy to know if a name is part of a public interface
Good design: Couples naming decisions with interface decisions
Package structure
Packages can propagate across multiple files.
Allows sharing of private implementations and informal code organization.
The Packages file must exist in the unique directory of the package.
The path to the directory is absolutely the path of the import.
Build system lookup dependencies are independent from the source code.
"Monkey Patching"
GO prohibits modifying a package's declaration from outside the package.
But we can use global variables to implement similar behavior:
package flagvar Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) PrintDefaults()}
or register the function:
package httpfunc Handle(pattern string, handler Handler)
This gives monkey patching enough flexibility but pay attention to the terms of the author on the package.
(This depends on the initialization semantics of Go).
Packages:why They work
Loose package organization makes it easy for us to write code and refactor code.
But the package encourages programmers to consider public interfaces.
This leads to a good naming and simple interface.
Sources as the only trustworthy source, they have no makefiles to synchronize.
(This involves prompting good tools such as godoc.org and Goimports).
The predictable semantics make the package very easy to read, understand and use.
Packages: What did I learn?
The Go package teaches me to prioritize the users of my code.
(even if the user is me)
It also stopped me from doing disgusting things.
In any case, the package is accurate.
That's a good feeling.
Maybe it's part of my favorite language.
Documentation
Documentation: First Impressions
Godoc reads documents from the Go source, like Pydoc or Javadoc.
However, unlike these two, it does not support complex formats or other meta-data.
Why?
The way of Documentation:go
Godoc Note Before an exported declaration 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 annotations and displays them:
$ godoc strings Joinfunc 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 integrates the test framework to provide examples of test functions:
func ExampleJoin() { s := []string{"foo", "bar", "baz"} fmt.Println(strings.Join(s, ", ")) // Output: foo, bar, baz}
Documentation:why It works
Godoc want you to write better comments, so the source looks good:
// ValidMove reports whether the specified move is valid.func ValidMove(from, to Position) bool
Javadoc only wants to produce beautiful documents, so the source code looks ugly.
/** * 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)
(a "Validmove" grep returns the first line of the document)
Documentation: What did I learn?
Godoc taught me to write a document just like writing code.
Writing a document improved my ability to write code.
More
There are many examples here.
The most important topics:
- First, some things look strange or lacking.
- I realized that it was a design decision.
These decisions make this language-and Go code-better
Sometimes you should live with a language for a while to see it.
Lessons learned
The code is used for communication.
Speak clearly:
- Choose a good name
- Design a simple interface
- Write a precise document
- Don't be smart.
Less that is more
The new feature will weaken the existing feature.
Features increase complexity.
Complexity defeats Orthogonality. ----complexity defeats Orthogonality (is this really translated?) Seeking the great God).
Orthogonality is crucial-it facilitates the combination.
Combination is the key
Don't try to solve the problem by building a thing.
Combine simple tools and make them instead.
Well-designed interface
- Don't overdo it.
- Find the key point (Bull's Eye)
- Don't be too rough.
Simplification is difficult.
Take the time to find a simple solution.
The impact of Go on me
These lessons are everything I know.
Go helps me to recognize them.
Go makes me become a better programmer.
A message to the Gophers of any place
Let's build small, simple, beautiful things together.