Skip to content
Published on

Go言語完全ガイド2025:ゴルーチン、チャネル、インターフェースからプロダクションパターンまで

Authors

はじめに:2025年(ねん)、なぜGoなのか?

Kubernetes、Docker、Terraform、Prometheus、Istio — クラウドネイティブエコシステムの中核(ちゅうかく)ツールはすべてGoで書(か)かれています。2025年(ねん)のStack Overflow調査(ちょうさ)でGoは「最(もっと)も学(まな)びたい言語(げんご)」3位(い)、「最(もっと)も高年収(こうねんしゅう)の言語(げんご)」4位(い)にランクインし、Indeed基準(きじゅん)でGo開発者(かいはつしゃ)の求人(きゅうじん)は前年比(ぜんねんひ)15%増加(ぞうか)しました。

Goが選(えら)ばれる理由(りゆう)は明確(めいかく)です:

  • シンプルさ: キーワード25個(こ)、クラスなし、継承(けいしょう)なし
  • 並行性(へいこうせい): ゴルーチンとチャネルで数百万(すうひゃくまん)の同時(どうじ)タスク処理(しょり)
  • 高速(こうそく)コンパイル: 数百万行(すうひゃくまんぎょう)のコードも数秒(すうびょう)でコンパイル
  • 単一(たんいつ)バイナリ: 依存関係(いぞんかんけい)のない静的(せいてき)バイナリでデプロイが極(きわ)めてシンプル
  • パフォーマンス: C/C++に近(ちか)い実行速度(じっこうそくど)、Java/Pythonよりメモリ効率的(こうりつてき)
Goを使用する企業とプロジェクト:
┌───────────────────────────────────────────────────────────┐
Google       - 内部インフラ、gRPC、Kubernetes原著者       │
Uber         - 高性能ジオフェンシングサービス(Go書換え) │
Cloudflare   - エッジプロキシ、DNS、Workersランタイム     │
Twitch       - リアルタイムチャット(数百万同時接続)     │
Docker       - コンテナランタイム全体がGo                 │
Terraform    - HashiCorp全製品がGoCockroachDB  - 分散SQLデータベース                        │
Dropbox      - パフォーマンス重要サービスをPythonからGoへ │
└───────────────────────────────────────────────────────────┘

このガイドでは、Goの基礎(きそ)文法(ぶんぽう)から並行性(へいこうせい)パターン、プロダクションレベルサーバー構築(こうちく)、CLIツール開発(かいはつ)、Docker最適化(さいてきか)まで体系的(たいけいてき)にカバーします。


1. Go基礎(きそ):型(かた)、構造体(こうぞうたい)、メソッド、ポインタ

1.1 基本型(きほんがた)とゼロ値(ち)

Goのすべての変数(へんすう)は宣言時(せんげんじ)にゼロ値(ち)で初期化(しょきか)されます:

package main

import "fmt"

func main() {
    // 基本型とゼロ値
    var i int        // 0
    var f float64    // 0.0
    var b bool       // false
    var s string     // "" (空文字列)

    // 短縮宣言(型推論)
    name := "gopher"        // string
    age := 10               // int
    pi := 3.14              // float64
    active := true           // bool

    // 定数
    const MaxRetries = 3
    const (
        StatusOK    = 200
        StatusError = 500
    )

    fmt.Println(i, f, b, s, name, age, pi, active)
}

1.2 スライス vs 配列(はいれつ)

配列(はいれつ)は固定(こてい)サイズで、スライスは動的(どうてき)サイズの配列(はいれつ)ビューです:

// 配列: サイズが型の一部
var arr [5]int              // [0, 0, 0, 0, 0]
matrix := [2][3]int{{1,2,3}, {4,5,6}}

// スライス: 動的、実務で99%使用
slice := []int{1, 2, 3}
slice = append(slice, 4, 5) // [1, 2, 3, 4, 5]

// makeで作成(長さ、容量)
buf := make([]byte, 0, 1024) // 長さ0、容量1024

