Day 1: Go言語の核心 - 講義

今日の目標

  • Go言語の設計思想と哲学を理解する
  • 他言語との違いを明確にする
  • Goの型システムと暗黙の変換の非存在を理解する
  • ゼロ値の概念とその重要性を学ぶ
  • ポインタと値の違いを完全に理解する

---

Go言語とは何か

設計思想

Go言語は2009年にGoogle社が開発した、シンプルさと効率性を追求したプログラミング言語です。

主要な設計原則

  • シンプルさ: 複雑な機能を削ぎ落とし、理解しやすい言語仕様
  • 並行性: ゴルーチンとチャネルによる軽量な並行処理
  • 高速なコンパイル: 大規模プロジェクトでも数秒でビルド
  • 静的型付け: コンパイル時の型安全性
  • ガベージコレクション: メモリ管理の自動化

他言語との比較

特徴 Go C/C++ Java Python Rust
コンパイル速度 -
実行速度
メモリ安全性 ×
並行処理
学習コスト × ×

Goが「持たない」もの

Goは意図的に以下の機能を持ちません

// クラス → 構造体とメソッドで代替
// 継承 → 埋め込みとインターフェースで代替
// ジェネリクス → Go 1.18から追加(限定的)
// 例外 → 明示的なエラー処理
// アノテーション/デコレータ → なし
// マクロ → なし
// デフォルト引数 → なし
// 演算子オーバーロード → なし

これらは「不要」ではなく、シンプルさを保つための選択です。

---

型システム

静的型付けと型推論

package main

import "fmt"

func main() {
    // 明示的な型指定
    var age int = 25
    var name string = "太郎"

    // 型推論(:=)
    age2 := 25  // int型と推論
    name2 := "太郎"  // string型と推論

    // 型推論後は型が固定
    age2 = 30  // OK
    // age2 = "30"  // コンパイルエラー

    fmt.Println(age, name, age2, name2)
}

暗黙の型変換の非存在

重要: Goは暗黙の型変換を一切行いません

package main

import "fmt"

func main() {
    var i int = 10
    var f float64 = 3.14

    // これはエラー
    // result := i + f  // コンパイルエラー

    // 明示的な変換が必要
    result := float64(i) + f
    fmt.Println(result)

    // intとint64も別の型
    var a int = 10
    var b int64 = 20
    // c := a + b  // エラー
    c := int64(a) + b
    fmt.Println(c)
}

基本型の詳細

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    // 整数型のサイズ
    var i8 int8       // -128 to 127
    var i16 int16     // -32768 to 32767
    var i32 int32     // -2^31 to 2^31-1
    var i64 int64     // -2^63 to 2^63-1

    // 符号なし整数
    var u8 uint8      // 0 to 255
    var u16 uint16    // 0 to 65535
    var u32 uint32    // 0 to 2^32-1
    var u64 uint64    // 0 to 2^64-1

    // プラットフォーム依存の整数(32bit or 64bit)
    var i int
    var u uint

    // 浮動小数点
    var f32 float32   // IEEE-754 32-bit
    var f64 float64   // IEEE-754 64-bit

    // 複素数
    var c64 complex64
    var c128 complex128

    // 文字列(UTF-8エンコード、イミュータブル)
    var s string = "Hello, 世界"

    // rune(int32のエイリアス、Unicodeコードポイント)
    var r rune = '世'

    // byte(uint8のエイリアス)
    var b byte = 255

    fmt.Printf("int size: %d bytes\n", unsafe.Sizeof(i))
    fmt.Printf("string: %s, type: %v\n", s, reflect.TypeOf(s))
    fmt.Printf("rune: %c, value: %d\n", r, r)

    _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ = i8, i16, i32, i64, u8, u16, u32, u64, i, u, f32, f64, c64, c128, r, b
}

---

ゼロ値の概念

Go言語の最も重要な特徴の1つ: すべての変数は宣言時にゼロ値で初期化されます。

package main

import "fmt"

