No.8 Golang开发新手常犯的50个错误(下)

简介: No.8 Golang开发新手常犯的50个错误

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

36、关闭HTTP的Response.Body

使用defer语句关闭资源时要注意nil值,在defer语句之前要进行nill值处理

以下以http包的使用为例

package main
import (      "fmt"    "net/http"    "io/ioutil")
func main() {      resp, err := http.Get("https://api.ipify.org?format=json")
    if resp != nil {        defer resp.Body.Close()     // ok,即使不读取Body中的数据,即使Body是空的,也要调用close方法    }
    //defer resp.Body.Close()       // (1)Error:在nil值判断之前使用,resp为nil时defer中的语句执行会引发空引用的panic    if err != nil {        fmt.Println(err)        return    }
    //defer resp.Body.Close()       // (2)Error:排除了nil隐患,但是出现重定向错误时,仍然需要调用close    body, err := ioutil.ReadAll(resp.Body)    if err != nil {        fmt.Println(err)        return    }
    fmt.Println(string(body))}


在Go 1.5之前resp.Body.Close()会读取并丢失body中的数据,保证在启用keepaliva的http时能够在下一次请求时重用。
在Go 1.5之后,就需要在关闭前手动处理。

_, err = io.Copy(ioutil.Discard, resp.Body)


如果只是读取Body的部分,就很有必要在关闭Body之前做这种手动处理。例如处理json api响应时json.NewDecoder(resp.Body).Decode(&data)就需要处理掉剩余的数据。

37、关闭HTTP连接:

(1) 可使用req.Close=true,表示在http请求完成时关闭连接

(2) 添加Connection: close的连接请求头。http服务端也会发送Connection: close的响应头,http库处理响应时会关闭连接。

package main
import (      "fmt"    "net/http"    "io/ioutil")
func main() {      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)))}


(3)全局关闭http连接重用。

对于相同http server发送多个请求时,适合保持网络连接;
但对于短时间内向多个HTTP服务器发送一个或两个请求时,最好在每次接收到服务端响应后关闭网络链接

package main
import (      "fmt"    "net/http"    "io/ioutil")
func main() {      tr := &http.Transport{DisableKeepAlives: true}    client := &http.Client{Transport: tr}
    resp, err := client.Get("http://golang.org")    if resp != nil {        defer resp.Body.Close()    }
    if err != nil {        fmt.Println(err)        return    }
    fmt.Println(resp.StatusCode)
    body, err := ioutil.ReadAll(resp.Body)    if err != nil {        fmt.Println(err)        return    }
    fmt.Println(len(string(body)))

38、Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意。

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) //error: panic: interface conversion: interface is float64, not int   var status = uint64(result["status"].(float64)) //ok  fmt.Println("status value:",status)


(1) 使用Decoder类型解析JSON

var data = []byte(`{"status": 200}`)
var decoder = json.NewDecoder(bytes.NewReader(data))decoder.UseNumber()
if err := decoder.Decode(&result); err != nil {    fmt.Println("error:", err)    return}
var status,_ = result["status"].(json.Number).Int64() //okfmt.Println("status value:",status)
var status2 uint64if err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status2); err != nil {    fmt.Println("error:", err)    return}

         


(2)使用struct结构体映射

var data = []byte(`{"status": 200}`)var result struct {    Status uint64 `json:"status"`}
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil {    fmt.Println("error:", err)    return}
fmt.Printf("result => %+v",result) //prints: result => {Status:200}

         

(3) 使用struct映射数字为json.RawMessage

records := [][]byte{        []byte(`{"status": 200, "tag":"one"}`),        []byte(`{"status":"ok", "tag":"two"}`),    }
    for idx, record := range records {        var result struct {            StatusCode uint64            StatusName string            Status json.RawMessage `json:"status"`            Tag string             `json:"tag"`        }
        if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil {            fmt.Println("error:", err)            return        }
        var sstatus string        if err := json.Unmarshal(result.Status, &sstatus); err == nil {            result.StatusName = sstatus        }
        var nstatus uint64        if err := json.Unmarshal(result.Status, &nstatus); err == nil {            result.StatusCode = nstatus        }
        fmt.Printf("[%v] result => %+v\n",idx,result)    }



         