// スライシング(元の配列を共有するので注意!)
original := []int{1, 2, 3, 4, 5}
sub := original[1:3]        // [2, 3] - 元のビュー
sub[0] = 99                 // originalも変更: [1, 99, 3, 4, 5]

// 安全なコピー
copied := make([]int, len(original))
copy(copied, original)

1.3 マップ

// マップ作成
m := map[string]int{
    "apple":  5,
    "banana": 3,
}

// 存在確認 (comma okパターン)
val, ok := m["cherry"]
if !ok {
    fmt.Println("cherry not found")
}

// イテレーション(順序保証なし)
for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

// 削除
delete(m, "apple")

1.4 構造体(こうぞうたい)とメソッド

type User struct {
    ID        int
    Name      string
    Email     string
    CreatedAt time.Time
}

// 値レシーバーメソッド(読み取り専用)
func (u User) FullName() string {
    return u.Name
}

// ポインタレシーバーメソッド(変更可能)
func (u *User) UpdateEmail(email string) {
    u.Email = email
}

// コンストラクタパターン(Goにはコンストラクタがないので関数で代替)
func NewUser(name, email string) *User {
    return &User{
        Name:      name,
        Email:     email,
        CreatedAt: time.Now(),
    }
}

1.5 ポインタ

Goのポインタはcとは異(こと)なりポインタ演算(えんざん)ができないため安全(あんぜん)です:

func main() {
    x := 42
    p := &x        // アドレス取得
    fmt.Println(*p) // デリファレンス: 42
    *p = 100
    fmt.Println(x)  // 100

    // nilポインタチェック
    var ptr *int
    if ptr != nil {
        fmt.Println(*ptr)
    }
}

// 値渡し vs ポインタ渡し
func doubleValue(n int) { n *= 2 }       // 元の値不変
func doublePointer(n *int) { *n *= 2 }   // 元の値変更

2. インターフェースとコンポジション

2.1 暗黙的(あんもくてき)インターフェース(ダックタイピング)

GoのインターフェースはJava/C#と異(こと)なり明示的(めいじてき)な宣言(せんげん)が不要(ふよう)です。メソッドを実装(じっそう)すると自動的(じどうてき)にインターフェースを満(み)たします:

// インターフェース定義
type Writer interface {
    Write(p []byte) (n int, err error)
}

type Reader interface {
    Read(p []byte) (n int, err error)
}

// インターフェース合成
type ReadWriter interface {
    Reader
    Writer
}

// MyBufferは明示的な宣言なしにWriterを満たす
type MyBuffer struct {
    data []byte
}

func (b *MyBuffer) Write(p []byte) (int, error) {
    b.data = append(b.data, p...)
    return len(p), nil
}

// io.Writerを受け取る関数にMyBufferを渡せる
func SaveToWriter(w Writer, content string) error {
    _, err := w.Write([]byte(content))
    return err
}

2.2 小(ちい)さなインターフェースの力(ちから)

Go標準(ひょうじゅん)ライブラリの核心(かくしん)哲学(てつがく)は小(ちい)さなインターフェースです:

Go標準ライブラリ コアインターフェース:
┌──────────────────────────────────────────────────┐
│ io.Reader      - Read(p []byte) (n, err)│ io.Writer      - Write(p []byte) (n, err)│ io.Closer      - Close() error                   │
│ fmt.Stringer   - String() string                 │
│ error          - Error() string                  │
│ sort.Interface - Len, Less, Swap│ http.Handler   - ServeHTTP(w, r)│ json.Marshaler - MarshalJSON() ([]byte, err)└──────────────────────────────────────────────────┘
// 実用例: どのReaderでも処理可能
func CountLines(r io.Reader) (int, error) {
    scanner := bufio.NewScanner(r)
    count := 0
    for scanner.Scan() {
        count++
    }
    return count, scanner.Err()
}