func main() {
    // ゼロ値
    var i int           // 0
    var f float64       // 0.0
    var b bool          // false
    var s string        // ""(空文字列)
    var p *int          // nil
    var slice []int     // nil
    var m map[string]int // nil
    var ch chan int     // nil

    fmt.Printf("int: %v\n", i)
    fmt.Printf("float64: %v\n", f)
    fmt.Printf("bool: %v\n", b)
    fmt.Printf("string: %q\n", s)
    fmt.Printf("pointer: %v\n", p)
    fmt.Printf("slice: %v\n", slice)
    fmt.Printf("map: %v\n", m)
    fmt.Printf("chan: %v\n", ch)

    // ゼロ値は有効な値として使える
    if i == 0 {
        fmt.Println("iはゼロ値")
    }

    // しかし、nilのマップとチャネルは使用不可
    // m["key"] = 1  // パニック
    m = make(map[string]int)  // 初期化が必要
    m["key"] = 1
}

ゼロ値の利点

  • 未初期化変数によるバグを防ぐ
  • 明示的な初期化が不要な場合が多い
  • 構造体のフィールドが自動的に初期化される

構造体とゼロ値

package main

import "fmt"

type Config struct {
    Host    string
    Port    int
    Timeout int
    Debug   bool
}

func main() {
    // すべてのフィールドがゼロ値で初期化される
    var config Config
    fmt.Printf("%+v\n", config)
    // {Host: Port:0 Timeout:0 Debug:false}

    // 一部だけ指定
    config2 := Config{
        Host: "localhost",
        Port: 8080,
        // TimeoutとDebugはゼロ値
    }
    fmt.Printf("%+v\n", config2)
    // {Host:localhost Port:8080 Timeout:0 Debug:false}

    // ゼロ値を利用したデフォルト値パターン
    if config2.Timeout == 0 {
        config2.Timeout = 30  // デフォルト30秒
    }
}

---

ポインタと値

ポインタの基本

package main

import "fmt"

func main() {
    x := 42

    // ポインタの取得(&)
    p := &x
    fmt.Printf("xの値: %d\n", x)
    fmt.Printf("xのアドレス: %p\n", p)

    // デリファレンス(*)
    fmt.Printf("ポインタが指す値: %d\n", *p)

    // ポインタ経由で値を変更
    *p = 100
    fmt.Printf("変更後のx: %d\n", x)
}

値渡しとポインタ渡し

package main

import "fmt"

func modifyValue(x int) {
    x = 100
}

func modifyPointer(x *int) {
    *x = 100
}

func main() {
    a := 42
    b := 42

    modifyValue(a)
    fmt.Println("値渡し後:", a)  // 42(変更されない)

    modifyPointer(&b)
    fmt.Println("ポインタ渡し後:", b)  // 100(変更される)
}

構造体とポインタ

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func modifyValue(p Person) {
    p.Age = 100
}

func modifyPointer(p *Person) {
    p.Age = 100
    // (*p).Age = 100  // これでもOKだが、Goは自動でデリファレンスしてくれる
}

func main() {
    person1 := Person{Name: "太郎", Age: 25}
    person2 := Person{Name: "花子", Age: 22}

    modifyValue(person1)
    fmt.Printf("person1: %+v\n", person1)  // Age: 25

    modifyPointer(&person2)
    fmt.Printf("person2: %+v\n", person2)  // Age: 100
}

newとmake

package main

import "fmt"

func main() {
    // new: ゼロ値で初期化し、ポインタを返す
    p := new(int)
    fmt.Printf("型: %T, 値: %v, ポインタが指す値: %v\n", p, p, *p)
    // 型: *int, 値: 0xc000012098, ポインタが指す値: 0

    // 構造体の場合
    type Person struct {
        Name string
        Age  int
    }

    person := new(Person)
    fmt.Printf("%+v\n", person)  // &{Name: Age:0}
    person.Name = "太郎"
    fmt.Printf("%+v\n", person)  // &{Name:太郎 Age:0}

    // make: スライス、マップ、チャネルの初期化専用
    slice := make([]int, 5)       // 長さ5のスライス
    m := make(map[string]int)     // マップ
    ch := make(chan int)          // チャネル

    fmt.Printf("slice: %v\n", slice)
    fmt.Printf("map: %v\n", m)
    fmt.Printf("chan: %v\n", ch)
}

