1. 程式人生 > >[轉載][翻譯]Go的50坑:新Golang開發者要注意的陷阱、技巧和常見錯誤[1]

[轉載][翻譯]Go的50坑:新Golang開發者要注意的陷阱、技巧和常見錯誤[1]

Go是一門簡單有趣的語言,但與其他語言類似,它會有一些技巧。。。這些技巧的絕大部分並不是Go的缺陷造成的。如果你以前使用的是其他語言,那麼這其中的有些錯誤就是很自然的陷阱。其它的是由錯誤的假設和缺少細節造成的。

如果你花時間學習這門語言,閱讀官方說明、wiki、郵件列表討論、大量的優秀博文和Rob Pike的展示,以及原始碼,這些技巧中的絕大多數都是顯而易見的。儘管不是每個人都是以這種方式開始學習的,但也沒關係。如果你是Go語言新人,那麼這裡的資訊將會節約你大量的除錯程式碼的時間。

目錄

  • 初級篇
    • 開大括號不能放在單獨的一行
    • 未使用的變數
    • 未使用的Imports
    • 簡式的變數宣告僅可以在函式內部使用
    • 使用簡式宣告重複宣告變數
    • 偶然的變數隱藏Accidental Variable Shadowing
    • 不使用顯式型別,無法使用“nil”來初始化變數
    • 使用“nil” Slices and Maps
    • Map的容量
    • 字串不會為“nil”
    • Array函式的引數
    • 在Slice和Array使用“range”語句時的出現的不希望得到的值
    • Slices和Arrays是一維的
    • 訪問不存在的Map Keys
    • Strings無法修改
    • String和Byte Slice之間的轉換
    • String和索引操作
    • 字串不總是UTF8文字
    • 字串的長度
    • 在多行的Slice、Array和Map語句中遺漏逗號
    • log.Fatal和log.Panic不僅僅是Log
    • 內建的資料結構操作不是同步的
    • String在“range”語句中的迭代值
    • 對Map使用“for range”語句迭代
    • "switch"宣告中的失效行為
    • 自增和自減
    • 按位NOT操作
    • 操作優先順序的差異
    • 未匯出的結構體不會被編碼
    • 有活動的Goroutines下的應用退出
    • 向無快取的Channel傳送訊息,只要目標接收者準備好就會立即返回
    • 向已關閉的Channel傳送會引起Panic
    • 使用"nil" Channels
    • 傳值方法的接收者無法修改原有的值
  • 進階篇
    • 關閉HTTP的響應
    • 關閉HTTP的連線
    • 比較Structs, Arrays, Slices, and Maps
    • 從Panic中恢復
    • 在Slice, Array, and Map "range"語句中更新引用元素的值
    • 在Slice中"隱藏"資料
    • Slice的資料“毀壞”
    • "走味的"Slices
    • 型別宣告和方法
    • 從"for switch"和"for select"程式碼塊中跳出
    • "for"宣告中的迭代變數和閉包
    • Defer函式呼叫引數的求值
    • 被Defer的函式呼叫執行
    • 失敗的型別斷言
    • 阻塞的Goroutine和資源洩露
  • 高階篇
    • 使用指標接收方法的值的例項
    • 更新Map的值
    • "nil" Interfaces和"nil" Interfaces的值
    • 棧和堆變數
    • GOMAXPROCS, 併發, 和並行
    • 讀寫操作的重排順序
    • 優先排程

初級篇

開大括號不能放在單獨的一行

  • level: beginner

在大多數其他使用大括號的語言中,你需要選擇放置它們的位置。Go的方式不同。你可以為此感謝下自動分號的注入(沒有預讀)。是的,Go中也是有分號的:-)

失敗的例子:

package main

import "fmt"

func main()  
{ //error, can't have the opening brace on a separate line
    fmt.Println("hello there!")
}

編譯錯誤:

/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {

有效的例子:

package main

import "fmt"

func main() {  
    fmt.Println("works!")
}

未使用的變數

  • level: beginner

如果你有未使用的變數,程式碼將編譯失敗。當然也有例外。在函式內一定要使用宣告的變數,但未使用的全域性變數是沒問題的。

如果你給未使用的變數分配了一個新的值,程式碼還是會編譯失敗。你需要在某個地方使用這個變數,才能讓編譯器愉快的編譯。

Fails:

package main

var gvar int //not an error

func main() {  
    var one int   //error, unused variable
    two := 2      //error, unused variable
    var three int //error, even though it's assigned 3 on the next line
    three = 3     
}

Compile Errors:

/tmp/sandbox473116179/main.go:6: one declared and not used /tmp/sandbox473116179/main.go:7: two declared and not used /tmp/sandbox473116179/main.go:8: three declared and not used

Works:

package main

import "fmt"

func main() {  
    var one int
    _ = one

    two := 2
    fmt.Println(two)

    var three int
    three = 3
    one = three

    var four int
    four = four
}

另一個選擇是註釋掉或者移除未使用的變數 :-)

