Go 語言常見錯誤——資料型別

FunTester發表於2025-03-03

在 Go 語言的開發中,常見的錯誤往往隱藏在細節之中,稍不注意就會引發嚴重的邏輯問題或效能瓶頸。正所謂千里之堤毀於蟻穴,這些看似不起眼的小問題,可能會讓整個專案功虧一簣。本文涵蓋了八進位制字面量的誤解、整數溢位的忽視、浮點數比較的陷阱、slice 和 map 的誤用,以及記憶體洩漏和值比較的問題。透過實際的程式碼示例和詳細解析,我們揭示了這些錯誤的潛在影響,並提供了最佳實踐解決方案。

錯誤十七:八進位制字面量引發的困惑

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    number := 0755 // 八進位制字面量
    fmt.Printf("FunTester: 許可權號碼為 %d\n", number)
}

錯誤說明:

  • 0 開頭的整數字面量被解釋為八進位制數,容易導致誤解。很多人可能會誤以為這是一個普通的十進位制數,結果鬧出笑話。

最佳實踐:

  • 顯式使用 0o 字首表示八進位制數,這樣一目瞭然,避免混淆。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    number := 0o755 // 顯式表示八進位制
    fmt.Printf("FunTester: 許可權號碼為 %d\n", number)
}

錯誤十八:未注意可能的整數溢位

示例程式碼:

package main

import (
    "fmt"
)

func calculateFunTester(a, b int) int {
    return a + b
}

func main() {
    a := 2147483647 // 最大的 int32 值
    b := 1
    result := calculateFunTester(a, b)
    fmt.Printf("FunTester: 結果為 %d\n", result) // 溢位
}

錯誤說明:

  • 整數溢位會導致數值不準確,就像水桶裝不下更多的水,結果只會溢位。

最佳實踐:

  • 檢測並處理潛在的溢位情況,防患於未然。

改進程式碼:

package main

import (
    "errors"
    "fmt"
)

func calculateFunTester(a, b int) (int, error) {
    result := a + b
    if (a > 0 && b > 0 && result < 0) || (a < 0 && b < 0 && result > 0) {
        return 0, errors.New("FunTester: 整數溢位")
    }
    return result, nil
}

func main() {
    a := 2147483647
    b := 1
    result, err := calculateFunTester(a, b)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("FunTester: 結果為 %d\n", result)
}

錯誤十九:沒有透徹理解浮點數

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    a := 0.1
    b := 0.2
    sum := a + b
    if sum == 0.3 {
        fmt.Println("FunTester: 相等")
    } else {
        fmt.Println("FunTester: 不相等") // 實際上會輸出
    }
}

錯誤說明:
浮點數在計算機中是近似表示的,就像用尺子量東西,總會有一些誤差。

最佳實踐:
使用容差範圍(delta)比較浮點數,避免因小失大。

改進程式碼:

package main

import (
    "fmt"
    "math"
)

func main() {
    a := 0.1
    b := 0.2
    sum := a + b
    delta := math.Abs(sum - 0.3)
    if delta < 1e-9 {
        fmt.Println("FunTester: 透過 delta 比較相等")
    }
}

錯誤二十:不理解 slice 的長度和容量

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    s := []int{1, 2, 3}
    fmt.Printf("FunTester: len=%d, cap=%d\n", len(s), cap(s))
}

錯誤說明:

  • len 表示當前元素數量,cap 表示底層陣列的容量。很多人只關注長度,忽略了容量,結果導致效能問題。

最佳實踐:

  • 合理設定 slice 的長度和容量,做到心中有數。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    s := make([]int, 3, 5) // 長度為3,容量為5
    fmt.Printf("FunTester: len=%d, cap=%d\n", len(s), cap(s))
}

錯誤二十一:不高效的 slice 初始化

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    var s []int
    for i := 0; i < 1000; i++ {
        s = append(s, i)
    }
    fmt.Printf("FunTester: len=%d, cap=%d\n", len(s), cap(s))
}

錯誤說明:

  • 頻繁的 append 操作會導致多次記憶體分配,就像搬家時一件一件搬,效率低下。

最佳實踐:

  • 預先設定 slice 的容量,做到未雨綢繆。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    s := make([]int, 0, 1000) // 預先設定容量
    for i := 0; i < 1000; i++ {
        s = append(s, i)
    }
    fmt.Printf("FunTester: len=%d, cap=%d\n", len(s), cap(s))
}

錯誤二十二:困惑於 nil 和空 slice

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    var nilSlice []int
    emptySlice := []int{}

    fmt.Println("FunTester: nilSlice == nil ?", nilSlice == nil)
    fmt.Println("FunTester: emptySlice == nil ?", emptySlice == nil)
}

錯誤說明:

  • nil 切片和空切片的區別容易被忽略,就像空箱子和沒有箱子,看似一樣,實則不同。

最佳實踐:

  • 統一處理 nil 和空切片,避免因小失大。

改進程式碼:

package main

import (
    "fmt"
)

func getFunTesterSlice() []int {
    return []int{} // 始終返回空切片
}

func main() {
    slice := getFunTesterSlice()
    fmt.Println("FunTester: slice 為空 ?", len(slice) == 0)
}

錯誤二十三:沒有適當檢查 slice 是否為空

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    var s []int
    if s[0] == 10 { // 執行時錯誤
        fmt.Println("FunTester: 第一個元素是 10")
    }
}

錯誤說明:

  • 未檢查 slice 是否為空直接訪問元素,就像伸手去拿一個空盒子,結果撲了個空。