newとmakeの違い

  • new(T): 任意の型Tでゼロ値を割り当て、Tを返す
  • make(T, args): スライス、マップ、チャネル専用。初期化済みのTを返す

---

スライスの内部構造

スライスは参照型

package main

import "fmt"

func modifySlice(s []int) {
    s[0] = 999
}

func main() {
    original := []int{1, 2, 3, 4, 5}
    fmt.Println("変更前:", original)

    modifySlice(original)
    fmt.Println("変更後:", original)  // [999 2 3 4 5]

    // スライスは内部で配列へのポインタを持つ
}

スライスの構造

スライスは以下の3つの要素で構成されます:

type slice struct {
    ptr *array  // 内部配列へのポインタ
    len int     // 長さ
    cap int     // 容量
}

package main

import "fmt"

func main() {
    s := make([]int, 5, 10)  // 長さ5、容量10

    fmt.Printf("長さ: %d, 容量: %d\n", len(s), cap(s))

    // 容量内なら再割り当てなし
    s = append(s, 1, 2, 3, 4, 5)
    fmt.Printf("append後 - 長さ: %d, 容量: %d\n", len(s), cap(s))

    // 容量を超えると新しい配列が割り当てられる
    s = append(s, 6)
    fmt.Printf("容量超過後 - 長さ: %d, 容量: %d\n", len(s), cap(s))
}

スライスの落とし穴

package main

import "fmt"

func main() {
    // 問題1: 部分スライスは元の配列を共有
    original := []int{1, 2, 3, 4, 5}
    sub := original[1:4]

    sub[0] = 999
    fmt.Println("original:", original)  // [1 999 3 4 5]
    fmt.Println("sub:", sub)            // [999 3 4]

    // 解決策: コピーを作る
    original2 := []int{1, 2, 3, 4, 5}
    sub2 := make([]int, 3)
    copy(sub2, original2[1:4])

    sub2[0] = 999
    fmt.Println("original2:", original2)  // [1 2 3 4 5]
    fmt.Println("sub2:", sub2)            // [999 3 4]
}

---

マップの詳細

マップの内部実装

マップはハッシュテーブルとして実装されています。

package main

import "fmt"

func main() {
    // nilマップ
    var m1 map[string]int
    fmt.Println("m1 == nil:", m1 == nil)  // true

    // nilマップは読み取りOK、書き込みNG
    fmt.Println("m1['key']:", m1["key"])  // 0(ゼロ値)
    // m1["key"] = 1  // パニック

    // makeで初期化
    m2 := make(map[string]int)
    m2["key"] = 1  // OK

    // 容量のヒント(最適化)
    m3 := make(map[string]int, 100)  // 100要素分を事前確保
    _ = m3
}

マップの安全な使用

package main

import "fmt"

func main() {
    m := map[string]int{
        "a": 1,
        "b": 2,
    }

    // 値と存在確認
    value, exists := m["a"]
    fmt.Printf("a: value=%d, exists=%v\n", value, exists)

    value, exists = m["c"]
    fmt.Printf("c: value=%d, exists=%v\n", value, exists)

    // 削除
    delete(m, "a")
    fmt.Println("削除後:", m)

    // 削除済みを再度削除してもエラーにならない
    delete(m, "a")
}

---

エラー処理の哲学

エラーは値

Goは例外を投げません。代わりに、エラーをとして返します。

package main

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("ゼロ除算エラー")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("エラー:", err)
        return
    }
    fmt.Println("結果:", result)

    result, err = divide(10, 0)
    if err != nil {
        fmt.Println("エラー:", err)
        return
    }
    fmt.Println("結果:", result)
}

早期リターンパターン

package main