// ファイル、HTTPレスポンス、文字列などすべて可能
lines1, _ := CountLines(os.Stdin)
lines2, _ := CountLines(resp.Body)
lines3, _ := CountLines(strings.NewReader("hello\nworld"))

2.3 構造体(こうぞうたい)エンベディング(コンポジション)

Goは継承(けいしょう)の代(か)わりにエンベディングでコードを再利用(さいりよう)します:

type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

// DogはAnimalをエンベッド(継承ではなくコンポジション)
type Dog struct {
    Animal       // エンベディング
    Breed string
}

func main() {
    d := Dog{
        Animal: Animal{Name: "Buddy"},
        Breed:  "Labrador",
    }
    fmt.Println(d.Speak()) // Animalのメソッドがプロモート
    fmt.Println(d.Name)    // Animalのフィールドに直接アクセス
}

3. 並行性(へいこうせい):ゴルーチンとチャネル

3.1 ゴルーチン

ゴルーチンはGoランタイムが管理(かんり)する軽量(けいりょう)スレッドです。OSスレッドが約(やく)1MBスタックなのに対(たい)し、ゴルーチンは約(やく)2KBから開始(かいし)します:

func main() {
    // ゴルーチン開始: goキーワードを付けるだけ
    go func() {
        fmt.Println("Hello from goroutine!")
    }()

    // 1000個のゴルーチンを同時実行
    for i := 0; i < 1000; i++ {
        go func(id int) {
            fmt.Printf("Worker %d\n", id)
        }(i) // iを引数で渡す(クロージャ変数キャプチャに注意)
    }

    time.Sleep(time.Second)
}

3.2 チャネル

チャネルはゴルーチン間(かん)でデータを安全(あんぜん)に渡(わた)すパイプです:

func main() {
    // バッファなしチャネル(同期)
    ch := make(chan string)

    go func() {
        ch <- "hello"  // 送信(受信者が来るまでブロック)
    }()

    msg := <-ch  // 受信(送信者が来るまでブロック)
    fmt.Println(msg)

    // バッファ付きチャネル(非同期)
    buffered := make(chan int, 3)
    buffered <- 1  // ブロックされない(バッファに余裕)
    buffered <- 2
    buffered <- 3
    // buffered <- 4  // ここでブロック(バッファ満杯)
}

// プロデューサー・コンシューマーパターン
func producer(ch chan<- int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch) // 送るデータがもうない
}

func consumer(ch <-chan int) {
    for val := range ch { // closeされるまで受信
        fmt.Println(val)
    }
}

3.3 Select文(ぶん)

selectは複数(ふくすう)のチャネル操作(そうさ)を同時(どうじ)に待機(たいき)します:

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "from ch1"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "from ch2"
    }()

    // 先に準備できたチャネルから受信
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    case msg := <-ch2:
        fmt.Println(msg)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout!")
    }
}

3.4 syncパッケージ

// WaitGroup: 複数のゴルーチン完了を待機
func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }

    wg.Wait() // すべてのゴルーチンが完了するまで待機
}

// Mutex: 共有リソースの保護
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// Once: 一度だけ実行
var once sync.Once
var instance *Database

func GetDB() *Database {
    once.Do(func() {
        instance = connectDB()
    })
    return instance
}

3.5 context.Context

contextはゴルーチンのライフタイムを管理(かんり)し、キャンセルシグナルとタイムアウトを伝播(でんぱ)します:

func fetchURL(ctx context.Context, url string) (string, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return "", err
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    return string(body), err
}

func main() {
    // 3秒タイムアウト
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    result, err := fetchURL(ctx, "https://api.example.com/data")
    if errors.Is(err, context.DeadlineExceeded) {
        fmt.Println("Request timed out!")
        return
    }
    fmt.Println(result)
}

4. 並行性(へいこうせい)パターン

4.1 ファンアウト / ファンイン

複数(ふくすう)のゴルーチンが作業(さぎょう)を分散処理(ぶんさんしょり)(ファンアウト)し、結果(けっか)を1つにまとめる(ファンイン)パターン:

