Skip to content

Latest commit

 

History

History
391 lines (310 loc) · 12.9 KB

go 11 常见陷阱.md

File metadata and controls

391 lines (310 loc) · 12.9 KB

Table of Contents

常见陷阱

➤ 参考 Common Mistakes

➤ 参考 50 Shades of Go

闭包相关

➤ 闭包捕获了一个会变的变量, 其一

func main() {
    data := []string{"one", "two", "three"}
    for _, v := range data {
        v := v
        go func() {
            fmt.Println(v)
        }()
    }
    // 会打印三次 three,  因为三个 goroutine 都捕获同一个变量
    // 等到 goroutine 执行时,  v 已经变成了 three
    // 解决办法是 v := v,  创建一个同名变量遮盖 for-range 定义的 v
    time.Sleep(500 * time.Millisecond)
}

➤ 用闭包捕获了一个会变的变量, 其二

type Name string
func (n *Name) Print() {
    time.Sleep(50 * time.Millisecond)
    fmt.Println(*n)
}
func main() {
    names := []Name{"Cloud", "Alice", "Tifa"}
    for _, n := range names {
        go n.Print()  // 在值类型上调用指针方法, 会变成 go (&n).Print()
        var _ = 1 + 1 // 而 n 变量的地址不会变,  所以会打印三次 Tifa
    }
    // 解决办法也是在 go 之前加个 n := n,  或者让 Print 改用 value receiver
    time.Sleep(500 * time.Millisecond)
}

➤ go/defer 函数的参数在什么时候进行求值 ?

func main() {
    var names = []string{"Cloud", "Alice", "Tifa"}
    for _, v := range names {
        go fmt.Println("go", v)       // 此时便会对函数参数 v 进行求值,  相当于存了 v 的快照
        defer fmt.Println("defer", v) // 此时便会对函数参数 v 进行求值,  相当于存了 v 的快照
    }
    time.Sleep(500 * time.Millisecond)
}

comparison

  1. You can use the equality operator, ==, to compare struct variables if each structure field can be compared with the equality operator.
  2. Slice, map, and function values are not comparable.

➤ The most generic solution is to use the DeepEqual() function in the reflect package.

func main() {
    m1 := map[string]int{"a": 1}
    m2 := map[string]int{"a": 1}
    fmt.Println(reflect.DeepEqual(m1, m2)) // true

    var s1 []byte
    var s2 = []byte{}
    fmt.Println(reflect.DeepEqual(s1, s2)) // false,  DeepEqual 认为 nil 切片和空切片不相等
    fmt.Println(bytes.Equal(s1, s2))       // true,   bytes.Equal 认为 nil 切片和空切片相等

    s3 := []string{"one", "two"}
    s4 := []interface{}{"one", "two"}
    fmt.Println(reflect.DeepEqual(s3, s4)) // false,  因为两个切片的类型不一样
}

slice & map

➤ nil slice 安全但 map 不是

func main() {
   var s []int
   var m map[string]int
   s = append(s, 1)             // 可以往 nil slice 添加元素
   fmt.Println(m["x"])          // 读取 nil map 返回零值
   m["one"] = 1                 // 写入 nil map 会 panic
   var _ = map[string]int{}     // 记得初始化 map
   var _ = make(map[string]int) // 记得初始化 map
}
  1. 数组是值类型, 若函数中要修改数组, 参数得用数组指针
  2. 若需要在函数中为切片增删元素, 参数也得用切片指针, 例如 AddItem(s *[]int)
  3. 不要依赖 map 的迭代顺序: Each map iteration will produce different results.

string

  1. str[0] 返回一个 byte, 而不是一个字符, 可以用 for offset, char := range "字符串" 按码点迭代
  2. string 不必是 utf-8 编码, 可以是任意一串字节, 比如 utf8.ValidString("A\xfeC") 返回 false
  3. len("🐶") 返回字节个数 4, utf8.RuneCountInString("🐶") 返回码点个数 1
  4. 恐怖的是, 一个 character 可以由多个 code point 构成, 例如 utf8.RuneCountInString("é́́") 是 4