import (
    "fmt"
    "os"
)

func processFile(filename string) error {
    // エラーがあればすぐに返す
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    // ... 処理 ...

    return nil
}

func main() {
    if err := processFile("test.txt"); err != nil {
        fmt.Println("エラー:", err)
    }
}

---

Go言語の歴史と設計思想の深掘り

Goの誕生背景

Go言語は2007年、Google社のRobert Griesemer、Rob Pike、Ken Thompsonによって設計が開始されました。2009年11月10日にオープンソースプロジェクトとして公開され、2012年3月にバージョン1.0がリリースされました。

開発の動機

  • コンパイル時間の長さ: C++の大規模プロジェクトでは、フルビルドに数時間かかることも
  • 言語の複雑さ: C++やJavaは機能が多すぎて、チーム全体が言語仕様を理解するのが困難
  • 並行処理の難しさ: マルチコアCPUが普及したが、既存言語では並行処理が複雑
  • 依存関係の管理: ヘッダーファイルの循環依存など、管理が困難
  • ガベージコレクションの欠如: C/C++では手動メモリ管理が必要

設計者の経歴と影響

Rob Pike:

  • Plan 9、UTF-8の共同開発者
  • UNIX開発チームのメンバー
  • シンプルさと明確さを重視する設計哲学

Ken Thompson:

  • UNIX、B言語、C言語の開発者
  • チューリング賞受賞者
  • 効率性とシステムプログラミングの専門家

Robert Griesemer:

  • V8 JavaScriptエンジン、HotSpot JVMの開発者
  • コンパイラ技術とガベージコレクションの専門家
  • この3人の経験が、Goの「シンプルで高速、かつ現代的」という特徴を形作りました。

    ---

    実世界での採用事例

    1. Docker(コンテナ技術)

    Dockerは2013年にGoで書き直されました。当初Pythonで書かれていましたが、以下の理由でGoに移行:

  • シングルバイナリ配布: 依存関係がないため、配布が容易
  • クロスコンパイル: Linux、Windows、macOS向けに簡単にビルド可能
  • 並行処理: コンテナの並列管理が効率的
  • 低レイヤーアクセス: システムコールやcgroupsへの直接アクセス
  • // Dockerのコンテナ管理の簡略化例
    type Container struct {
        ID     string
        Image  string
        Status string
    }
    
    type Runtime struct {
        containers map[string]*Container
        mu         sync.RWMutex
    }
    
    func (r *Runtime) Start(id string) error {
        r.mu.Lock()
        defer r.mu.Unlock()
    
        container := r.containers[id]
        // 並行処理でコンテナを起動
        go container.Run()
    
        return nil
    }
    

    2. Kubernetes(オーケストレーション)

    Kubernetesは最初からGoで開発されました。

  • スケーラビリティ: 数千ノードのクラスタ管理
  • 並行制御: 複数のコントローラーが並行動作
  • APIサーバー: 高速なREST APIの実装
  • リソース効率: メモリ使用量が少ない

統計データ:

  • コードベース: 約100万行のGo
  • コントリビューター: 3,000人以上
  • 企業採用率: Fortune 500企業の70%以上
  • 3. Prometheus(モニタリング)

    時系列データベースとモニタリングシステム。

  • 高スループット: 毎秒数百万メトリクスを処理
  • 効率的なメモリ管理: GCの最適化で低レイテンシを実現
  • 並行クエリ: 複数のクエリを並行実行
  • // Prometheusのメトリクス収集の概念例
    type Metric struct {
        Name      string
        Value     float64
        Timestamp time.Time
        Labels    map[string]string
    }
    
    type Collector struct {
        metrics chan Metric
    }
    
    func (c *Collector) Collect() {
        // 並行してメトリクスを収集
        var wg sync.WaitGroup
        for _, target := range targets {
            wg.Add(1)
            go func(t Target) {
                defer wg.Done()
                metrics := t.Scrape()
                for _, m := range metrics {
                    c.metrics <- m
                }
            }(target)
        }
        wg.Wait()
    }
    

    4. CockroachDB(分散データベース)

    PostgreSQL互換の分散SQLデータベース。

  • トランザクション処理: Goの並行処理で高スループット実現
  • ネットワークI/O: 効率的なgoroutineベースのネットワーク処理
  • 一貫性: Raft合意アルゴリズムの実装
  • 5. Vitess(YouTube/Google)

    MySQLのスケーリングを実現するデータベースクラスタリングシステム。

  • YouTubeで採用: 1日あたり数十億のクエリを処理
  • 水平スケーリング: Goの並行処理により、数千のMySQLインスタンスを管理
  • クエリルーティング: 低レイテンシのクエリ処理