39、Struct、Array、Slice、Map的比较

如果struct结构体的所有字段都能够使用==操作比较,那么结构体变量也能够使用==比较。
但是,如果struct字段不能使用
==比较,那么结构体变量使用==比较会导致编译错误。

同样,array只有在它的每个元素能够使用==比较时,array变量才能够比较。

Go提供了一些用于比较不能直接使用==比较的函数,其中最常用的是reflect.DeepEqual()函数。

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


如果要忽略大小写来比较包含文字数据的字节切片(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()避免泄漏时间信息。

40、从panic中恢复

recover()函数能够捕获或拦截panic,但必须在defer函数或语句中直接调用,否则无效。

package main
import "fmt"
func doRecover() {      fmt.Println("recovered =>",recover()) //prints: recovered => <nil>}
func main() {      defer func() {        fmt.Println("recovered:",recover()) // ok    }()
    defer func() {        doRecover() //panic is not recovered    }()
    // recover()   //doesn't do anything    panic("not good")    // recover()   //won't be executed :)
    fmt.Println("ok")}

         


41、在slice、array、map的for range子句中修改和引用数据项

使用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}

         


42、Slice中的隐藏数据

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

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

raw := make([]byte,10000)fmt.Println(len(raw),cap(raw),&raw[0])      //prints: 10000 10000 <byte_addr_x>
data := raw[:3]fmt.Println(len(data),cap(data),&data[0])   //prints: 3 10000 <byte_addr_x>
res := make([]byte,3)copy(res,raw[:3])fmt.Println(len(res),cap(res),&res[0])      //prints: 3 3 <byte_addr_y>


43、Slice超范围数据覆盖

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

新生成切片之间capicity区域是重叠的,因此在添加数据时易造成数据覆盖问题。

slice使用append添加的内容时超出capicity时,会重新分配空间。
利用这一点,将要修改的切片指定capicity为切片当前length,可避免切片之间的超范围覆盖影响。


44、Slice增加元素重新分配内存导致的怪事

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]

         

45、类型重定义与方法继承

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

但是从一个已存在接口重新定义一个新接口时,新接口会继承原接口所有方法。

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

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

return当然也是可以的,如果在这里可以用的话。

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

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

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

package main
import (      "fmt"    "time")
func forState1(){    data := []string{"one","two","three"}
    for _,v := range data {        go func() {            fmt.Println(v)        }()    }    time.Sleep(3 * time.Second)    //goroutines print: three, three, three
    for _,v := range data {        vcopy := v // 使用临时变量        go func() {            fmt.Println(vcopy)        }()    }    time.Sleep(3 * time.Second)    //goroutines print: one, two, three
    for _,v := range data {        go func(in string) {            fmt.Println(in)        }(v)    }    time.Sleep(3 * time.Second)    //goroutines print: one, two, three}
func main() {      forState1()}

         


再看一个坑埋得比较深的例子。


package main
import (      "fmt"    "time")
type field struct {      name string}
func (p *field) print() {      fmt.Println(p.name)}
func main() {      data := []field{{"one"},{"two"},{"three"}}    for _,v := range data {        // 解决办法:添加如下语句        // v := v        go v.print()    }    time.Sleep(3 * time.Second)     //goroutines print: three, three, three
    data2 := []*field{{"one"}, {"two"}, {"three"}}  // 注意data2是指针数组    for _, v := range data2 {        go v.print()                // go执行是函数,函数执行之前,函数的接受对象已经传过来    }    time.Sleep(3 * time.Second)     //goroutines print: one, two, three}



48、defer函数调用参数

defer后面必须是函数或方法的调用语句。defer后面不论是函数还是方法,输入参数的值是在defer声明时已计算好,

而不是调用开始计算。

要特别注意的是,defer后面是方法调用语句时,方法的接受者是在defer语句执行时传递的,而不是defer声明时传入的。


type field struct{    num int}func(t *field) print(n int){    fmt.println(t.num, n)}func main() {        var i int = 1    defer fmt.Println("result2 =>",func() int { return i * 2 }())    i++
    v := field{1}    defer v.print(func() int { return i * 2 }())    v = field{2}    i++
    // prints:     // 2 4    // result => 2 (not ok if you expected 4)}

         


49、defer语句调用是在当前函数结束之后调用,而不是变量的作用范围。

for _,target := range targets {    f, err := os.Open(target)    if err != nil {        fmt.Println("bad target:",target,"error:",err) //prints error: too many open files        break    }    defer f.Close()         //will not be closed at the end of this code block, but closed at end of this function    // 解决方法1:不用defer    // f.Close()    // 解决方法2:将for中的语句添加到匿名函数中执行。    // func () {    // }() }

         


50、失败的类型断言

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


    var data interface{} = "great"
    if data, ok := data.(int); ok {        fmt.Println("[is an int] value =>",data)    } else {        fmt.Println("[not an int] value =>",data)         //prints: [not an int] value => 0 (not "great")    }
    if res, ok := data.(int); ok {        fmt.Println("[is an int] value =>",res)    } else {        fmt.Println("[not an int] value =>",data)         //prints: [not an int] value => great (as expected)    }

         




相关文章
|
3月前
|
Go API
Golang语言开发注意事项
这篇文章总结了Go语言开发中的注意事项,包括语法细节、注释使用、代码风格、API文档的利用以及如何使用godoc工具来生成文档。
43 2
|
4月前
|
监控 测试技术 API
|
4月前
|
监控 Serverless Go
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
|
4月前
|
Java Serverless Go
Golang 开发函数计算问题之在 Golang 中避免 "concurrent map writes" 异常如何解决
Golang 开发函数计算问题之在 Golang 中避免 "concurrent map writes" 异常如何解决
|
4月前
|
Serverless Go
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
|
5月前
|
监控 Go
golang开发 gorilla websocket的使用
【7月更文挑战第11天】在Golang中, 使用Gorilla WebSocket库可轻松实现WebSocket通信。安装库: `go get github.com/gorilla/websocket`。创建连接: `websocket.DefaultDialer.Dial(&quot;ws://url&quot;, nil)`。发送消息: `conn.WriteMessage(websocket.TextMessage, []byte(&quot;Hello&quot;))`。接收消息: 循环调用`conn.ReadMessage()`。适用于实时聊天或股票行情等场景。
132 0
|
6月前
|
Go 开发工具 C语言
从零开始使用golang开发
【6月更文挑战第17天】本文介绍 Go 语言安装与配置等操作。包括.下载与安装从[Go官网](https://golang.org/dl/)下载对应平台的安装包,安装时可自定义路径。安装验证,使用 `go version` 检查版本。环境配置和变量设置,包管理等
61 1
|
7月前
|
Kubernetes Cloud Native Go
Golang深入浅出之-Go语言中的云原生开发:Kubernetes与Docker
【5月更文挑战第5天】本文探讨了Go语言在云原生开发中的应用,特别是在Kubernetes和Docker中的使用。Docker利用Go语言的性能和跨平台能力编写Dockerfile和构建镜像。Kubernetes,主要由Go语言编写,提供了方便的客户端库与集群交互。文章列举了Dockerfile编写、Kubernetes资源定义和服务发现的常见问题及解决方案,并给出了Go语言构建Docker镜像和与Kubernetes交互的代码示例。通过掌握这些技巧,开发者能更高效地进行云原生应用开发。
204 1
|
7月前
|
Go
golang学习2,golang开发配置国内镜像
golang学习2,golang开发配置国内镜像
|
7月前
|
安全 Go
golang开发window环境搭建
golang开发window环境搭建