func fanOut(input <-chan int, workers int) []<-chan int {
    channels := make([]<-chan int, workers)
    for i := 0; i < workers; i++ {
        channels[i] = process(input)
    }
    return channels
}

func process(input <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range input {
            out <- n * n
        }
    }()
    return out
}

func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    merged := make(chan int)

    for _, ch := range channels {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for val := range c {
                merged <- val
            }
        }(ch)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()

    return merged
}

4.2 ワーカープール

func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
    var wg sync.WaitGroup

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            for job := range jobs {
                result := processJob(job)
                results <- result
            }
        }(i)
    }

    go func() {
        wg.Wait()
        close(results)
    }()
}

4.3 パイプライン

func generator(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

// パイプライン組み合わせ
func main() {
    nums := generator(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    squares := square(nums)

    for result := range squares {
        fmt.Println(result)
    }
}

4.4 レートリミッターとセマフォ

// レートリミッター
func rateLimitedFetch(urls []string, rps int) {
    ticker := time.NewTicker(time.Second / time.Duration(rps))
    defer ticker.Stop()

    for _, url := range urls {
        <-ticker.C // 毎秒rps回だけ実行
        go fetch(url)
    }
}

// セマフォパターン(同時実行数制限)
func semaphorePattern(tasks []Task, maxConcurrent int) {
    sem := make(chan struct{}, maxConcurrent)

    var wg sync.WaitGroup
    for _, task := range tasks {
        wg.Add(1)
        sem <- struct{}{} // スロット獲得

        go func(t Task) {
            defer wg.Done()
            defer func() { <-sem }() // スロット返却
            process(t)
        }(task)
    }
    wg.Wait()
}

4.5 errgroupパターン

import "golang.org/x/sync/errgroup"

func fetchAll(ctx context.Context, urls []string) ([]string, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make([]string, len(urls))

    for i, url := range urls {
        i, url := i, url
        g.Go(func() error {
            body, err := fetchURL(ctx, url)
            if err != nil {
                return fmt.Errorf("fetching %s: %w", url, err)
            }
            results[i] = body
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

5. エラー処理(しょり)

5.1 基本(きほん)エラーパターン

Goは例外(れいがい)(exception)の代(か)わりに値(あたい)でエラーを返(かえ)します:

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

func readConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("reading config %s: %w", path, err)
    }
    return config, nil
}

5.2 センチネルエラーとカスタムエラー

// センチネルエラー(パッケージレベル変数)
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
)

// カスタムエラー型
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}

// errors.Is / errors.Asの使用
func handleError(err error) {
    if errors.Is(err, ErrNotFound) {
        // 404処理
    }

    var valErr *ValidationError
    if errors.As(err, &valErr) {
        fmt.Printf("Field: %s, Message: %s\n", valErr.Field, valErr.Message)
    }
}

5.3 Defer、Panic、Recover

// defer: 関数終了時に逆順で実行(リソースクリーンアップに必須)
func readFile(path string) (string, error) {
    f, err := os.Open(path)
    if err != nil {
        return "", err
    }
    defer f.Close()

    data, err := io.ReadAll(f)
    return string(data), err
}

// panic/recover: 本当に回復不可能な状況でのみ使用
func safeExecute(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    fn()
    return nil
}

6. ジェネリクス(Go 1.18+)

6.1 基本構文(きほんこうぶん)

func Map[T any, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
evens := Filter(nums, func(n int) bool { return n%2 == 0 })

6.2 型(かた)制約(せいやく)

type Number interface {
    ~int | ~int32 | ~int64 | ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// ジェネリック構造体
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

7. テスト

7.1 テーブル駆動(くどう)テスト

Goの慣用的(かんようてき)テストパターン:

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"zero", 0, 0, 0},
        {"mixed", -1, 5, 4},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d, want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

7.2 HTTPテスト

func TestGetUserHandler(t *testing.T) {
    handler := http.HandlerFunc(GetUserHandler)

    req := httptest.NewRequest("GET", "/users/123", nil)
    rec := httptest.NewRecorder()

    handler.ServeHTTP(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)

    var user User
    err := json.NewDecoder(rec.Body).Decode(&user)
    require.NoError(t, err)
    assert.Equal(t, "123", user.ID)
}

7.3 ベンチマークとファジング

func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

func FuzzParseJSON(f *testing.F) {
    f.Add([]byte(`{"name": "test"}`))

    f.Fuzz(func(t *testing.T, data []byte) {
        var result map[string]interface{}
        if err := json.Unmarshal(data, &result); err != nil {
            return
        }
        _, err := json.Marshal(result)
        if err != nil {
            t.Errorf("re-marshal failed: %v", err)
        }
    })
}
go test ./...                          # 全テスト
go test -v -run TestUser ./pkg/user    # 特定テスト
go test -bench=. ./...                 # ベンチマーク
go test -fuzz=FuzzParseJSON ./...      # ファジング
go test -race ./...                    # レースディテクター
go test -cover ./...                   # カバレッジ

8. REST API構築(こうちく)

8.1 Chiルーター + ミドルウェア

package main

import (
    "encoding/json"
    "net/http"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

type Server struct {
    router *chi.Mux
    db     *sql.DB
}

func NewServer(db *sql.DB) *Server {
    s := &Server{
        router: chi.NewRouter(),
        db:     db,
    }
    s.routes()
    return s
}

func (s *Server) routes() {
    s.router.Use(middleware.Logger)
    s.router.Use(middleware.Recoverer)
    s.router.Use(middleware.Timeout(30 * time.Second))

    s.router.Route("/api/v1", func(r chi.Router) {
        r.Get("/health", s.handleHealth)
        r.Route("/users", func(r chi.Router) {
            r.Get("/", s.handleListUsers)
            r.Post("/", s.handleCreateUser)
            r.Route("/{userID}", func(r chi.Router) {
                r.Get("/", s.handleGetUser)
                r.Put("/", s.handleUpdateUser)
                r.Delete("/", s.handleDeleteUser)
            })
        })
    })
}

func respondJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

9. gRPCサービス構築(こうちく)

9.1 Protocol Buffers定義(ていぎ)

syntax = "proto3";
package user.v1;
option go_package = "gen/user/v1;userv1";

service UserService {
    rpc GetUser(GetUserRequest) returns (GetUserResponse);
    rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
    rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
    rpc StreamUpdates(StreamUpdatesRequest) returns (stream UserEvent);
}

message User {
    string id = 1;
    string name = 2;
    string email = 3;
    int64 created_at = 4;
}

9.2 gRPCサーバー実装(じっそう)

type userServer struct {
    userv1.UnimplementedUserServiceServer
    repo UserRepository
}

func (s *userServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
    user, err := s.repo.GetByID(ctx, req.Id)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            return nil, status.Error(codes.NotFound, "user not found")
        }
        return nil, status.Error(codes.Internal, "internal error")
    }
    return &userv1.GetUserResponse{User: toProtoUser(user)}, nil
}

// サーバーストリーミング
func (s *userServer) StreamUpdates(req *userv1.StreamUpdatesRequest, stream userv1.UserService_StreamUpdatesServer) error {
    for event := range s.eventCh {
        if err := stream.Send(event); err != nil {
            return err
        }
    }
    return nil
}

10. CLIツール開発(かいはつ)

10.1 Cobra + Viper

var rootCmd = &cobra.Command{
    Use:   "mytool",
    Short: "A powerful CLI tool",
}

var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy application",
    Args:  cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        env := args[0]
        image, _ := cmd.Flags().GetString("image")
        replicas, _ := cmd.Flags().GetInt("replicas")
        dryRun, _ := cmd.Flags().GetBool("dry-run")

        fmt.Printf("Deploying to %s: image=%s, replicas=%d, dry-run=%v\n",
            env, image, replicas, dryRun)
        return nil
    },
}

