English | 繁體中文

Go / Golang

Go 是一種具備垃圾回收、明確錯誤值、結構型介面、輕量級 goroutine 與通道的編譯型靜態型別語言。偏好簡單、易讀的程式碼;執行 gofmt,並讓 go vet/測試引導重構。

工具鏈與模組

go mod init example.com/myapp       # 建立 go.mod
go get example.com/lib@v1.2.3       # 新增或變更相依項
go mod tidy                         # 同步 go.mod/go.sum
go run .                           # 編譯並執行
go build ./...                     # 建置所有套件
go test ./... -race -cover         # 測試、競態偵測器、涵蓋率
go vet ./...                       # 可疑的建構
gofmt -w .                         # 標準格式化
go doc fmt.Println                  # 套件/API 文件
go env GOPATH GOMOD GOVERSION

一個目錄通常對應一個套件。一個模組可以包含多個套件;internal/ 會限制只能從其父系樹狀目錄匯入;cmd/ 通常包含可執行檔。套件名稱應簡短且使用小寫。匯出的識別字以大寫字母開頭,並應附有文件註解。

語法與核心型別

package main
 
import "fmt"
 
const (
    MaxRetries = 3
    _          = iota // iota 是無型別整數常數產生器
)
 
var count int = 1
name := "Go" // 短宣告;只能在函式內使用
 
// 零值:0、false、""、nil 指標/切片/映射/函式/介面/通道。
var (
    n    int
    ok   bool
    text string
    nums []int       // nil 切片可以安全地進行 range 與 append
    table map[string]int
)
 
// 內建型別:bool;有號/無號整數;float32/64;complex64/128;
// string(不可變的位元組,通常是 UTF-8);byte = uint8;rune = int32。
// 轉換必須明確指定:int64(n)、float64(n)、[]byte(text)、string(data)。
 
var (
    a [3]int              // 陣列:長度是其型別的一部分
    s = []int{1, 2, 3}    // 切片:指標、長度、容量
    m = map[string]int{}  // 映射;查找會回傳(值、是否找到)
)
s = append(s, 4)
copy(s[1:], s[:2])
for key, value := range m { _ = key; _ = value }
 
// 結構體將具名欄位分組;嵌入會提升欄位/方法。
type User struct {
    ID   int    `json:"id"` // 結構標籤,由反射/套件讀取
    Name string `json:"name"`
}
u := User{ID: 1, Name: "Ada"}
ptr := &u
ptr.Name = "Grace" // 選擇器會自動解參照
 
// 指標:*T;取址:&x;解參照:*p。Go 沒有指標算術。

控制流程

if value, found := m["go"]; found {
    fmt.Println(value)
} else {
    fmt.Println("missing")
}
 
for i := 0; i < 3; i++ {}       // 唯一的迴圈關鍵字
for condition() {}              // while 風格
for range s {}                  // 迭代值/索引
switch name {
case "Go":
    // 不會隱含貫穿到下一個 case
case "Rust", "C":
    // 以逗號分隔的 case
 default:
}
 
// 可使用 `break`、`continue` 以及帶標籤的 break/continue。
// `goto` 存在,但很少適用。

函式、方法與介面

func divide(a, b int) (quotient int, err error) {
    if b == 0 { return 0, fmt.Errorf("divide by zero") }
    return a / b, nil
}
 
func sum(values ...int) int { // 在函式內,變動參數 values 是 []int
    total := 0
    for _, v := range values { total += v }
    return total
}
 
func (u User) Label() string { return u.Name }       // 值接收者
func (u *User) Rename(name string) { u.Name = name } // 指標接收者
 
// 介面是隱式滿足的:型別只需要具備所要求的方法。
type Stringer interface { String() string }
 
func printAny(v any) {
    switch x := v.(type) { // 型別 switch
    case string:
        fmt.Println(x)
    case int:
        fmt.Println(x)
    default:
        fmt.Printf("%T\n", x)
    }
}
 
// `any` 是 interface{} 的別名;可行時請使用更窄的介面。
// 斷言:text, ok := v.(string)。未使用 `ok` 的失敗斷言會觸發 panic。

泛型

type Number interface { ~int | ~int64 | ~float64 }
func Sum[T Number](xs []T) T {
    var total T
    for _, x := range xs { total += x }
    return total
}

~int 包含底層型別為 int 的具名型別。型別參數可以由介面加以限制;泛型方法不允許引入自身的型別參數。

錯誤與資源安全

錯誤是普通值。回傳它們;使用 %w 增加上下文;使用 errors.Is/errors.As 進行檢查。

if err != nil {
    return fmt.Errorf("load user %d: %w", id, err)
}
if errors.Is(err, context.Canceled) { /* 取消是預期情況 */ }
var pathErr *os.PathError
if errors.As(err, &pathErr) { /* 處理具型別的錯誤 */ }

取得資源後應立即使用 defer:defer file.Close()、defer mu.Unlock()。延遲呼叫會以 LIFO 順序執行,並擷取 defer 陳述式當下的引數。panic 用於不可恢復的程式設計錯誤/不變量違反,而非例行驗證。recover 只有在同一個 goroutine 的 deferred 函式中才有效。

對不受信任的檔案系統路徑,請在邊界進行驗證(filepath.IsLocal、filepath.Localize);在 Go 1.24 及更新版本中,也可考慮使用 os.OpenRoot/Root.OpenInRoot,將存取限制在某個目錄內。