最佳實踐:

  • 始終檢查 slice 的長度,做到有備無患。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    var s []int
    if len(s) > 0 && s[0] == 10 {
        fmt.Println("FunTester: 第一個元素是 10")
    } else {
        fmt.Println("FunTester: slice 為空")
    }
}

錯誤二十四:沒有正確複製 slice

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    original := []int{1, 2, 3}
    copySlice := make([]int, 2)
    copy(copySlice, original) // 只複製前兩個元素
    fmt.Printf("FunTester: copySlice = %v\n", copySlice)
}

錯誤說明:
未正確複製 slice 的所有元素,就像影印檔案只印了一半,結果不完整。

最佳實踐:
確保目標 slice 的長度足夠,做到面面俱到。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    original := []int{1, 2, 3}
    copySlice := make([]int, len(original))
    copy(copySlice, original) // 完整複製
    fmt.Printf("FunTester: copySlice = %v\n", copySlice)
}

錯誤二十五:slice append 帶來的預期之外的副作用

示例程式碼:

package main

import (
    "fmt"
)

func modifySlice(s []int) {
    s = append(s, 4)
    fmt.Println("FunTester: modifySlice 內部 s =", s)
}

func main() {
    s := []int{1, 2, 3}
    modifySlice(s)
    fmt.Println("FunTester: main 中的 s =", s) // 未被修改
}

錯誤說明:
append 可能導致底層陣列共享,就像兩個人共用一把傘,結果誰都遮不住。

最佳實踐:
顯式建立 slice 副本,做到涇渭分明。

改進程式碼:

package main

import (
    "fmt"
)

func modifySlice(s []int) {
    copied := make([]int, len(s))
    copy(copied, s)
    copied = append(copied, 4)
    fmt.Println("FunTester: modifySlice 內部 copied =", copied)
}

func main() {
    s := []int{1, 2, 3}
    modifySlice(s)
    fmt.Println("FunTester: main 中的 s =", s)
}

錯誤二十六:slice 和記憶體洩漏

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    nodes := []*int{new(int), new(int), new(int)}
    subslice := nodes[:2]

    fmt.Printf("FunTester: subslice = %v\n", subslice)
}

錯誤說明:
未釋放不可訪問的元素可能導致記憶體洩漏,就像房間裡堆滿了沒用的東西,結果越堆越多。

最佳實踐:
顯式設定不可訪問的元素為 nil,做到乾淨利落。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    nodes := []*int{new(int), new(int), new(int)}
    subslice := nodes[:2]

    for i := 2; i < len(nodes); i++ {
        nodes[i] = nil // 顯式釋放
    }

    fmt.Printf("FunTester: subslice = %v\n", subslice)
}

錯誤二十七:不高效的 map 初始化

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    m := make(map[string]int)
    for i := 0; i < 1000; i++ {
        key := fmt.Sprintf("key%d", i)
        m[key] = i
    }
    fmt.Printf("FunTester: map 大小為 %d\n", len(m))
}

錯誤說明:
未預先設定 map 的容量,導致頻繁擴容,就像開車時頻繁換擋,結果速度上不去。

最佳實踐:
預先設定 map 的容量,做到事半功倍。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    m := make(map[string]int, 1000) // 預先設定容量
    for i := 0; i < 1000; i++ {
        key := fmt.Sprintf("key%d", i)
        m[key] = i
    }
    fmt.Printf("FunTester: map 大小為 %d\n", len(m))
}

錯誤二十八:map 和記憶體洩漏

示例程式碼:

package main

import (
    "fmt"
)

func main() {
    m := make(map[int][]int, 10)
    for i := 0; i < 100; i++ {
        m[i] = make([]int, 1000)
    }

    for k := range m {
        m[k] = nil // 清空 map
    }

    fmt.Println("FunTester: map 已清空")
}

錯誤說明:
map 的 buckets 記憶體不會自動縮減,就像房間裡的垃圾,不清掃就會一直堆積。

最佳實踐:
重新建立 map 以釋放記憶體,做到一勞永逸。

改進程式碼:

package main

import (
    "fmt"
)

func main() {
    m := make(map[int][]int, 10)
    for i := 0; i < 100; i++ {
        m[i] = make([]int, 1000)
    }

    m = make(map[int][]int, 10) // 重新建立 map

    fmt.Println("FunTester: map 已重新建立,舊記憶體已釋放")
}

錯誤二十九:不正確的值比較

示例程式碼:

package main

import (
    "fmt"
    "reflect"
)

type FunTester struct {
    Name string
    Age  int
}

func main() {
    a := FunTester{Name: "FunTester", Age: 30}
    b := FunTester{Name: "FunTester", Age: 30}

    if a == b {
        fmt.Println("FunTester: a 和 b 相等")
    } else {
        fmt.Println("FunTester: a 和 b 不相等")
    }

    if reflect.DeepEqual(a, b) {
        fmt.Println("FunTester: 透過 reflect.DeepEqual 比較相等")
    }
}

錯誤說明:
== 運算子不適用於包含不可比較欄位的結構體,就像用尺子量溫度,結果不準確。

最佳實踐:
使用 reflect.DeepEqual 或自定義比較函式,做到精準無誤。

改進程式碼:

package main

import (
    "fmt"
    "reflect"
)

type FunTester struct {
    Name string
    Age  int
}

func main() {
    a := FunTester{Name: "FunTester", Age: 30}
    b := FunTester{Name: "FunTester", Age: 30}

    if reflect.DeepEqual(a, b) {
        fmt.Println("FunTester: 透過 reflect.DeepEqual 比較相等")
    }
}
FunTester 原創精華

【連載】從 Java 開始效能測試

  • 故障測試與 Web 前端
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go
  • 白盒、工具、爬蟲、UI 自動化
  • 理論、感悟、影片
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章