Effective Go 摘记

Formatting

The gofmt program (also available as go fmt, which operates at the package level rather than source file level) reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments.

Commentary

Every package should have a package comment, a block comment preceding the package clause. For multi-file packages, the package comment only needs to be present in one file, and any one will do. The package comment should introduce the package and provide information relevant to the package as a whole, for example

Names

By convention, packages are given lower case, single-word names, no need for underscores or mixedCaps

Another convention is that the package name is the base name of its source directory; the package in “src/encoding/base64” is imported as “encoding/base64” but has name base64, not encoding_base64 and not encodingBase64

By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier

the convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names

Control structures

if and switch accept an optional initialization statement like that of for

1234

if err := file.Chmod(0664); err != nil { log.Print(err)return err}

In a := declaration a variable v may appear even if it has already been declared, providing that there is at least one other variable in the declaration that is being declared anew, otherwise an error no new variables on left side of := will occur

For strings, the range breaks out individual Unicode code points by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD, rune is Go terminology for a single Unicode code point, similar to char in other languages

Data

new v.s make

Go has two allocation primitives, the built-in functions new and make

New does not initialize the memory, new(T) allocates zeroed storage for a new item of type T and returns its address, that is a pointer to a newly allocated zero value of type T, it’s helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization

Sometimes the zero value isn’t good enough and an initializing constructor is necessary, as in this example derived from package os

We can simplify it using a composite literal, which is an expression that creates a new instance each time it is evaluated(File{fd, name, nil, 0} in the following code). If a composite literal contains no fields at all, it creates a zero value for the type. The expressions new(File) and &File{} are equivalent.

If a function takes a slice argument, modification of elements of the slice will be visible to the caller, but append elements won’t, if you want to append elements to slice in function, pass the address instead

slices are variable-length, for a two-dimensional slice, it is possible to have each inner slice be a different length

12345

text := LinesOfText{ []byte("Now is the time"), []byte("for all good gophers"), []byte("to bring some fun to the party."),}

map

For a map in golang like map[KeyType]ValueType, KeyType may be any type that is comparable ,such as integers, floating point and complex numbers, strings, pointers, interfaces (as long as the dynamic type supports equality).Slices cannot be used as map keys, because equality is not defined on them, and ValueType may be any type at all, including another map!

12

hits := make(map[string]map[string]int)n := hits["/doc/"]["au"]

Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.

An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map.The zero value is:

0 for numeric types,

false for the boolean type

“” (the empty string) for strings.

If you need to judge whether a key in map, you can do this

123

if val, ok := dict["foo"]; ok {//do something here}

Methods

Methods can be defined for any named type (except a pointer or an interface); the receiver does not have to be a struct.

Concurrency

Share by communicating

Concurrent programming in many environments is made difficult by the subtleties required to implement correct access to shared variables.

Go encourages a different approach in which shared values are passed around on channels and, in fact, never actively shared by separate threads of execution，only one goroutine has access to the value at any given time.

For example，Reference counts may be best done by putting a mutex around an integer variable. But as a high-level approach, using channels to control access makes it easier to write clear, correct programs.

Goroutines

A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space.

Prefix a function or method call with the go keyword to run the call in a new goroutine. When the call completes, the goroutine exits silently，don’t wait for it.

Channels

Like maps, channels are allocated with make, and the resulting value acts as a reference to an underlying data structure

There are lots of nice idioms using channels. For example, if we launched a sort in the background and do sth else while waiting for the goroutine to finish. A channel allows us to do so

12345678

c := make(chanint) // Allocate a channel.// Start the sort in a goroutine; when it completes, signal on the channel.gofunc() { list.Sort() c <- 1// Send a signal; value does not matter.}()doSomethingForAWhile()<-c // Wait for sort to finish; discard sent value.

The above code works becase receivers always block until there is data to receive. As for the sender, if the channel is unbuffered, the sender blocks until the receiver has received the value. If the channel has a buffer, the sender blocks only until the value has been copied to the buffer; if the buffer is full, this means waiting until some receiver has retrieved a value.

A buffered channel can be used like a semaphore, for instance to limit throughput. In the following example, incoming requests are passed to handle, which sends a value into the channel, processes the request, and then receives a value from the channel to ready the “semaphore” for the next consumer. The capacity of the channel buffer limits the number of simultaneous calls to process.

The above design has a problem: Serve creates a new goroutine for every incoming request, even though only MaxOutstanding of them can run at any moment. As a result, the program can consume unlimited resources if the requests come in too fast. We can address that deficiency by changing Serve to gate the creation of the goroutines.

The bug in the above code is that in a Go for loop, the loop variable is reused for each iteration, so the req variable is shared across all goroutines. But we need to make sure that req is unique for each goroutine. Here’s one way to do that, passing the value of req as an argument to the closure in the goroutine:

Another solution is just to create a new variable with the same name, like the following code, req := req may seem odd, but it’s legal and idiomatic in Go to do this. You get a fresh version of the variable with the same name

Another approach that manages resources well is to start a fixed number of handle goroutines all reading from the request channel. The number of goroutines limits the number of simultaneous calls to process.

Channels of channels

In the example in the previous section, handle was an idealized handler for a request but we didn’t define the type it was handling. If that type includes a channel on which to reply, each client can provide its own path for the answer.

Errors

Library routines must often return some sort of error indication to the caller, it is easy to return a detailed error description alongside the normal return value with Go’s multivalue return feature

Type error is a simple built-in interface, and library writer is free to implement this interface with a richer model under the covers, making it possible not only to see the error but also to provide some context

The usual way to report an error to a caller is to return an error as an extra return value, but sometimes the error is unrecoverable, the program simply cannot continue. We can use the built-in function panic in this case

panic function takes a single argument of arbitrary type—often a string—to be printed as the program dies. It’s also a way to indicate that something impossible has happened, for example,it is reasonable to use panic with the failure of initialization,

When panic is called, including implicitly for run-time errors such as indexing a slice out of bounds or failing a type assertion, it immediately stops execution of the current function and begins unwinding the stack of the goroutine, running any deferred functions along the way. If that unwinding reaches the top of the goroutine’s stack, the program dies. But it is possible to use the built-in function recover to regain control of the goroutine and resume normal execution.

A call to recover stops the unwinding and returns the argument passed to panic. Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside deferred functions.

One application of recover is to shut down a failing goroutine inside a server without killing the other executing goroutines. In this example, if do(work) panics, the result will be logged and the goroutine will exit cleanly without disturbing the others