---

Go開発者の市場価値とキャリアパス

年収データ(2024-2025年)

日本市場:

  • ジュニア(0-2年): 400万円〜600万円
  • ミドル(3-5年): 600万円〜900万円
  • シニア(5年以上): 900万円〜1,500万円
  • テックリード/アーキテクト: 1,200万円〜2,000万円

米国市場:

  • ジュニア: $80,000〜$120,000
  • ミドル: $120,000〜$180,000
  • シニア: $180,000〜$250,000
  • スタッフエンジニア: $250,000〜$400,000+

リモートワーク(グローバル企業):

  • ミドル: $100,000〜$150,000
  • シニア: $150,000〜$250,000

求人需要のトレンド

増加している分野:

  • クラウドインフラ: AWS、GCP、Azure向けのツール開発
  • DevOps/SRE: インフラ自動化、モニタリング
  • マイクロサービス: API Gateway、サービスメッシュ
  • ブロックチェーン: Ethereum、Hyperledger
  • エッジコンピューティング: IoT、CDN

人気企業での採用:

  • Google: Kubernetes、Go言語本体
  • Uber: マイクロサービス基盤
  • Dropbox: ストレージシステム
  • Netflix: インフラツール
  • Cloudflare: エッジネットワーク

キャリアパスの例

レベル1(0-1年): Go基礎習得
├─ 基本的なCLIツール開発
├─ REST API実装
└─ ユニットテスト作成

レベル2(1-3年): 実践的開発
├─ マイクロサービス設計
├─ データベース最適化
├─ 並行処理パターン習得
└─ Dockerコンテナ化

レベル3(3-5年): アーキテクチャ設計
├─ システム全体の設計
├─ パフォーマンスチューニング
├─ セキュリティ実装
└─ チームリード

レベル4(5年以上): エキスパート
├─ 技術選定・意思決定
├─ 大規模システム設計
├─ OSS貢献・コミュニティ活動
└─ テックブログ執筆・講演

---

プロダクション環境での考慮事項

1. スケーラビリティパターン

垂直スケーリング(スケールアップ):

// GOMAXPROCS: 使用するCPUコア数の設定
runtime.GOMAXPROCS(runtime.NumCPU())

// ワーカープールパターン
type WorkerPool struct {
    workers int
    jobs    chan Job
    results chan Result
}

func NewWorkerPool(workers int) *WorkerPool {
    wp := &WorkerPool{
        workers: workers,
        jobs:    make(chan Job, 100),
        results: make(chan Result, 100),
    }

    for i := 0; i < workers; i++ {
        go wp.worker()
    }

    return wp
}

func (wp *WorkerPool) worker() {
    for job := range wp.jobs {
        result := job.Execute()
        wp.results <- result
    }
}

水平スケーリング(スケールアウト):

  • ステートレス設計: セッション情報をRedisに保存
  • 負荷分散: Nginx、HAProxy、Envoy
  • サービス分割: マイクロサービスアーキテクチャ

2. 可観測性(Observability)

ログ:

import (
    "go.uber.org/zap"
)

// 構造化ログ
logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("user login",
    zap.String("user_id", userID),
    zap.String("ip", clientIP),
    zap.Duration("duration", duration),
)

メトリクス:

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "endpoint", "status"},
    )

    httpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "endpoint"},
    )
)

トレーシング:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/trace"
)