concurrency

➤ Go 不提供并发安全的容器

Even though Go has a number of features to support concurrency natively, concurrency safe data collections are not one them :-) It's your responsibility to ensure the data collection updates are atomic. Goroutines and channels are the recommended way to implement those atomic operations, but you can also leverage the "sync" package if it makes sense for your application.

➤ 危险事项

  1. Send and receive operations on a nil channel block forver.
  2. Sending to an Closed Channel Causes a Panic. Receiving from a closed channel is safe (会返回零值).
  3. 可能犯的并发错误多了去了, 远远不止这里提到的

➤ 不要复制 sync.WaitGroupsync.Mutex 之类的东西

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go doWork(wg)
    wg.Wait() // 发生死锁,  因为 doWork 用值类型拷贝了 sync.WaitGroup
    fmt.Println("all done")
}

// 不要这么做! 复制锁会让锁失去意义, 函数参数记得用指针类型 *sync.WaitGroup
func doWork(wg sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("do some work")
}

http

➤ 别忘了关 HTTP Response Body

When you make requests using the standard http library you get a http response variable. If you don't read the response body you still need to close it. Note that you must do it for empty responses too. It's very easy to forget especially for new Go developers.

➤ 要先检查错误再设置 defer

func main() {
    resp, err := http.Get("https://127.0.0.1")
    defer resp.Body.Close() // 如果有错,  resp 是 nil,  defer 会空指针异常
    if err != nil {
        fmt.Println(err)
        return
    }
    // 应该在这个位置 (即检查错误之后) 设置 defer
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(body))
}

json

➤ JSON Encoder Adds a Newline Character

func main() {
   var b bytes.Buffer
   var c = Character{Name: "Cloud", From: "FF7", Age: 21}
   var _ = json.NewEncoder(&b).Encode(c)     // encoder 为 stream 设计,  所以
   fmt.Println(b.String())                   // 所以
   fmt.Println(b.Bytes()[b.Len()-1] == '\n') // 会在结尾加个 \n
}

➤ JSON Package Escapes Special HTML Characters in Keys and String Values

The Encoder.SetEscapeHTML method description talks about the default encoding behavior for the and, less than and greater than characters. You can't disable this behavior for the json.Marshal calls. It's bad because it assumes that the primary use case for JSON is a web page, which breaks the configuration libraries and the REST/HTTP APIs by default.

func main() {
    var m = map[string]string{
        "<KEY>": "<VALUE>",
    }

    data, err := json.Marshal(m)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data)) // 会被自动转义: {"\u003cKEY\u003e":"\u003cVALUE\u003e"}

    var b bytes.Buffer
    var encoder = json.NewEncoder(&b)
    encoder.SetEscapeHTML(false) // 关掉 <>& 这三个字符的自动转义
    err = encoder.Encode(m)
    if err != nil {
        panic(err)
    }
    fmt.Println(b.String()) // 原样输出: {"<KEY>":"<VALUE>"}
}

➤ Unmarshalling JSON Numbers into Interface Values

By default, Go treats numeric values in JSON as float64 numbers when you decode/unmarshal JSON data into an interface. This means the following code will fail with a panic:

func main() {
    var data = []byte(`{"status": 200}`)

    var result map[string]interface{}
    if err := json.Unmarshal(data, &result); err != nil {
        fmt.Println("error:", err)
        return
    }

    var status = result["status"].(int) // panic
    fmt.Println("status value:", status)

    // #### ➤ Option 1: use the float value as-is :-)

    // #### ➤ Option 2: convert the float value to the integer type you need.
    var _ = uint64(result["status"].(float64))

    // #### ➤ Option 3: use a Decoder and tell it to represent JSON numbers using the Number interface type.
    var decoder = json.NewDecoder(bytes.NewReader(data))
    decoder.UseNumber()
    if err := decoder.Decode(&result); err != nil {
        fmt.Println("error:", err)
        return
    }
    var state, _ = result["status"].(json.Number).Int64() // Int64 returns the number as an int64.
    fmt.Println("status value:", state)

    // #### ➤ Option 4: use a struct type that maps your numeric value to the numeric type you need.
    var result2 struct {
        Status uint64 `json:"status"`
    }
    if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result2); err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Printf("result2 => %+v", result2)
}

