EffectiveGo_简单笔记
地址: 教材
可用于快速了解GO语言.
Introduction
Effective_Go provide:
- Go language properties and idioms
- Established conventions for programming in Go
This document gives tips for writting clear,idiomatic Go code.
Example
Go standard package is a short path to understand Excellent Go Code. Fortunately many lib provide self-contained executable example you can run directly from the golang.org web site,such as this one.
Formatting
Formatting issues are the most contentious but the least consequential. While in real life, every one may adapt different code style. The best way is Every developer adapt same code style.
In Go language, the machine could take care of most formatting issues. The gofmt program could read Go code and emits the source in a standard style. The standard style may include: indentation and vertical alignment, retaining and if necessary reformatting comments.
Example Code: before
1 | type T struct { |
After gofmt:
1 | type T struct { |
Formatting include:
- Indentation: GO use TAB as default
- Line length: No length limit
- Parentheses: Control structures(if, for, switch) do not have parenthese in their syntax
Commentary
Go provides block(/* */) comments and line comments(//). line comments is the same as other language, While block coments may serve as package comments.
Types:
package comments: Appear before top-level declarations
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. It will appear first on the
godoc
page and should set up the detailed documentation that follows.doc comments: Apeear above the declarations.
In go, every export(Capital Letter start) needs doc comments
general comments: others
Conventions
godoc process Go source files to extract documentation about the package. The document quality is based on the comments quality. So thire are some rules we need to obey:
Package Comments: introduce the package and provide information relevant to the package as a whole. It will appear first on the
godoc
page and should set up the detailed documentation that follows.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/*
Package regexp implements a simple library for regular expressions.
The syntax of the regular expressions accepted is:
regexp:
concatenation { '|' concatenation }
concatenation:
{ closure }
closure:
term [ '*' | '+' | '?' ]
term:
'^'
'$'
'.'
character
'[' [ '^' ] character-ranges ']'
'(' regexp ')'
*/
package regexpIf package is simple, the package comments can be brief:
1
2// Package path implements utility routines for
// manipulating slash-separated filename paths.Doc comments: The first sentence should be a one-sentence summary that starts with the name being declared.
1
2
3// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) {Comments do not need extra formatting such as banners of stars.
Names
Name, in Go language, has syntax effect: the visibility of a name outside a package is determined by whether its first character is upper case.
Package Name
- Package Name is an accessor for the content.
- convention:
- packages are given lower case, single-word names;
- No underscore,No mixedCaps
- the package name is the base name of its source directory;
- the package in
src/encoding/base64
is imported as"encoding/base64"
but has namebase64
, notencoding_base64
and notencodingBase64
.
- the package in
- The importer of a package will use the name to refer to its contents, so exported names in the package can use that fact to avoid repetition.
Interface Name
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
etc.
MixedCap
the convention in Go is to use MixedCaps
or mixedCaps
rather than underscores to write multiword names.
Semicolons
In Go language, Semicolons(;) is used to terminate statement. The lexer would add semicolons autumatically based on some rules:
Rules:
Add semicolons : if the newline comes after a token that could end a statement, insert a semicolon:
- identifier:
int
,string
- basic literial: a number or const string
- some tokens:
break continue fallthrough return ++ -- ) }
- identifier:
Omit semicolons: Closing identifier, such as
1
go func() { for { dst <- <-src } }()
you cannot put the opening brace of a control structure (
if
,for
,switch
, orselect
) on the next line. If you do, a semicolon will be inserted before the brace, which could cause unwanted effects.1
2
3
4// right
if i < f() {
g()
}1
2
3
4
5//wrong
if i < f() // wrong!
{ // wrong!
g()
}
Idiomatic:
;
,Go programs have semicolons only in places such asfor
loop clauses, to separate the initializer, condition, and continuation elements.;
, separate multiple statements on a line, should you write code that way.
Control structures
In Go language, there is only if , for , switch
as control structure while no do, while
loop.
- Same with other language:
break,continue
statements is the same as others - Different :
- in
switch
structure,type
is optional label select
- in
Syntax in Go: No need parentheses while the body must always be brace-delimited.
if
Ordinary syntan: Writting simple
if
in multiple lines.1
2
3if x > 0 {
return y
}Intersting syntax:
if
with initialization statement1
2
3
4if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
for
There are three forms in for
syntaxs:
1 | // Like a C for |
for with range:
range statements is a good way to loop array, slice, map, channel, the syntax is :
1 | for key, value := range oldMap { |
If you only need the first item in the range (the key or index), drop the second:
1 | for key := range m { |
If you only need the second item in the range (the value), use the blank identifier, an underscore, to discard the first:
1 | sum := 0 |
- Range does more work for
string
:Breaking out individual Unicode code points by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD.
1 | for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding |
switch
switch has the same mwaning as other language. In go language, if the switch
has no expression it switches on true
. It’s therefore possible—and idiomatic—to write an if
-else
-if
-else
chain as a switch
.
1 | func unhex(c byte) byte { |
There is no automatic fall through, but cases can be presented in comma-separated lists.
1 | func shouldEscape(c byte) bool { |
Type switch
A switch can also be used to discover the dynamic type of an interface variable. Such a type switch uses the syntax of a type assertion with the keyword type
inside the parentheses. If the switch declares a variable in the expression, the variable will have the corresponding type in each clause. It’s also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.
1 | var t interface{} |
Function
Three unusual features in Go Functions:
Multiple return values
Like PYTHON, functions in go could return multiple values. A standard example in package os
is Write
method:
1 | func (file *File) Write(b []byte) (n int, err error) |
1 | it returns the number of bytes written and a non-nil error when n != len(b). |
Named result parameters
The return parameters of a Go function can be given names and used as regular variables, just like incoming parameters. When named, they are initialized to the ZERO values for their types when the function begins; if the function executes a return
statement with no arguments, the current values of the result parameters are used as the returned values.
1 | func nextInt(b []byte, pos int) (value, nextPos int) { |
- Make code shorter and clearer
Because named results are initialized and tied to an unadorned return, they can simplify as well as clarify. Here’s a version of io.ReadFull
that uses them well:
1 | func ReadFull(r Reader, buf []byte) (n int, err error) { |
Defer
Go’s defer
statement schedules a function call (the deferred function) to be run immediately before the function executing the defer
returns. It’s an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return. The canonical examples are unlocking a mutex or closing a file.
1 | // Contents returns the file's contents as a string. |
- First, it guarantees that you will never forget to close the file, a mistake that’s easy to make if you later edit the function to add a new return path.
- Second, it means that the close sits near the open, which is much clearer than placing it at the end of the function.
Deferred functions are executed in LIFO order,
1 | func trace(s string) string { |
1 | entering: b |
Data
Allocation
Three allocation methods in Go language, new
make
composite literals
; While new
and make
are build in functions, composite literals
is a kind of initialise format. Different allocation method with different feature.
new
: build in function, allocate memory , zeros it, ,return the pointer(*T) of type T
.
Apply Type: Any
Basic Format:
new(type)
Zeros struct:
1
2
3
4
5
6type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
var p *SyncedBuffer = new(SyncedBuffer) // type *SyncedBuffer
p := new(SyncedBuffer) // type *SyncedBufferZeros slice:
1
2var p *[]int = new([]int) // allocates slice structure; *p == nil;
p := new([]int) //allocates slice structure; *p == nil;
make
:build in function, allocate memory, initialized it, return the value(T) of type T
.
Aapply Type: only slices, maps, channels
Basic Format:
make(type , length , capacity)
Initialized Slice:
A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is
nil
.1
make([]int, 10, 100)
allocates an array of 100 ints and then creates a slice structure with length 10 and a capacity of 100 pointing at the first 10 elements of the array. When making a slice, the capacity can be omitted;
composite literals
: a kind of initialise format, allocate memory, initialized it , return the value(T) of type T
Apply Type: Any
Basic Format:
Type{value_0, ..., value_n}
orType{item_y : value_x, item_x: value_x, ... }
Initialized Slice:
1
2// TODO: figure out
s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}Initialized Struct:
1
2
3
4
5
6
7
8
9func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := File{fd, name, nil, 0}
return &f
// equals to: return &File{fd, name, nil, 0}
// equals to: return &File{fd: fd, name: name}
}Data structure
Array
Array is primarily as the building block for Slice.
Features:
- Arrays are values. Assigning one array to another copies all the elements.
- In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
- The size of an array is part of its type. The types
[10]int
and[20]int
are distinct.
The value property can be useful but also expensive; if you want C-like behavior and efficiency, you can pass a pointer to the array.
1 | func Sum(a *[3]float64) (sum float64) { |
But even this style isn’t idiomatic Go. Use slices instead.
Slice
Slice wrap arrays to give a more general, powerful and convinent interface to sequence of data. Most array programming in GO is done with slices rather than simple arrays.
Features:
- Slices hold the references to an underlaying array
- if you assign one slice to another, both refer to the same array.
- If a functions take Slice argument, changes it makes to the slices elements will be visible to the caller.
The length of a slice may be changed as long as it still fits within the limits of the underlying array; just assign it to a slice of itself. The capacity of a slice, accessible by the built-in function cap
, reports the maximum length the slice may assume. Here is a function to append data to a slice. If the data exceeds the capacity, the slice is reallocated. The resulting slice is returned. The function uses the fact that len
and cap
are legal when applied to the nil
slice, and return 0.
1 | func Append(slice, data []byte) []byte { |
We must return the slice afterwards because, although Append
can modify the elements of slice
, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.
Map
What Map ?
The key can be of any type for which the equality operator is defined, such as integers, floating point and complex numbers, strings, pointers, interfaces (as long as the dynamic type supports equality), structs and arrays. Slices cannot be used as map keys, because equality is not defined on them. 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.
initialize Map
Maps can be constructed using the usual composite literal syntax with colon-separated key-value pairs, so it’s easy to build them during initialization.
1 | var timeZone = map[string]int{ |
Features
get value: simliar with slice
1
offset := timeZone["EST"]
apply the key which not in map : return zeros of value type
1
2
3
4
5
6
7
8
9attended := map[string]bool{
"Ann": true,
"Joe": true,
...
}
if attended[person] { // will be false if person is not in the map
fmt.Println(person, "was at the meeting")
}two var is accept : get gistinguish a missing entry from zeros value
1
2
3
4
5
6
7
8
9
10var seconds int
var ok bool
seconds, ok = timeZone[tz]
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Println("unknown time zone:", tz)
return 0
}Omit value:
_
1
_, present := timeZone[tz]
Delete map entry:
delete
functionSAFE function even if the key is already absent from the map.
1
delete(timeZone, "PDT") // Now on Standard Time
Initialization
constant
CreateTime: compile time
can only be:
numbers
,characters
,string
,booleans
Define location: could be defined as locals in functions
constant expression: only constant expression
1<<3
: Correctmath.Sin(math.Pi)
: Wrong
enumerated constant:
iota
1
2
3
4
5
6
7
8
9
10
11
12
13type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)variable
CreateTime: Runtime
can be any type
initializer can be a general expression computed at run time.
1
2
3
4
5var (
home = os.Getenv("HOME")
user = os.Getenv("USER")
gopath = os.Getenv("GOPATH")
)
init function
Call Time:After all the variable declarations in the package have evaluated;
a common use of
init
functions is to verify or repair correctness of the program state before real execution begins.1
2
3
4
5
6
7
8
9
10
11
12
13func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
if gopath == "" {
gopath = home + "/go"
}
// gopath may be overridden by --gopath flag on command line.
flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}Methods
Methods can be divited into two types: Values methods OR Pointer methods.For Example:
1 | // values method |
Difference: The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.
- pointer methods can modify the receiver
- value methods will receive a copy of value, so the change will be discard
In GO language the pointer method is recommend. When the value is addressable, the complier would rewrite that expression for us. For example, coder wirte b.write
, the complier would rewrite it to (&b).write
.
Interface and other types
The blank identifier
The blank identifier _
can be assigned with any value of any type, with the value discarded harmlessly.
usage scenario
The blank identifier in multiple assignment
1
2
3if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("%s does not exist\n", path)
}Unused imports and variables: half-written program
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package main
import (
"fmt"
"io"
"log"
"os"
)
var _ = fmt.Printf // For debugging; delete when done.
var _ io.Reader // For debugging; delete when done.
func main() {
fd, err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
// TODO: use fd.
_ = fd
}import for side effect
1
import _ "net/http/pprof"
interface checks
…
Embedding
In GO language there is no typical, type-driver notion of subclassing, while it have the ability to borrow the implementation by embedding
types with interface or struct.
embedding in interface
Interface embedding is simple.For example, we define two basic interface Reader
Writer
,and embedding them into complex interface ReadWriter
.
1 | type Reader interface { |
1 | // embedding in interface |
This says just what it looks like: A ReadWriter
can do what a Reader
does and what a Writer
does; it is a union of the embedded interfaces. Only interfaces can be embedded within interfaces.
embedding in struct
Embedding in struct is far-reaching than interface. Two steps in struct way:
struct definition with embedding
1
2
3
4type ReadWriter struct {
reader *Reader
writer *Writer
}provide forwarding methods
1
2
3func (rw *ReadWriter) Read(p []byte) (n int, err error) {
return rw.reader.Read(p)
}difference from subclassing
There’s an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one. In our example, when the Read
method of a bufio.ReadWriter
is invoked, it has exactly the same effect as the forwarding method written out above; the receiver is the reader
field of the ReadWriter
, not the ReadWriter
itself.