func processRequest(ctx context.Context, req Request) error {
    tracer := otel.Tracer("myapp")
    ctx, span := tracer.Start(ctx, "processRequest")
    defer span.End()

    // 処理の実装
    span.SetAttributes(attribute.String("request.id", req.ID))

    return nil
}

3. 障害耐性(Resilience)

サーキットブレーカー:

type CircuitBreaker struct {
    maxFailures int
    timeout     time.Duration

    mu           sync.Mutex
    failures     int
    lastFailTime time.Time
    state        string // "closed", "open", "half-open"
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    if cb.state == "open" {
        if time.Since(cb.lastFailTime) > cb.timeout {
            cb.state = "half-open"
        } else {
            return errors.New("circuit breaker is open")
        }
    }

    err := fn()
    if err != nil {
        cb.failures++
        cb.lastFailTime = time.Now()
        if cb.failures >= cb.maxFailures {
            cb.state = "open"
        }
        return err
    }

    // 成功時はリセット
    cb.failures = 0
    cb.state = "closed"
    return nil
}

リトライ戦略:

func RetryWithBackoff(fn func() error, maxRetries int) error {
    var err error
    backoff := time.Second

    for i := 0; i < maxRetries; i++ {
        err = fn()
        if err == nil {
            return nil
        }

        if i < maxRetries-1 {
            time.Sleep(backoff)
            backoff *= 2 // エクスポネンシャルバックオフ
        }
    }

    return fmt.Errorf("max retries exceeded: %w", err)
}

---

パフォーマンス最適化とプロファイリング

1. ベンチマーク測定

// ベンチマークの書き方
func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = "Hello, " + "World!"
    }
}

func BenchmarkStringBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        sb.WriteString("Hello, ")
        sb.WriteString("World!")
        _ = sb.String()
    }
}

// 実行
// go test -bench=. -benchmem

結果の読み方:

BenchmarkStringConcat-8     100000000    10.5 ns/op    0 B/op    0 allocs/op
BenchmarkStringBuilder-8    50000000     30.2 ns/op   24 B/op    2 allocs/op
  • ns/op: 1操作あたりのナノ秒
  • B/op: 1操作あたりのバイト数
  • allocs/op: 1操作あたりのメモリアロケーション数

2. CPU プロファイリング

import (
    "os"
    "runtime/pprof"
)

func main() {
    // CPUプロファイリング開始
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // プログラム実行
    doWork()
}

// 解析
// go tool pprof cpu.prof
// (pprof) top10
// (pprof) list functionName

3. メモリプロファイリング

import (
    "os"
    "runtime"
    "runtime/pprof"
)

func main() {
    doWork()

    // メモリプロファイル取得
    f, _ := os.Create("mem.prof")
    runtime.GC() // GC実行
    pprof.WriteHeapProfile(f)
    f.Close()
}

// 解析
// go tool pprof mem.prof
// (pprof) top10
// (pprof) list functionName

4. Race Detector(データ競合検出)

// 競合が発生するコード例
var counter int

func increment() {
    counter++ // データ競合!
}

func main() {
    go increment()
    go increment()
    time.Sleep(time.Second)
}

// 検出
// go run -race main.go
// go test -race ./...

---

ソフトスキルと技術的コミュニケーション

1. 設計レビューの進め方

設計書のテンプレート:

# 機能設計書: ユーザー認証システム

## 目的
既存の認証システムをJWT方式に移行し、スケーラビリティを向上させる

## 背景
- 現状: セッションベース認証(メモリストア)
- 課題: 水平スケーリング時のセッション共有が困難
- 解決策: JWT + Redisによるトークン無効化管理

## 設計概要

### アーキテクチャ図
[図を挿入]

### コンポーネント
1. AuthService: JWT生成・検証
2. TokenStore: Redisベースの無効化リスト管理
3. Middleware: HTTPリクエストの認証チェック

### インターフェース定義
go type AuthService interface { GenerateToken(userID string) (string, error) ValidateToken(token string) (
Claims, error) RevokeToken(token string) error }