interface

➤ 什么是 nil interface ?

An interface is two fields:

  1. A pointer to some type-specific information. You can think of this as "type."
  2. Data pointer. If the data stored is a pointer, it’s stored directly.
    If the data stored is a value, then a pointer to the value is stored.
  3. Interface variables will be "nil" only when their type and value fields are "nil".
func main() {
    var m map[string]int
    var i111 interface{} = m // type 字段不为 nil,  value 字段为 nil
    var i222 interface{}     // 接口尚未被赋值,  所以两个字段都是 nil

    fmt.Println("m    == nil", m == nil)    // true
    fmt.Println("i111 == nil", i111 == nil) // false
    fmt.Println("i222 == nil", i222 == nil) // true

    if i111 != nil {
        m := i111.(map[string]int)
        m["key"]++ // 虽然检测过接口不为 nil,  但还是空指针异常了
    }
}

➤ 函数返回接口时, 小心接口被具体类型污染

func DeleteFile(path string) error {
    var err *os.PathError
    if path == "/root" {
        err = &os.PathError{}
    }
    return err // err 的类型会让返回值永远不可能是 nil interface
}

func main() {
    fmt.Println(DeleteFile("abc.txt") == nil) // false
}

更多常见陷阱

➤ recover 需要在 defer 函数中直接调用

func doRecover() {
   // 若 recover was not called directly by a deferred function.
   // 则 recover 不会起作用,  语言就是这么规定的
   fmt.Println("recovered =>", recover())
}

func main() {
   defer func() {
      doRecover() // panic is not recovered
   }()
   
   panic("not good")
}

➤ for-range 会拷贝 value, 让对结构体的修改无效

func main() {
    cs := []Character{{Name: "Cloud", From: "FF7"}, {Name: "Tifa", From: "FF7"}}
    for i, c := range cs {
        c.From = "Final Fantasy 7" // 无效, 因为 c 是拷贝
        cs[i].Age = 21             // 若要修改,  请用 index
    }
    fmt.Println(cs)
}

➤ switch/select 中的 break 只是跳出当前 switch/select

func main() {
loop:
    for {
        switch {
        case true:
            fmt.Println("breaking out...")
            // break   // X 仅退出当前 switch, 会无限循环
            break loop // √ 用标签跳出外层循环
        }
    }
    fmt.Println("out!")
}

➤ iota 并非从零开始逐渐加一, iota 的值是它在 constant block 中的索引

const (
    zero  = 0
    one   = iota // iota 的值为索引 1
    two   = 2
    three = iota // iota 的值为索引 3
)

➤ 无法修改 map 中的结构体, Why ?

You are storing a struct by value which means that accession of that struct in the map gives you a copy of the value. This is why when you modify it, the struct in the map remains unmutated until you overwrite it with the new copy.

func main() {
    var c = Character{Name: "Cloud", From: "FF7", Age: 21}
    var s = []Character{c}
    var m1 = map[string]Character{"Cloud": c}
    var m2 = map[string]*Character{"Cloud": &c}

    s[0].From = "Final Fantasy 7"        // 一切正常
    m1["Cloud"].From = "Final Fantasy 7" // 编译错误
    m2["Cloud"].From = "Final Fantasy 7" // 改用结构体指针, 一切正常
    m2["Tifa"].From = "Final Fantasy 7"  // 但是用指针类型就得小心 nil 指针 !

    temp := m1["Cloud"] // 这样也行,  复制一份出来,  改完了再放回去
    temp.Name = "Final Fantasy 7"
    m1["Cloud"] = temp
}