並行

  • go f(x) 會在 goroutine 中啟動 f;它與呼叫者共享記憶體。
  • 通道會傳遞值並進行同步:make(chan T)、具緩衝的 make(chan T, n)。
  • 通常由傳送者關閉通道;從已關閉的通道接收時會得到零值,且 ok == false。
  • select 會等待一個已就緒的通道操作;case <-ctx.Done() 用於處理取消。
  • 第一次使用後絕不要複製 sync.Mutex。保護共享狀態,或使用通道/原子操作。
  • sync.WaitGroup 會等待 goroutine 加入;sync.Once 只初始化一次;sync.Pool 用於暫時可重用的物件;sync.Map 是專門用途,不是預設的映射替代品。
  • 小型獨立計數器/狀態請使用 sync/atomic。執行 go test -race ./...;競態是正確性錯誤。
  • 每個 goroutine 都需要終止路徑。將 context.Context 作為第一個參數,取消衍生的 context,並避免 goroutine/通道洩漏。
  • nil 通道會永久阻塞;將通道變數設為 nil 可以停用 select case。向已關閉的通道傳送會 panic;重複關閉會 panic。select { default: } 是非阻塞的。

可編譯範例:可取消的 worker pool、錯誤、泛型與 JSON

將其儲存為模組中的 main.go,然後執行 go run .(適用於目前的 Go 版本)。

package main
 
import (
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)
 
type Job struct{ ID int; Text string }
type Result struct{ ID int; Length int; Err error }
 
// 泛型輔助函式:適用於任何元素型別。
func Map[T, U any](xs []T, f func(T) U) []U {
    out := make([]U, len(xs))
    for i, x := range xs { out[i] = f(x) }
    return out
}
 
func worker(ctx context.Context, jobs <-chan Job, results chan<- Result, processed *atomic.Int64, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        select {
        case <-ctx.Done():
            return
        case job, ok := <-jobs:
            if !ok { return }
            if job.Text == "" {
                results <- Result{ID: job.ID, Err: errors.New("empty text")}
                continue
            }
            // 模擬 I/O,同時保持可取消。
            select {
            case <-ctx.Done():
                return
            case <-time.After(1 * time.Millisecond):
            }
            processed.Add(1)
            results <- Result{ID: job.ID, Length: len([]rune(job.Text))}
        }
    }
}
 
func run(ctx context.Context, jobs []Job, workerCount int) ([]Result, int64, error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
 
    jobCh := make(chan Job)
    resultCh := make(chan Result)
    var processed atomic.Int64
    var wg sync.WaitGroup
    wg.Add(workerCount)
    for i := 0; i < workerCount; i++ {
        go worker(ctx, jobCh, resultCh, &processed, &wg)
    }
 
    go func() {
        defer close(jobCh)
        for _, job := range jobs {
            select {
            case jobCh <- job:
            case <-ctx.Done():
                return
            }
        }
    }()
    go func() {
        wg.Wait()
        close(resultCh)
    }()
 
    results := make([]Result, 0, len(jobs))
    for result := range resultCh {
        results = append(results, result)
        if result.Err != nil {
            cancel() // 第一個錯誤工作出現後停止剩餘工作
        }
    }
    for _, result := range results {
        if result.Err != nil {
            return results, processed.Load(), fmt.Errorf("job %d: %w", result.ID, result.Err)
        }
    }
    return results, processed.Load(), nil
}
 
func main() {
    jobs := []Job{{1, "gopher"}, {2, "並發"}, {3, ""}}
    results, processed, err := run(context.Background(), jobs, 2)
    payload, marshalErr := json.Marshal(results)
    if marshalErr != nil { panic(marshalErr) }
    fmt.Printf("processed=%d results=%s err=%v\n", processed, payload, err)
 
    lengths := Map([]string{"go", "is", "fun"}, func(s string) int { return len(s) })
    fmt.Println("lengths:", lengths)
}

此範例展示了只接收/只傳送的通道型別、取消、通道所有權/關閉、WaitGroup、原子狀態、泛型函式、多重回傳值、包裝錯誤、Unicode rune 計數與 JSON。它會刻意將空工作回報為錯誤,同時乾淨地關閉 worker。

標準函式庫索引

  • 文字/資料:strings、bytes、strconv、unicode/utf8、encoding/json、encoding/csv、regexp。
  • 檔案/路徑:io、os、path/filepath、bufio、embed。
  • HTTP/網路:net/http、net/url、net、httptest;務必為用戶端/伺服器設定逾時。
  • 時間/context:time、context;在呼叫鏈中傳遞 context,不要將其儲存在結構體中。
  • 集合/演算法:sort、slices、maps、container/heap。
  • 系統/並行:sync、sync/atomic、runtime、os/signal。
  • 可觀測性:log/slog、expvar、runtime/pprof、net/http/pprof。

測試、效能與設計檢查清單

func TestSum(t *testing.T) {
    got := Sum([]int{1, 2, 3})
    if got != 6 { t.Fatalf("got %d, want 6", got) }
}

使用表格驅動測試、子測試(t.Run)、基準測試(go test -bench .)、模糊測試(go test -fuzz=.)以及用於 HTTP 的 httptest。測試公開行為時,偏好黑箱套件測試(package x_test)。最佳化前先進行基準測試;使用 -benchmem、pprof 與配置檔分析。避免不必要的配置;重複建構字串時使用 strings.Builder,但應先以清晰易讀為優先。

慣用法: 使用由使用者擁有的小型介面;接受介面並回傳具體型別;以組合/嵌入取代繼承;讓建構函式驗證不變量;讓零值可用;避免全域可變狀態;為匯出的 API 撰寫文件;使用 defer 進行清理;除非有刻意且充分的理由,絕不忽略錯誤;明確管理 goroutine 的所有權與關閉。