### データフロー
1. ユーザーログイン → JWT発行
2. リクエスト → JWT検証 → 無効化リスト確認
3. ログアウト → JWT無効化(Redisに登録)

## セキュリティ考慮事項
- トークン有効期限: 1時間
- リフレッシュトークン: 30日
- HTTPS必須
- CORS設定

## パフォーマンス目標
- トークン検証: <5ms (p99)
- Redis照会: <2ms (p99)
- スループット: 10,000 req/s

## マイグレーション計画
1. フェーズ1: 新システム並行稼働
2. フェーズ2: トラフィック段階的移行
3. フェーズ3: 旧システム廃止

## テスト計画
- ユニットテスト: 90%以上のカバレッジ
- 統合テスト: 主要フロー全カバー
- 負荷テスト: 10,000 req/s で安定稼働確認

## リスクと対策
| リスク | 影響 | 対策 |
|--------|------|------|
| Redis障害 | 認証不可 | フォールバック機構 |
| トークン漏洩 | 不正アクセス | 短い有効期限 |

2. RFC(Request for Comments)の書き方

社内RFCの例:

# RFC-001: エラーハンドリング標準化

## ステータス
提案中 / レビュー中 / 承認済み / 却下

## 概要
プロジェクト全体で一貫したエラーハンドリングパターンを導入する

## 動機
現状、各開発者が異なるエラー処理を実装しており、以下の問題が発生:
- ログの形式が不統一
- エラーの原因追跡が困難
- クライアントへのエラーレスポンスが一貫しない

## 提案

### エラー型の定義
go type AppError struct { Code string // エラーコード(例: "USER_NOT_FOUND") Message string // ユーザー向けメッセージ Err error // 元のエラー Context map[string]interface{} // コンテキスト情報 }

func (e AppError) Error() string { return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Err) }

func (e AppError) Unwrap() error { return e.Err }


### 使用例
go func GetUser(id string) (*User, error) { user, err := db.Query(id) if err == sql.ErrNoRows { return nil, &AppError{ Code: "USER_NOT_FOUND", Message: "ユーザーが見つかりません", Err: err, Context: map[string]interface{}{"user_id": id}, } } if err != nil { return nil, &AppError{ Code: "DATABASE_ERROR", Message: "データベースエラーが発生しました", Err: err, } } return user, nil }

## 代替案
1. pkg/errorsパッケージのみ使用
2. サードパーティライブラリ(github.com/pkg/errors)

## 影響範囲
- 全サービスのエラーハンドリング修正必要
- APIレスポンス形式の変更
- ログフォーマットの統一

## 移行計画
1. 新規コードから適用開始
2. 既存コードは段階的に移行
3. 期限: 3ヶ月

## 未解決の課題
- パニック時の統一的なハンドリング方法
- エラーコードの命名規則

---

設計原則とアーキテクチャパターン

1. SOLID原則のGo実装

Single Responsibility Principle(単一責任の原則):

// 悪い例: 複数の責任を持つ
type UserService struct {
    db *sql.DB
}

func (s *UserService) CreateUser(user *User) error {
    // データベース操作
    // メール送信
    // ログ記録
    // キャッシュ更新
}

// 良い例: 責任を分離
type UserRepository interface {
    Save(user *User) error
}

type EmailService interface {
    SendWelcomeEmail(email string) error
}

type UserService struct {
    repo  UserRepository
    email EmailService
    logger Logger
}

func (s *UserService) CreateUser(user *User) error {
    if err := s.repo.Save(user); err != nil {
        return err
    }

    if err := s.email.SendWelcomeEmail(user.Email); err != nil {
        s.logger.Warn("failed to send email", err)
    }

    s.logger.Info("user created", user.ID)
    return nil
}

Dependency Inversion Principle(依存性逆転の原則):

// 高レベルモジュールが低レベルモジュールに依存しない
// どちらも抽象(インターフェース)に依存する