func init() {
    deployCmd.Flags().StringP("image", "i", "", "Container image (required)")
    deployCmd.Flags().IntP("replicas", "r", 1, "Number of replicas")
    deployCmd.Flags().Bool("dry-run", false, "Dry run mode")
    deployCmd.MarkFlagRequired("image")
    rootCmd.AddCommand(deployCmd)
}

11. プロダクションデプロイ

11.1 Dockerマルチステージビルド(5MBバイナリ)

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-s -w" -o /app/server ./cmd/server

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
# ビルド結果サイズ比較
# Go scratch:  ~5-10MB
# Go alpine:   ~15MB
# Node.js:     ~200MB+
# Java Spring: ~300MB+
# Python:      ~150MB+

11.2 Graceful Shutdown

func main() {
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      router,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
    }

    go func() {
        log.Printf("Server starting on :8080")
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("Shutting down gracefully...")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Forced shutdown: %v", err)
    }
    log.Println("Server stopped")
}

11.3 pprofプロファイリング

import _ "net/http/pprof"

func main() {
    go func() {
        log.Println(http.ListenAndServe(":6060", nil))
    }()
}
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30  # CPU
go tool pprof http://localhost:6060/debug/pprof/heap                # メモリ
go tool pprof http://localhost:6060/debug/pprof/goroutine           # ゴルーチン