未使用的Imports

  • level: beginner

如果你引入一個包,而沒有使用其中的任何函式、介面、結構體或者變數的話,程式碼將會編譯失敗。

如果你真的需要引入的包,你可以新增一個下劃線標記符, _,來作為這個包的名字,從而避免編譯失敗。下滑線標記符用於引入,但不使用。

Fails:

package main

import (  
    "fmt"
    "log"
    "time"
)

func main() {  
}

Compile Errors:

/tmp/sandbox627475386/main.go:4: imported and not used: "fmt" /tmp/sandbox627475386/main.go:5: imported and not used: "log" /tmp/sandbox627475386/main.go:6: imported and not used: "time"

Works:

package main

import (  
    _ "fmt"
    "log"
    "time"
)

var _ = log.Println

func main() {  
    _ = time.Now
}

另一個選擇是移除或者註釋掉未使用的imports :-)

簡式的變數宣告僅可以在函式內部使用

  • level: beginner

Fails:

package main

myvar := 1 //error

func main() {  
}

Compile Error:

/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body

Works:

package main

var myvar = 1

func main() {  
}

使用簡式宣告重複宣告變數

  • level: beginner

你不能在一個單獨的宣告中重複宣告一個變數,但在多變數宣告中這是允許的,其中至少要有一個新的宣告變數。

重複變數需要在相同的程式碼塊內,否則你將得到一個隱藏變數。

Fails:

package main

func main() {  
    one := 0
    one := 1 //error
}

Compile Error:

/tmp/sandbox706333626/main.go:5: no new variables on left side of :=

Works:

package main

func main() {  
    one := 0
    one, two := 1,2

    one,two = two,one
}

偶然的變數隱藏Accidental Variable Shadowing

  • level: beginner

短式變數宣告的語法如此的方便(尤其對於那些使用過動態語言的開發者而言),很容易讓人把它當成一個正常的分配操作。如果你在一個新的程式碼塊中犯了這個錯誤,將不會出現編譯錯誤,但你的應用將不會做你所期望的事情。

package main

import "fmt"

func main() {  
    x := 1
    fmt.Println(x)     //prints 1
    {
        fmt.Println(x) //prints 1
        x := 2
        fmt.Println(x) //prints 2
    }
    fmt.Println(x)     //prints 1 (bad if you need 2)
}

即使對於經驗豐富的Go開發者而言,這也是一個非常常見的陷阱。這個坑很容易挖,但又很難發現。

不使用顯式型別,無法使用“nil”來初始化變數

  • level: beginner

“nil”標誌符用於表示interface、函式、maps、slices和channels的“零值”。如果你不指定變數的型別,編譯器將無法編譯你的程式碼,因為它猜不出具體的型別。

Fails:

package main

func main() {  
    var x = nil //error

    _ = x
}

Compile Error:

/tmp/sandbox188239583/main.go:4: use of untyped nil

Works:

package main

func main() {  
    var x interface{} = nil

    _ = x
}

使用“nil” Slices and Maps

  • level: beginner

在一個“nil”的slice中新增元素是沒問題的,但對一個map做同樣的事將會生成一個執行時的panic。

Works:

package main

func main() {  
    var s []int
    s = append(s,1)
}

Fails:

package main

func main() {  
    var m map[string]int
    m["one"] = 1 //error

}

Map的容量

  • level: beginner

你可以在map建立時指定它的容量,但你無法在map上使用cap()函式。

Fails:

package main

func main() {  
    m := make(map[string]int,99)
    cap(m) //error
}

Compile Error:

/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap

字串不會為“nil”

  • level: beginner

這對於經常使用“nil”分配字串變數的開發者而言是個需要注意的地方。

Fails:

package main

func main() {  
    var x string = nil //error

    if x == nil { //error
        x = "default"
    }
}

Compile Errors:

/tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)

Works:

package main

func main() {  
    var x string //defaults to "" (zero value)

    if x == "" {
        x = "default"
    }
}

Array函式的引數

-level: beginner

如果你是一個C或則C++開發者,那麼陣列對你而言就是指標。當你向函式中傳遞陣列時,函式會參照相同的記憶體區域,這樣它們就可以修改原始的資料。Go中的陣列是數值,因此當你向函式中傳遞陣列時,函式會得到原始陣列資料的一份複製。如果你打算更新陣列的資料,這將會是個問題。

package main

import "fmt"

func main() {  
    x := [3]int{1,2,3}

    func(arr [3]int) {
        arr[0] = 7
        fmt.Println(arr) //prints [7 2 3]
    }(x)

    fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
}

如果你需要更新原始陣列的資料,你可以使用陣列指標型別。

package main

import "fmt"

func main() {  
    x := [3]int{1,2,3}

    func(arr *[3]int) {
        (*arr)[0] = 7
        fmt.Println(arr) //prints &[7 2 3]
    }(&x)

    fmt.Println(x) //prints [7 2 3]