// 抽象
type Storage interface {
    Get(key string) ([]byte, error)
    Set(key string, value []byte) error
}

// 高レベルモジュール
type Cache struct {
    storage Storage // 抽象に依存
}

// 低レベルモジュール(実装)
type RedisStorage struct {
    client *redis.Client
}

func (r *RedisStorage) Get(key string) ([]byte, error) {
    return r.client.Get(context.Background(), key).Bytes()
}

func (r *RedisStorage) Set(key string, value []byte) error {
    return r.client.Set(context.Background(), key, value, 0).Err()
}

// 使用
func main() {
    redis := &RedisStorage{client: redis.NewClient(&redis.Options{})}
    cache := &Cache{storage: redis}
}

2. クリーンアーキテクチャ

プロジェクト構造:
cmd/
  app/
    main.go           # エントリーポイント
internal/
  domain/             # ビジネスロジック(最内層)
    user.go
    repository.go     # インターフェース定義
  usecase/            # ユースケース層
    user_interactor.go
  interface/          # インターフェースアダプター層
    repository/
      user_repository.go
    handler/
      user_handler.go
  infrastructure/     # 外部層
    database/
      postgres.go
    http/
      server.go

実装例:

// domain/user.go(ビジネスロジック)
type User struct {
    ID    string
    Email string
    Name  string
}

type UserRepository interface {
    FindByID(id string) (*User, error)
    Save(user *User) error
}

// usecase/user_interactor.go(ユースケース)
type UserInteractor struct {
    repo UserRepository
}

func (i *UserInteractor) GetUser(id string) (*User, error) {
    return i.repo.FindByID(id)
}

// interface/repository/user_repository.go(実装)
type PostgresUserRepository struct {
    db *sql.DB
}

func (r *PostgresUserRepository) FindByID(id string) (*User, error) {
    // SQL実行
}

// interface/handler/user_handler.go(HTTPハンドラー)
type UserHandler struct {
    interactor *UserInteractor
}

func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")
    user, err := h.interactor.GetUser(id)
    // レスポンス返却
}

---

Go固有のイディオムとベストプラクティス

1. Accept Interfaces, Return Structs

// 良い例
func ProcessData(r io.Reader) (*Result, error) {
    // インターフェースを受け取る → 柔軟性
    // 具象型を返す → シンプル
}

// 悪い例
func ProcessData(r *os.File) (io.Reader, error) {
    // 具象型を受け取る → 制限的
    // インターフェースを返す → 複雑
}

2. エラーラッピング(Go 1.13+)

import "fmt"

func readConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("failed to read config file: %w", err)
    }
    // ...
    return nil
}

func main() {
    err := readConfig("config.yaml")
    if err != nil {
        // エラーチェーン全体を表示
        fmt.Printf("%+v\n", err)

        // 特定のエラーを判定
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("設定ファイルが存在しません")
        }
    }
}

3. Functional Options Pattern(上級)

type Server struct {
    host    string
    port    int
    timeout time.Duration
    logger  *log.Logger
}

type Option func(*Server)

func WithHost(host string) Option {
    return func(s *Server) {
        s.host = host
    }
}

func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

func NewServer(opts ...Option) *Server {
    // デフォルト値
    s := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30 * time.Second,
        logger:  log.Default(),
    }

    // オプション適用
    for _, opt := range opts {
        opt(s)
    }

    return s
}

// 使用
func main() {
    server := NewServer(
        WithHost("0.0.0.0"),
        WithPort(3000),
        WithTimeout(60 * time.Second),
    )
}

---

参考リソース

公式ドキュメント

書籍

  • The Go Programming Language - Alan A. A. Donovan & Brian W. Kernighan
  • Concurrency in Go - Katherine Cox-Buday
  • Go in Action - William Kennedy
  • Cloud Native Go - Matthew A. Titmus

オンラインリソース

コミュニティ

---

Go言語の核心を理解することで、なぜGoがこのように設計されているのかが見えてきます。明日はこの知識を活かして、Goらしいコードを書く方法を学びましょう!