12. Go vs Rust vs Java比較(ひかく)

┌─────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ 項目            │ GoRustJava├─────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 学習曲線        │ 低い(1-2週間)  │ 高い(3-6ヶ月)  │ 中程度(1-2ヶ月)│
│ コンパイル速度  │ 非常に速い       │ 遅い             │ 普通             │
│ 実行速度        │ 速い             │ 非常に速い       │ 速い(JIT)      │
│ メモリ管理      │ GC               │ 所有権システム   │ GC│ 並行性          │ ゴルーチン/Chan  │ async/tokio      │ Virtual Thread│ バイナリサイズ  │ 5-15MB           │ 1-5MB            │ 100MB+JRE)    │
│ エコシステム    │ クラウド/インフラ │ システム/WASM    │ エンタープライズ │
│ エラー処理      │ 値返却           │ Result/Option    │ 例外             │
│ ジェネリクス    │ 1.18+(基本)    │ 強力             │ 完全サポート     │
│ 主要用途        │ マイクロサービス │ OS、ゲーム      │ 大規模エンタ     │
│                 │ CLI、DevOps      │ エンジン         │ プライズ         │
└─────────────────┴──────────────────┴──────────────────┴──────────────────┘

Goが適(てき)している場合(ばあい): マイクロサービス、APIサーバー、CLIツール、DevOpsツール Rustが適(てき)している場合(ばあい): システムプログラミング、ゲームエンジン、組込(くみこ)み Javaが適(てき)している場合(ばあい): 大規模(だいきぼ)エンタープライズ、Androidアプリ


13. 面接(めんせつ)質問(しつもん)15選(せん)

基礎(きそ)

  1. Goでスライスと配列(はいれつ)の違(ちが)いは? 配列(はいれつ)は固定(こてい)サイズで型(かた)の一部(いちぶ)です(例(れい):[5]int)。スライスは動的(どうてき)サイズの配列(はいれつ)ビューで、内部的(ないぶてき)にポインタ、長(なが)さ、容量(ようりょう)を持(も)ちます。

  2. Goに継承(けいしょう)がない理由(りゆう)は?コード再利用(さいりよう)はどうする? 構造体(こうぞうたい)エンベディング(コンポジション)を使用(しよう)します。「継承(けいしょう)よりコンポジション」の原則(げんそく)を言語(げんご)レベルで強制(きょうせい)します。

  3. nilインターフェースとnilポインタを持(も)つインターフェースの違(ちが)いは? インターフェースは(type, value)ペアです。両方(りょうほう)nilならnilインターフェース。typeがあるがvalueがnilならnon-nilインターフェースです。

  4. deferの実行(じっこう)順序(じゅんじょ)と注意点(ちゅういてん)は? LIFO(後入先出(あといれさきだし))順(じゅん)で実行(じっこう)されます。defer時点(じてん)の引数値(ひきすうち)がキャプチャされるため、ループでのdefer使用(しよう)に注意(ちゅうい)が必要(ひつよう)です。

  5. Goのゼロ値(ち)哲学(てつがく)を説明(せつめい)してください。 すべての変数(へんすう)が宣言時(せんげんじ)に有効(ゆうこう)なデフォルト値(ち)を持(も)つため、初期化(しょきか)されていない変数(へんすう)のバグを防(ふせ)ぎます。

