关于golang:Golang开发常见的57个错误

9次阅读

共计 9560 个字符,预计需要花费 24 分钟才能阅读完成。

1、不容许左大括号独自一行

2、不容许呈现未应用的变量

3、不容许呈现未应用的 import(应用 _ 包名 引入)

4、短的变量申明 (Short Variable Declarations) 只能在函数外部应用

// myvar := 1   // error
var myvar = 1   // ok

5、不能应用短变量申明 (Short Variable Declarations) 反复申明

6、不能应用短变量申明 (Short Variable Declarations) 这种形式来设置字段值

data.result, err := work() //error

7、意外的变量幽灵(Accidental Variable Shadowing)
代码块中同名短变量申明从申明开始到代码块完结,对变量的批改将不会影响到内部变量!

8、不能应用 nil 初始化一个未指定类型的变量

9、不能间接应用 nil 值的 Slice 和 Map

10、map 应用 make 分配内存时可指定 capicity,然而不能对 map 应用 cap 函数

在 golang 中,nil 只能赋值给指针、channel、func、interface、map 或 slice 类型的变量。

12、数组用于函数传参时是值复制

留神:办法或函数调用时,传入参数都是值复制(跟赋值统一),除非是 map、slice、channel、指针类型这些非凡类型是援用传递。

13、range 关键字返回是键值对,而不是值

14、Slice 和 Array 是一维的

15、从不存在 key 的 map 中取值时,返回的总是”0 值”

16、字符串是不可变的

17、字符串与[]byte 之间的转换是复制(有内存损耗),能够用 map[string] []byte 建设字符串与[]byte 之间映射,也可 range 来防止内存调配来进步性能

//[]byte: 
for i,v := range []byte(str) {}

18、string 的索引操作返回的是 byte(或 uint8),如想获取字符可应用 for range,也可应用 unicode/utf8 包和 golang.org/x/exp/utf8string 包的 At()办法。

19、字符串并不总是 UTF8 的文本

20、len(str)返回的是字符串的字节数

str := "我"
fmt.Println(len(str)) //3

21、在 Slice、Array、Map 的多行书写最初的逗号不可省略,单行书写,最初一个元素的逗号可省略

22、内置数据结构的操作并不同步,但可把 Go 提供了并发的个性应用起来:goroutines 和 channels。

23、应用 for range 迭代 String,是以 rune 来迭代的。
一个字符,也能够有多个 rune 组成。须要解决字符,尽量应用 golang.org/x/text/unicode/norm 包。

for range 总是尝试将字符串解析成 utf8 的文本,对于它无奈解析的字节,它会返回 oxfffd 的 rune 字符。
因而,任何蕴含非 utf8 的文本,肯定要先将其转换成字符切片([]byte)。

24、应用 for range 迭代 map 时每次迭代的程序可能不一样,因为 map 的迭代是随机的。

25、switch 的 case 默认匹配规定不同于其它语言的是,匹配 case 条件后默认退出,除非应用 fallthrough 持续匹配;而其它语言是默认持续匹配,除非应用 break 退出匹配。

26、只有后置自增(a++)、后置自减,不存在前置自增(++a)、前置自减

27、位运算的非操作是 ^(跟异或位运算一样),有别于其它语言的~。

28、位运算 (与、或、异或、取反) 优先级高于四则运算(加、减、乘、除、取余),有别于 C 语言。

29、构造体在序列化时非导出字段(以小写字母结尾的字段名)不会被 encode,因而在 decode 时这些非导出字段的值为”0 值”

30、程序不等所有 goroutine 完结就会退出。可通过 channel 实现主协程 (main goroutine) 期待所有 goroutine 实现。

31、对于无缓存区的 channel,写入 channel 的 goroutine 会阻塞直到被读取,读取 channel 的 goroutine 会阻塞直到有数据写入。

32、从一个 closed 状态的 channel 读取数据是平安的,可通过返回状态(第二个返回参数)判断是否敞开;而向一个 closed 状态的 channel 写数据会导致 panic。

33、向一个 nil 值(未用 make 调配空间)的 channel 发送或读取数据,会导致永远阻塞。

34、办法接收者是类型(T),接收者只是原对象的值复制,在办法中批改接收者不会批改原始对象的值;如果办法接收者是指针类型(*T),是对原对象的援用,办法中对其批改当然是原对象批改。

35、log 包中的 log.Fatal 和 log.Panic 不仅仅记录日志,还会停止程序。它不同于 Logging 库。

