LabHub

ブログ

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

한국어English日本語

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

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

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

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フレームワーク

コメント

まだコメントはありません。

ログインするとコメントできます