並行性(へいこうせい)

  1. ゴルーチンがOSスレッドより軽(かる)い理由(りゆう)は? ゴルーチンは約(やく)2KBスタックで開始(かいし)(OSスレッドは1MB+)、GoランタイムのM:Nスケジューラが数千(すうせん)のゴルーチンを少数(しょうすう)のOSスレッドにマッピングします。

  2. バッファチャネルとアンバッファチャネルの違(ちが)いは? アンバッファチャネルは送信(そうしん)と受信(じゅしん)が同期的(どうきてき)に会(あ)う必要(ひつよう)があります。バッファチャネルは容量(ようりょう)分(ぶん)だけ非同期(ひどうき)送信(そうしん)可能(かのう)です。

  3. ゴルーチンリークが発生(はっせい)する状況(じょうきょう)と防止法(ぼうしほう)は? チャネルに送受信(そうじゅしん)する相手(あいて)がいない場合(ばあい)や、contextキャンセルを処理(しょり)しない場合(ばあい)に発生(はっせい)します。context.WithCancel/Timeoutの使用(しよう)が鍵(かぎ)です。

  4. デッドロックの条件(じょうけん)とGoでの検出方法(けんしゅつほうほう)は? Goランタイムがすべてのゴルーチンがブロックされたことを検出(けんしゅつ)してpanicします。go vetとrace detectorも役立(やくだ)ちます。

  5. sync.Mutex vs sync.RWMutexの使(つか)い分(わ)け基準(きじゅん)は? 読(よ)み取(と)りが書(か)き込(こ)みよりはるかに多(おお)い場合(ばあい)はRWMutexが有利(ゆうり)です。複数(ふくすう)のゴルーチンが同時(どうじ)に読(よ)み取(と)れるためです。

上級(じょうきゅう)

  1. context.Contextの主要(しゅよう)な用途(ようと)3つは? キャンセル伝播(でんぱ)(Cancel)、タイムアウト(Timeout/Deadline)、値渡(あたいわた)し(WithValue)。HTTPハンドラーとDB呼出(よびだ)しに常(つね)に使用(しよう)すべきです。

  2. GoのGC特性(とくせい)は? 並行(へいこう)マーク&スイープGCを使用(しよう)し、STW時間(じかん)が1ms未満(みまん)です。GOGC環境変数(かんきょうへんすう)で調整可能(ちょうせいかのう)です。

  3. ジェネリクス導入前後(どうにゅうぜんご)のコードの違(ちが)いは? 導入前(どうにゅうまえ):interfaceと型(かた)アサーションまたはコード生成(せいせい)が必要(ひつよう)。導入後(どうにゅうご):型(かた)パラメータでコンパイル時(じ)型安全性(かたあんぜんせい)を提供(ていきょう)。

  4. Goでの依存性注入(いぞんせいちゅうにゅう)(DI)はどうする? フレームワークなしでコンストラクタ関数(かんすう)を通(つう)じてインターフェースを注入(ちゅうにゅう)します。wire(Google)、fx(Uber)などのDIフレームワークもあります。

  5. Goモジュールのindirect依存関係管理(いぞんかんけいかんり)は? go.modのindirectコメントは直接(ちょくせつ)インポートしない依存関係(いぞんかんけい)です。go mod tidyで整理(せいり)し、go mod vendorでベンダリングできます。