36、应用 defer 语句敞开资源时要留神 nil 值,在 defer 语句之前要进行 nil 值判断解决(否则会引发空援用的 panic)

37、敞开 HTTP 连贯,可应用

  1. req.Close=true,示意在 http 申请实现时敞开连贯
  2. 增加 Connection: close 的连贯申请头。http 服务端也会发送 Connection: close 的响应头,http 库解决响应时会敞开连贯。
  3. 全局敞开 http 连贯重用。

    package main
    
    import (  
     "fmt"
     "net/http"
     "io/ioutil"
    )
    
    func main() {  
     // 全局敞开 http 连贯重用
     //tr := &http.Transport{DisableKeepAlives: true}
     //client := &http.Client{Transport: tr}
    
     req, err := http.NewRequest("GET","http://golang.org",nil)
     if err != nil {fmt.Println(err)
         return
     }
    
     req.Close = true
     //or do this:
     //req.Header.Add("Connection", "close")
    
     resp, err := http.DefaultClient.Do(req)
     if resp != nil {defer resp.Body.Close()
     }
    
     if err != nil {fmt.Println(err)
         return
     }
    
     body, err := ioutil.ReadAll(resp.Body)
     if err != nil {fmt.Println(err)
         return
     }
    
     fmt.Println(len(string(body)))
    }

37、Json 反序列化数字到 interface{}类型的值中,默认解析为 float64 类型

38.Struct、Array、Slice、Map 的比拟
如果 struct 构造体的所有字段都可能应用 == 操作比拟,那么构造体变量也可能应用 == 比拟。
然而,如果 struct 字段不能应用 == 比拟,那么构造体变量应用 == 比拟会导致编译谬误。

同样,array 只有在它的每个元素可能应用 == 比拟时,array 变量才可能比拟。

Go 提供了一些用于比拟不能间接应用 == 比拟的函数,其中最罕用的是 reflect.DeepEqual()函数。

DeepEqual()函数对于 nil 值的 slice 与空元素的 slice 是不相等的,这点不同于 bytes.Equal()函数。

var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: false

var b3 []byte = nil
b4 := []byte{}
fmt.Println("b3 == b4:",bytes.Equal(b3, b4)) //prints: b3 == b4: true

如果要疏忽大小写来比拟蕴含文字数据的字节切片 (byte slice),
不倡议应用 bytes 包和 strings 包里的 ToUpper()、ToLower()这些函数转换后再用 ==、byte.Equal()、bytes.Compare()等比拟,ToUpper()、ToLower()只能解决英文文字,对其它语言有效。因而倡议应用 strings.EqualFold()和 bytes.EqualFold()

如果要比拟用于验证用户数据密钥信息的字节切片时,应用 reflact.DeepEqual()、bytes.Equal()、
bytes.Compare()会使应用程序蒙受计时攻打 (Timing Attack),可应用 crypto/subtle.ConstantTimeCompare() 防止透露工夫信息。

39、recover()函数可能捕捉或拦挡 panic,但必须在 defer 函数或语句中间接调用,否则有效。

40、在 slice、array、map 的 for range 获取的数据项是从汇合元素的复制过去的,并非援用原始数据,但应用索引能拜访原始数据。

data := []int{1,2,3}
for _,v := range data {v *= 10          // original item is not changed}

data2 := []int{1,2,3}
for i,v := range data2 {data2[i] *= 10       // change original item
}

// 元素是指针类型就不一样了
data3 := []*struct{num int} {{1}, {2}, {3}}
for _,v := range data {v.num *= 10}

fmt.Println("data:", data)              //prints data: [1 2 3]
fmt.Println("data:", data2)             //prints data: [10 20 30]
fmt.Println(data3[0],data3[1],data3[2])    //prints &{10} &{20} &{30}

41、从一个 slice 上再生成一个切片 slice,新的 slice 将间接援用原始 slice 的那个数组,两个 slice 对同一数组的操作,会相互影响。

可通过 copy()为新切片 slice 重新分配空间,从 slice 中 copy 局部的数据来防止相互之间的影响。

42. 从已存在的切片 slice 中持续切片时,新切片的 capicity 等于原 capicity 减去新切片之前局部的数量,新切片与原切片都指向同一数组空间。

新生成切片之间 capicity 区域是重叠的,因而在增加数据时易造成数据笼罩问题。

slice 应用 append 增加的内容时超出 capicity 时,会重新分配空间。
利用这一点,将要批改的切片指定 capicity 为切片以后 length,可防止切片之间的超范围笼罩影响。

    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex]
    // 解决办法
    // dir1 := path[:sepIndex:sepIndex] //full slice expression
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})

    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)

    fmt.Println("new path =>",string(path))

