コンテンツにスキップ

Go言語を書き始めた最初の30日で学ぶべきこと

概要

Go言語を書き始めた最初の30日間で必ず押さえるべき重要概念のリスト。Yoshik氏が「これを学ぶべき」として挙げた項目を解説する。

詳細

1. goroutine + channel の基礎(Day 1 から)

Goの並行処理はgoroutineとchannelが中心。Day 1から学ぶべき理由は「Goの並行処理こそGoの本質」だから。

// goroutineの起動
go func() {
    fmt.Println("並行実行")
}()

// channelで値を渡す
ch := make(chan int)
go func() { ch <- 42 }()
val := <-ch
fmt.Println(val) // 42

なぜ重要か: 他の言語のスレッドと異なり、goroutineは軽量(数KB)で何万個も起動できる。channelで安全に値を受け渡しすることでデータ競合を防ぐ。

2. defer(クリーンアップの制御)

deferはpanicが起きても実行される。リソース解放に使う。

func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // 関数終了時に必ず実行される

    // ファイル処理...
    return nil
}

注意点: deferはLIFO(後入れ先出し)で実行される。複数のdeferがある場合は最後に登録されたものから実行。

3. エラーハンドリング

Goには例外がない。エラーは戻り値として返す。

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

result, err := divide(10, 0)
if err != nil {
    log.Printf("error: %v", err)
    return
}

パターン: 関数は (result, error) を返す。呼び出し側は必ずエラーを確認する。errors.Is() / errors.As() でエラー種別を判定する。

4. interface(暗黙の実装)

Goのinterfaceは実装を宣言する必要がない(ダックタイピング)。

type Writer interface {
    Write(p []byte) (n int, err error)
}

// File, bytes.Buffer, net.Conn など全て Writer を満たす
// 明示的な "implements Writer" の記述は不要

活用: テストでのモック作成、依存性の注入が容易になる。

5. テスト(標準パッケージ)

外部ライブラリ不要でテストが書ける。

// sum_test.go
func TestSum(t *testing.T) {
    got := Sum(1, 2)
    want := 3
    if got != want {
        t.Errorf("Sum(1, 2) = %d; want %d", got, want)
    }
}
go test ./...          # 全テスト実行
go test -run TestSum   # 特定テスト
go test -race          # データ競合検出

6. goroutineリーク

goroutineはcontextでキャンセルしないとリークする。

// 悪い例: goroutineが永遠に待ち続ける
go func() {
    <-ch // chに誰も送信しなければリーク
}()

// 良い例: contextでキャンセル可能にする
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go func() {
    select {
    case val := <-ch:
        process(val)
    case <-ctx.Done():
        return // タイムアウト or キャンセルで終了
    }
}()

7. ポインタ vs 値渡し

// 値渡し: コピーが作られる
func incrementValue(n int) int {
    return n + 1
}

// ポインタ渡し: 元の値を変更できる
func incrementPointer(n *int) {
    *n++
}

x := 10
incrementPointer(&x) // x は 11 になる

目安: 大きな構造体やミュータブルな操作はポインタ。小さな値型は値渡しでOK。

8. Go Modules

go mod init github.com/username/project  # モジュール初期化
go get github.com/some/package           # 依存追加
go mod tidy                              # 不要な依存を削除

なぜ重要か / いつ使うか

  • Goを書き始めた最初の数週間で理解すると、後で「なぜこう書くのか」で迷わなくなる
  • goroutineのリークはサービスのメモリ増大に直結する。最初からcontextの使い方を習得する
  • エラーハンドリングのパターンはGoコードのほぼ全ての関数に登場する。早期に慣れる