14. 実践(じっせん)クイズ

Q1: このコードの出力(しゅつりょく)は?
func main() {
    for i := 0; i < 3; i++ {
        go func() {
            fmt.Println(i)
        }()
    }
    time.Sleep(time.Second)
}

答(こた)え: Go 1.22以前(いぜん)ではほとんど3, 3, 3が出力(しゅつりょく)されます。クロージャがループ変数(へんすう)iを参照(さんしょう)し、ゴルーチン実行時(じっこうじ)にはすでにループが終了(しゅうりょう)してi=3です。Go 1.22からは各(かく)反復(はんぷく)でループ変数(へんすう)が新(あたら)しく作成(さくせい)され、0, 1, 2(順序(じゅんじょ)不定(ふてい))が出力(しゅつりょく)されます。

Q2: このコードでデッドロックが発生(はっせい)する理由(りゆう)は?
func main() {
    ch := make(chan int)
    ch <- 42
    fmt.Println(<-ch)
}

答(こた)え: アンバッファチャネルへの送信(そうしん)は受信者(じゅしんしゃ)が来(く)るまでブロックされます。メインゴルーチンがch <- 42でブロックされ、<-chに到達(とうたつ)できないためデッドロックです。解決策(かいけつさく):別(べつ)のゴルーチンで送信(そうしん)するかバッファチャネルを使用(しよう)。

Q3: nilインターフェース問題(もんだい) - なぜerr != nilがtrueなのか?
type MyError struct{}
func (e *MyError) Error() string { return "error" }

func doSomething() error {
    var err *MyError = nil
    return err
}

func main() {
    err := doSomething()
    fmt.Println(err == nil) // false!
}

答(こた)え: doSomething()はnilポインタを持(も)つ*MyError型(がた)のインターフェースを返(かえ)します。インターフェースは(type=*MyError, value=nil)なのでnilではありません。解決策(かいけつさく):関数(かんすう)から明示的(めいじてき)にreturn nilを返(かえ)します。

Q4: WaitGroup使用時(しようじ)のよくある間違(まちが)いは?
func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        go func(id int) {
            wg.Add(1) // 間違った位置!
            defer wg.Done()
            fmt.Println(id)
        }(i)
    }
    wg.Wait()
}

答(こた)え: wg.Add(1)がゴルーチン内(ない)にあるため、wg.Wait()がどのAddよりも先(さき)に実行(じっこう)される可能性(かのうせい)があります。正(ただ)しい方法(ほうほう)はゴルーチン開始前(かいしまえ)にwg.Add(1)を呼(よ)び出(だ)すことです。

Q5: contextキャンセルが伝播(でんぱ)する仕組(しく)みを説明(せつめい)してください。

答(こた)え: context.WithCancelは親(おや)コンテキストのDoneチャネルを監視(かんし)する子(こ)コンテキストを作成(さくせい)します。親(おや)がキャンセルされると子(こ)のDoneチャネルも閉(と)じられます。このチェーンは最上位(さいじょうい)まで伝播(でんぱ)し、1つのキャンセルでリクエストツリー全体(ぜんたい)をクリーンアップできます。ゴルーチンはselect文(ぶん)でctx.Done()を確認(かくにん)して適切(てきせつ)に終了(しゅうりょう)すべきです。


参考資料(さんこうしりょう)

  1. Go公式ドキュメント
  2. Effective Go
  3. Go by Example
  4. Go Concurrency Patterns (Rob Pike)
  5. Advanced Go Concurrency Patterns
  6. The Go Programming Language (Donovan & Kernighan)
  7. Concurrency in Go (Katherine Cox-Buday)
  8. Go Wiki: Table Driven Tests
  9. Go Wiki: Code Review Comments
  10. Uber Go Style Guide
  11. Go Module Reference
  12. Go Generics Tutorial
  13. pprofガイド
  14. Chi Router
  15. gRPC Go公式ガイド
  16. Cobra CLIライブラリ
  17. BubbleTea TUIフレームワーク