43、slice 在增加元素前,与其它切片共享同一数据区域,批改会相互影响;但增加元素导致内存重新分配之后,不再指向原来的数据区域,批改元素,不再影响其它切片。

    s1 := []int{1,2,3}
    fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]

    s2 := s1[1:]
    fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]

    for i := range s2 {s2[i] += 20 }

    //still referencing the same array
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [22 23]

    s2 = append(s2,4)

    for i := range s2 {s2[i] += 10 }

    //s1 is now "stale"
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [32 33 14]

44、类型重定义与办法继承

从一个已存在的 (non-interface) 非接口类型从新定义一个新类型时,不会继承原类型的任何办法。
能够通过定义一个组合匿名变量的类型,来实现对此匿名变量类型的继承。

然而从一个已存在接口从新定义一个新接口时,新接口会继承原接口所有办法。

45、从”for switch”和”for select”代码块中跳出。

无 label 的 break 只会跳出最内层的 switch/select 代码块。
如须要从 switch/select 代码块中跳出外层的 for 循环,能够在 for 循环内部定义 label,供 break 跳出。

return 当然也是能够的,如果在这里能够用的话。

46、在 for 语句的闭包中应用迭代变量会有问题

在 for 迭代过程中,迭代变量会始终保留,只是每次迭代值不一样。
因而在 for 循环中在闭包里间接援用迭代变量,在执行时间接取迭代变量的值,而不是闭包所在迭代的变量值。

如果闭包要取所在迭代变量的值,就须要 for 中定义一个变量来保留所在迭代的值,或者通过闭包函数传参。

47、defer 函数调用参数

defer 前面必须是函数或办法的调用语句。defer 前面不论是函数还是办法,输出参数的值是在 defer 申明时已计算好,
而不是调用开始计算。

要特地留神的是,defer 前面是办法调用语句时,办法的接受者是在 defer 语句执行时传递的,而不是 defer 申明时传入的。

48、defer 语句调用是在以后函数完结之后调用,而不是变量的作用范畴。

49、失败的类型断言:var.(T)类型断言失败时会返回 T 类型的“0 值”,而不是变量原始值。

func main() {var data interface{} = "great"
      res, ok := data.(int); 
    fmt.Println("res =>",res, ",ok =>",ok)//res => 0 ,ok => false
}

50、阻塞的 goroutine 与资源透露

func First(query string, replicas ...Search) Result {c := make(chan Result)
    // 解决 1:应用缓冲的 channel:c := make(chan Result,len(replicas))
    searchReplica := func(i int) {c <- replicas[i](query) }
    // 解决 2:应用 select-default,避免阻塞
    // searchReplica := func(i int) { 
    //     select {//     case c <- replicas[i](query):
    //     default:
    //     }
    // }
    // 解决 3:应用非凡的 channel 来中断原有工作
    // done := make(chan struct{})
    // defer close(done)
    // searchReplica := func(i int) { 
    //     select {//     case c <- replicas[i](query):
    //     case <- done:
    //     }
    // }

    for i := range replicas {go searchReplica(i)
    }
    return <-c
}

51、用值实例上调用接收者为指针的办法

对于可寻址 (addressable) 的值变量 (而不是指针),能够间接调用承受对象为指针类型的办法。
换句话说,就不须要为可寻址值变量定义以承受对象为值类型的办法了。

然而,并不是所有变量都是可寻址的,像 Map 的元素就是不可寻址的。

package main

import "fmt"

type data struct {name string}

func (p *data) print() {fmt.Println("name:",p.name)
}

type printer interface {print()
}

