0%

Go_Learning_Effective_Go_Docs.md

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
2
3
4
type T struct {
name string // name of the object
value int // its value
}

After gofmt:

1
2
3
4
type T struct {
name string // name of the object
value int // its value
}

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 regexp

    If 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 name base64, not encoding_base64 and not encodingBase64.
  • 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 ++ -- ) }
  • 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, or select) 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 as for 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

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
    3
    if x > 0 {
    return y
    }
  • Intersting syntax: if with initialization statement

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

for

There are three forms in for syntaxs:

1
2
3
4
5
6
7
8
// Like a C for
for init; condition; post { }

// Like a C while
for condition { }

// Like a C for(;;)
for { }

for with range:

range statements is a good way to loop array, slice, map, channel, the syntax is :

1
2
3
for key, value := range oldMap {
newMap[key] = value
}

If you only need the first item in the range (the key or index), drop the second:

1
2
3
4
5
for key := range m {
if key.expired() {
delete(m, key)
}
}

If you only need the second item in the range (the value), use the blank identifier, an underscore, to discard the first:

1
2
3
4
sum := 0
for _, value := range array {
sum += value
}
  • 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
2
3
4
5
6
7
8
for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}

//character U+65E5 '日' starts at byte position 0
//character U+672C '本' starts at byte position 3
//character U+FFFD '�' starts at byte position 6
//character U+8A9E '語' starts at byte position 7

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
2
3
4
5
6
7
8
9
10
11
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}

There is no automatic fall through, but cases can be presented in comma-separated lists.

1
2
3
4
5
6
7
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

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
2
3
4
5
6
7
8
9
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.

var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append is discussed later.
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed if we return here.
}
}
return string(result), nil // f will be closed if we return here.
}
  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func trace(s string) string {
fmt.Println("entering:", s)
return s
}

func un(s string) {
fmt.Println("leaving:", s)
}

func a() {
defer un(trace("a"))
fmt.Println("in a")
}

func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}

func main() {
b()
}
1
2
3
4
5
6
entering: b
in b
entering: a
in a
leaving: a
leaving: 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
      6
      type SyncedBuffer struct {
      lock sync.Mutex
      buffer bytes.Buffer
      }
      var p *SyncedBuffer = new(SyncedBuffer) // type *SyncedBuffer
      p := new(SyncedBuffer) // type *SyncedBuffer
    • Zeros slice:

      1
      2
      var 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} or Type{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
      9
      func 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
2
3
4
5
6
7
8
9
func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator

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
2
3
4
5
6
7
8
9
10
11
12
13
func Append(slice, data []byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
copy(slice[l:], data)
return slice
}

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
2
3
4
5
6
7
var timeZone = map[string]int{
"UTC": 0*60*60,
"EST": -5*60*60,
"CST": -6*60*60,
"MST": -7*60*60,
"PST": -8*60*60,
}

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
    9
    attended := 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
    10
    var 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 function

    SAFE 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 : Correct
    • math.Sin(math.Pi): Wrong
  • enumerated constant: iota

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    type 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
    5
    var (
    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
    13
    func 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
2
3
4
5
6
7
8
9
10
// values method
func (slice ByteSlice) Append(data []byte) []byte {
// Body exactly the same as the Append function defined above.
}
// pointer method
func (p *ByteSlice) Append(data []byte) {
slice := *p
// Body as above, without the return.
*p = slice
}

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
    3
    if _, 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
    20
    package 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
2
3
4
5
6
7
type Reader interface {
Read(p []byte) (n int, err error)
}

type Writer interface {
Write(p []byte) (n int, err error)
}
1
2
3
4
5
6
// embedding in interface
// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
Reader
Writer
}

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:

  1. struct definition with embedding

    1
    2
    3
    4
    type ReadWriter struct {
    reader *Reader
    writer *Writer
    }
  2. provide forwarding methods

    1
    2
    3
    func (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.

Concurrency

Errors

A web server