func main() {d1 := data{"one"}
    d1.print() //ok

    // var in printer = data{"two"} //error
    var in printer = &data{"two"}
    in.print()

    m := map[string]data {"x":data{"three"}}
    //m["x"].print() //error
    d2 = m["x"]
    d2.print()      // ok}

52、更新 map 值的字段

如果 map 的值类型是构造体类型,那么不能更新从 map 中取出的构造体的字段值。
然而对于构造体类型的 slice 却是能够的。

package main

type data struct {name string}

func main() {m := map[string]data {"x":{"one"}}
    //m["x"].name = "two" //error
    r := m["x"]
    r.name = "two"
    m["x"] = r
    fmt.Println(s)       // prints: map[x:{two}]

    mp := map[string]*data {"x": {"one"}}
    mp["x"].name = "two" // ok

    s := []data{{"one"}}
    s[0].name = "two"    // ok
    fmt.Println(s)       // prints: [{two}]
}

53、nil 值的 interface{}不等于 nil
在 golang 中,nil 只能赋值给指针、channel、func、interface、map 或 slice 类型的变量。

interface{}示意任意类型,能够接管任意类型的值。interface{}变量在底是由类型和值两局部组成,示意为 (T,V),interface{} 变量比拟非凡,判断它是 nil 时,要求它的类型和值都是 nil,即 (nil, nil)。
其它类型变量,只有值是 nil,那么此变量就是 nil(为什么?变量类型不是 nil,那当然只能用值来判断了)

申明变量 interface{},它默认就是 nil,底层类型与值示意是 (nil, nil)。
当任何类型 T 的变量值 V 给 interface{}变量赋值时,interface{}变量的底层示意是 (T, V)。只有 T 非 nil,即便 V 是 nil,interface{} 变量也不是 nil。

    var data *byte
    var in interface{}

    fmt.Println(data,data == nil) //prints: <nil> true
    fmt.Println(in,in == nil)     //prints: <nil> true

    in = data
    fmt.Println(in,in == nil)     //prints: <nil> false
    //'data' is 'nil', but 'in' is not 'nil'

    doit := func(arg int) interface{} {var result *struct{} = nil
        if(arg > 0) {result = &struct{}{}}
        return result
    }
    if res := doit(-1); res != nil {fmt.Println("good result:",res) //prints: good result: <nil>
        //'res' is not 'nil', but its value is 'nil'
    }

    doit = func(arg int) interface{} {var result *struct{} = nil
        if(arg > 0) {result = &struct{}{}} else {return nil //return an explicit 'nil'}
        return result
    }

    if res := doit(-1); res != nil {fmt.Println("good result:",res)
    } else {fmt.Println("bad result (res is nil)") //here as expected
    }

54、变量内存的调配

在 C ++ 中应用 new 操作符总是在 heap 上调配变量。Go 编译器应用 new()和 make()分配内存的地位到底是 stack 还是 heap,
取决于变量的大小 (size) 和逃逸剖析的后果(result of“escape analysis”)。这意味着 Go 语言中,返回本地变量的援用也不会有问题。

要想晓得变量内存调配的地位,能够在 go build、go run 命令指定 -gcflags - m 即可:
go run -gcflags -m app.go

55、GOMAXPROCS、Concurrency 和 Parallelism

Go 1.4 及以下版本每个操作系统线程只应用一个执行上下文 execution context)。这意味着每个工夫片,只有一个 goroutine 执行。
从 Go 1.5 开始能够设置执行上下文的数量为 CUP 内核数量 runtime.NumCPU(),也能够通过 GOMAXPROCS 环境变量来设置,
还可调用 runtime.GOMAXPROCS()函数来设置。

留神,GOMAXPROCS 并不代表 Go 运行时可能应用的 CPU 数量,它是一个小 256 的数值,能够设置比理论的 CPU 数量更大的数字。

56、读写操作排序

Go 可能会对一些操作排序,但它保障在 goroutine 的所有行为放弃不变。
然而,它无奈保障在跨多个 goroutine 时的执行程序。

package main

import (  
    "runtime"
    "time"
)

var _ = runtime.GOMAXPROCS(3)

var a, b int

func u1() {  
    a = 1
    b = 2
}

func u2() {  
    a = 3
    b = 4
}

func p() {println(a)
    println(b)
}

func main() {go u1()
    go u2()
    go p()
    time.Sleep(1 * time.Second)
    // 屡次执行可显示以下以几种打印后果
    // 1   2
    // 3   4
    // 0   2 (奇怪吗?)
    // 0   0    
    // 1   4 (奇怪吗?)
}

57、优先调度

有一些比拟流氓的 goroutine 会阻止其它 goroutine 的执行。
例如 for 循环可能就不容许调度器 (scheduler) 执行。

scheduler 会在 GC、go 语句、阻塞 channel 的操作、阻塞零碎调用、lock 操作等语句执行之后立刻执行。
也能够显示地执行 runtime.Gosched()(让出工夫片)使 scheduler 执行调度工作。

package main

import (  
    "fmt"
    "runtime"
)

func main() {  
    done := false
    go func(){done = true}()

    for !done {
        // ... 
        //runtime.Gosched() // 让 scheduler 执行调度,让出执行工夫片}
    fmt.Println("done!")
}

参考资料:https://blog.csdn.net/gezhong…

正文完
 0