Go言語 チートシート

  • 変数宣言:
    // 型を指定して宣言 (ゼロ値で初期化)
    var count int
    // 初期値を指定して宣言 (型推論)
    var message = "Hello"
    // 関数内での短縮宣言 (型推論)
    value := 100
  • 複数変数宣言:
    // まとめて宣言
    var x, y int
    var a, b = 1, "text"
    c, d := true, 3.14
    
    // グループ化して宣言
    var (
        appVersion string = "1.0.0"
        debugMode  bool   = false
    )
  • 定数宣言:
    // 型なし定数 (使用時に型が決定)
    const Pi = 3.14159
    // 型付き定数
    const MaxSize int = 1024
    // 連続する定数 (iota)
    const (
        Sunday = iota // 0
        Monday        // 1
        Tuesday       // 2
    )
    // iotaの応用
    const (
        _ = 1 << (10 * iota) // 読み飛ばし
        KiB                   // 1 << (10*1) = 1024
        MiB                   // 1 << (10*2) = 1048576
        GiB                   // 1 << (10*3) = 1073741824
    )
  • if / else if / else:
    score := 75
    if score >= 80 {
        fmt.Println("Great")
    } else if score >= 60 {
        fmt.Println("Good")
    } else {
        fmt.Println("Needs Improvement")
    }
    
    // 条件判定前に簡単なステートメントを実行
    if err := process(); err != nil {
        fmt.Println("Error:", err)
    }
  • switch:
    // 基本的なswitch
    day := "Monday"
    switch day {
    case "Sunday", "Saturday":
        fmt.Println("Weekend! 😊")
    case "Monday":
        fmt.Println("Start of the week...")
    default:
        fmt.Println("Weekday")
    }
    
    // 条件式を持つswitch (if/else ifの代替)
    num := 15
    switch {
    case num < 10:
        fmt.Println("Single digit")
    case num < 100:
        fmt.Println("Double digit")
    default:
        fmt.Println("Large number")
    }
    
    // フォールスルー (fallthrough)
    value := 1
    switch value {
    case 1:
        fmt.Println("Value is 1")
        fallthrough // 次のcaseも実行
    case 2:
        fmt.Println("Value is 1 or 2")
    default:
        fmt.Println("Other value")
    }
  • for:
    // C言語風forループ
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
    
    // 条件式のみ (while風)
    n := 0
    for n < 5 {
        fmt.Println(n)
        n++
    }
    
    // 無限ループ
    // for {
    //     fmt.Println("Looping forever...")
    //     time.Sleep(1 * time.Second)
    // }
    
    // スライスやマップの反復 (range)
    nums := []int{10, 20, 30}
    for index, value := range nums {
        fmt.Printf("Index: %d, Value: %d\n", index, value)
    }
    
    // インデックスのみ
    for index := range nums {
        fmt.Println("Index:", index)
    }
    
    // 値のみ
    for _, value := range nums {
        fmt.Println("Value:", value)
    }
    
    // マップの反復
    colors := map[string]string{"red": "#ff0000", "green": "#00ff00"}
    for key, value := range colors {
        fmt.Printf("Key: %s, Value: %s\n", key, value)
    }
    
    // 文字列の反復 (rune単位)
    for index, runeValue := range "Go言語" {
        fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
    }
  • defer: 関数終了時に実行される処理を登録。リソース解放などに便利。
    func readFile(filename string) error {
        file, err := os.Open(filename)
        if err != nil {
            return err
        }
        defer file.Close() // 関数終了時に必ず実行される
    
        // ファイル処理...
        return nil
    }
  • 基本的な関数定義:
    func add(x int, y int) int {
        return x + y
    }
    
    // 引数の型が同じ場合は省略可能
    func multiply(x, y int) int {
        return x * y
    }
  • 複数の戻り値:
    func divide(numerator, denominator int) (int, error) {
        if denominator == 0 {
            return 0, fmt.Errorf("cannot divide by zero")
        }
        return numerator / denominator, nil
    }
    
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
  • 名前付き戻り値:
    // 戻り値の変数を事前に宣言
    func calculate(a, b int) (sum int, difference int) {
        sum = a + b
        difference = a - b
        // return文で変数を指定しなくても良い (naked return)
        return
    }
    
    s, d := calculate(5, 3) // s = 8, d = 2
  • 可変長引数:
    func sumNumbers(nums ...int) int {
        total := 0
        for _, num := range nums {
            total += num
        }
        return total
    }
    
    sum := sumNumbers(1, 2, 3, 4, 5) // sum = 15
    
    // スライスを渡すことも可能
    numbers := []int{10, 20}
    sum = sumNumbers(numbers...) // sum = 30
  • 無名関数 (クロージャ):
    // 変数に関数を代入
    greet := func(name string) {
        fmt.Println("Hello,", name)
    }
    greet("Alice")
    
    // クロージャ (関数が定義されたスコープの変数を参照)
    func incrementer() func() int {
        count := 0
        return func() int {
            count++
            return count
        }
    }
    
    inc := incrementer()
    fmt.Println(inc()) // 1
    fmt.Println(inc()) // 2
  • 配列 (Array): 固定長
    var arr [3]int // [0 0 0]
    arr[0] = 1
    fmt.Println(arr, len(arr)) // [1 0 0] 3
    
    arr2 := [...]string{"apple", "banana"} // 要素数から長さを推論
    fmt.Println(arr2, len(arr2)) // [apple banana] 2
  • スライス (Slice): 可変長、配列への参照
    // スライスリテラルで作成
    s1 := []int{1, 2, 3}
    fmt.Println(s1, len(s1), cap(s1)) // [1 2 3] 3 3
    
    // makeで作成 (長さと容量を指定)
    s2 := make([]int, 3, 5) // 長さ3, 容量5のスライス [0 0 0]
    fmt.Println(s2, len(s2), cap(s2)) // [0 0 0] 3 5
    
    // appendで要素追加 (容量が足りなければ新しい配列を確保)
    s1 = append(s1, 4, 5)
    fmt.Println(s1, len(s1), cap(s1)) // [1 2 3 4 5] 5 6 (容量は実行環境依存で増加)
    
    // スライスからスライスを作成 (元の配列を共有)
    s3 := s1[1:3] // インデックス1から3の手前まで [2 3]
    fmt.Println(s3, len(s3), cap(s3)) // [2 3] 2 5 (容量は元のスライスの末尾まで)
    
    s3[0] = 99 // 元のスライスs1も変更される
    fmt.Println(s1) // [1 99 3 4 5]
    
    // copyでスライスをコピー
    s4 := make([]int, len(s1))
    copiedCount := copy(s4, s1)
    fmt.Println(s4, copiedCount) // [1 99 3 4 5] 5
  • マップ (Map): キーと値のペア
    // マップリテラルで作成
    m1 := map[string]int{"apple": 100, "banana": 200}
    fmt.Println(m1) // map[apple:100 banana:200]
    
    // makeで作成
    m2 := make(map[string]string)
    m2["go"] = "Golang"
    m2["js"] = "JavaScript"
    fmt.Println(m2) // map[go:Golang js:JavaScript]
    
    // 要素へのアクセスと存在確認
    price := m1["apple"] // 100
    price, ok := m1["orange"] // price = 0 (ゼロ値), ok = false
    if ok {
        fmt.Println("Price:", price)
    } else {
        fmt.Println("Orange not found")
    }
    
    // 要素の削除
    delete(m1, "banana")
    fmt.Println(m1) // map[apple:100]
    
    // マップの反復 (順序は保証されない)
    for key, value := range m2 {
        fmt.Printf("Key: %s, Value: %s\n", key, value)
    }
  • 構造体 (Struct): 複数のフィールドを持つカスタム型
    type Person struct {
        Name string
        Age  int
    }
    
    // 構造体の初期化
    p1 := Person{Name: "Alice", Age: 30}
    p2 := Person{"Bob", 25} // フィールド名を省略 (順序が重要)
    p3 := new(Person) // ポインタを返す (*Person)、フィールドはゼロ値
    p3.Name = "Charlie"
    p3.Age = 40
    
    fmt.Println(p1, p2, *p3) // {Alice 30} {Bob 25} {Charlie 40}
    
    // フィールドへのアクセス
    fmt.Println(p1.Name) // Alice
    
    // 構造体の埋め込み (Embedding)
    type Employee struct {
        Person // 型名をそのままフィールドとして埋め込む
        EmployeeID string
        Salary     int
    }
    
    emp := Employee{
        Person: Person{Name: "David", Age: 35},
        EmployeeID: "E123",
        Salary: 50000,
    }
    
    // 埋め込まれた構造体のフィールドに直接アクセス可能
    fmt.Println(emp.Name) // David
    fmt.Println(emp.Person.Age) // 35
  • ポインタの取得とデリファレンス:
    x := 10
    p := &x // xのアドレスをポインタpに格納
    
    fmt.Println("Value of x:", x)    // 10
    fmt.Println("Address of x:", p)  // メモリアドレス (例: 0x...)
    fmt.Println("Value via p:", *p) // ポインタpが指す先の値 (デリファレンス) -> 10
    
    *p = 20 // ポインタ経由で値を変更
    fmt.Println("New value of x:", x) // 20
  • 関数でのポインタ利用: 値渡しではなく参照渡しのような効果
    func increment(val *int) {
        *val++ // ポインタが指す先の値をインクリメント
    }
    
    num := 5
    increment(&num)
    fmt.Println("Incremented num:", num) // 6
  • new関数: 型のゼロ値へのポインタを割り当てて返す
    ptr := new(int) // int型のゼロ値(0)へのポインタを返す
    fmt.Println(*ptr) // 0
    *ptr = 100
    fmt.Println(*ptr) // 100
  • メソッド定義: 特定の型に関連付けられた関数
    type Rect struct {
        Width, Height float64
    }
    
    // 値レシーバ (元の値を変更しない)
    func (r Rect) Area() float64 {
        return r.Width * r.Height
    }
    
    // ポインタレシーバ (元の値を変更できる)
    func (r *Rect) Scale(factor float64) {
        r.Width *= factor
        r.Height *= factor
    }
    
    rect := Rect{Width: 10, Height: 5}
    fmt.Println("Area:", rect.Area()) // 50.0
    
    rect.Scale(2) // ポインタレシーバでも自動的にアドレスが渡される
    fmt.Println("Scaled Rect:", rect) // {20 10}
    
    // ポインタ型変数でもメソッド呼び出し可能
    rectPtr := &Rect{Width: 3, Height: 4}
    fmt.Println("Area:", rectPtr.Area()) // 12.0 (自動的にデリファレンスされる)
    rectPtr.Scale(3)
    fmt.Println("Scaled Rect Ptr:", *rectPtr) // {9 12}
  • インターフェース定義: メソッドシグネチャの集まり
    type Shape interface {
        Area() float64
    }
    
    type Circle struct {
        Radius float64
    }
    
    func (c Circle) Area() float64 {
        return math.Pi * c.Radius * c.Radius
    }
    
    // Shapeインターフェースを満たす型はShape型の変数に代入可能
    func printArea(s Shape) {
        fmt.Printf("Area of %T: %f\n", s, s.Area())
    }
    
    r := Rect{Width: 4, Height: 5}
    c := Circle{Radius: 3}
    
    printArea(r) // Rect型はArea()メソッドを持つのでShapeインターフェースを満たす
    printArea(c) // Circle型も同様
  • 空インターフェース (`interface{}`): あらゆる型を保持できる。型アサーションや型スイッチで元の型を取り出す。
    var i interface{}
    i = "hello"
    fmt.Println(i) // hello
    
    i = 42
    fmt.Println(i) // 42
    
    // 型アサーション (Type Assertion)
    s, ok := i.(string) // string型への変換を試みる
    if ok {
        fmt.Println("It's a string:", s)
    } else {
        fmt.Println("Not a string")
    }
    
    // 型スイッチ (Type Switch)
    func checkType(v interface{}) {
        switch t := v.(type) {
        case string:
            fmt.Println("String:", t)
        case int:
            fmt.Println("Integer:", t)
        case bool:
            fmt.Println("Boolean:", t)
        default:
            fmt.Printf("Unknown type: %T\n", t)
        }
    }
    
    checkType("world")
    checkType(123)
    checkType(true)
    checkType(3.14)
  • Stringer インターフェース: `fmt`パッケージなどでオブジェクトを文字列として表現するために使われる。
    type IPAddr [4]byte
    
    // String()メソッドを実装することでStringerインターフェースを満たす
    func (ip IPAddr) String() string {
    	return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
    }
    
    addr := IPAddr{127, 0, 0, 1}
    fmt.Println(addr) // 127.0.0.1 (String()が呼ばれる)
  • `error` インターフェース: Goのエラー処理の基本。`Error()`メソッドを持つ。
    type error interface {
        Error() string
    }
  • 基本的なエラー処理: 関数の戻り値として`error`を返し、呼び出し側で`nil`チェックを行う。
    file, err := os.Open("nonexistent.txt")
    if err != nil {
        log.Printf("Failed to open file: %v\n", err)
        // エラーに応じた処理 (早期リターンなど)
        return
    }
    defer file.Close()
    // 正常系の処理
    fmt.Println("File opened successfully.")
  • `errors.New`: シンプルなエラーメッセージを持つ`error`を作成。
    err := errors.New("something went wrong")
    fmt.Println(err)
  • `fmt.Errorf`: フォーマット指定子を使ってエラーメッセージを組み立て、ラップされたエラー情報を含める。
    func readFile(path string) error {
        _, err := os.ReadFile(path)
        if err != nil {
            // %w で元のエラーをラップする
            return fmt.Errorf("error reading file %s: %w", path, err)
        }
        return nil
    }
    
    err := readFile("config.yaml")
    if err != nil {
        fmt.Println(err)
    }
  • `errors.Is`: エラーチェーンを辿り、特定のエラーが含まれているか確認。
    err1 := os.ErrNotExist // 事前定義されたエラー
    err2 := fmt.Errorf("operation failed: %w", err1)
    
    if errors.Is(err2, os.ErrNotExist) {
        fmt.Println("The file does not exist.")
    }
  • `errors.As`: エラーチェーンを辿り、特定の型のエラーが含まれているか確認し、その値を抽出。
    type MyError struct {
        Code int
        Msg  string
    }
    
    func (e *MyError) Error() string {
        return fmt.Sprintf("Error %d: %s", e.Code, e.Msg)
    }
    
    func doSomething() error {
        // 何らかの処理...
        return fmt.Errorf("wrapping custom error: %w", &MyError{Code: 500, Msg: "internal server error"})
    }
    
    err := doSomething()
    var myErr *MyError
    if errors.As(err, &myErr) {
        fmt.Printf("Caught MyError - Code: %d, Message: %s\n", myErr.Code, myErr.Msg)
    } else {
        fmt.Println("Caught a different error:", err)
    }
  • `panic` と `recover`: 予期せぬ致命的なエラー(例: 配列の範囲外アクセス)で発生。`defer`と`recover`でパニックから回復可能だが、通常のエラー処理には使わないのがベストプラクティス。
    func recoveryDemo() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Println("Recovered from panic:", r)
            }
        }()
    
        fmt.Println("Calling dangerous function...")
        causePanic()
        fmt.Println("This line will not be executed if panic occurs and is recovered.")
    }
    
    func causePanic() {
        fmt.Println("About to panic!")
        panic("something bad happened")
        fmt.Println("This line will not be executed.")
    }
    
    recoveryDemo()
  • Goroutine (ゴルーチン): `go`キーワードで関数を非同期に実行。軽量なスレッドのようなもの。
    func say(s string) {
        for i := 0; i < 3; i++ {
            time.Sleep(100 * time.Millisecond)
            fmt.Println(s)
        }
    }
    
    func main() {
        go say("World") // 新しいGoroutineでsay("World")を実行
        say("Hello")    // 現在のGoroutineでsay("Hello")を実行
    
        // main関数が終了すると他のGoroutineも終了するため、少し待つ
        // time.Sleep(500 * time.Millisecond) // より良い方法はsync.WaitGroupを使う
    }
  • Channel (チャネル): Goroutine間でデータを送受信するためのパイプ。型を持つ。
    // チャネル作成 (バッファなし)
    ch := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        ch <- "Data from goroutine" // チャネルにデータを送信 (受信されるまでブロック)
    }()
    
    fmt.Println("Waiting for data...")
    msg := <-ch // チャネルからデータを受信 (データが来るまでブロック)
    fmt.Println("Received:", msg)
    
    // バッファ付きチャネル (指定サイズまでブロックせずに送信可能)
    bufCh := make(chan int, 2)
    bufCh <- 1 // ブロックしない
    bufCh <- 2 // ブロックしない
    // bufCh <- 3 // バッファが満杯なのでブロックする
    
    fmt.Println(<-bufCh) // 1
    fmt.Println(<-bufCh) // 2
  • `select`: 複数のチャネル操作を待機し、最初に準備ができた操作を実行。
    c1 := make(chan string)
    c2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()
    
    // 2つのGoroutineからの応答を待つ
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("Received from c1:", msg1)
        case msg2 := <-c2:
            fmt.Println("Received from c2:", msg2)
        case <-time.After(3 * time.Second): // タイムアウト
            fmt.Println("Timeout waiting for messages")
            return
        // default: // 他に準備ができているチャネルがない場合に実行 (ノンブロッキング)
        //     fmt.Println("No messages ready yet")
        //     time.Sleep(100 * time.Millisecond)
        }
    }
  • `close`: チャネルを閉じる。送信側が行う。受信側はチャネルが閉じられたことを検知できる。
    jobs := make(chan int, 5)
    done := make(chan bool)
    
    go func() {
        for {
            j, more := <-jobs // moreはチャネルが開いているかを示す
            if more {
                fmt.Println("Received job", j)
            } else {
                fmt.Println("Received all jobs")
                done <- true
                return
            }
        }
    }()
    
    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("Sent job", j)
    }
    close(jobs) // これ以上送信しないことを示す
    fmt.Println("Sent all jobs and closed channel")
    
    <-done // worker goroutineの終了を待つ
  • `sync.Mutex`: 複数のGoroutineから共有リソースへのアクセスを排他制御(ロック)。
    var counter int
    var mu sync.Mutex
    var wg sync.WaitGroup
    
    func incrementCounter() {
        defer wg.Done()
        for i := 0; i < 1000; i++ {
            mu.Lock() // ロックを取得
            counter++
            mu.Unlock() // ロックを解放
        }
    }
    
    func main() {
        numGoroutines := 5
        wg.Add(numGoroutines)
        for i := 0; i < numGoroutines; i++ {
            go incrementCounter()
        }
        wg.Wait() // 全てのGoroutineの終了を待つ
        fmt.Println("Final Counter:", counter) // 期待値: 5000
    }
  • `sync.WaitGroup`: 複数のGoroutineの終了を待機。
    // 上記Mutexの例を参照
    // 1. WaitGroup変数を宣言: var wg sync.WaitGroup
    // 2. Goroutine起動前に wg.Add(1) でカウンタを増やす
    // 3. Goroutine内の処理終了時に defer wg.Done() でカウンタを減らす
    // 4. メインGoroutineで wg.Wait() でカウンタが0になるのを待つ
    
  • `context`: リクエストスコープの値、キャンセル信号、デッドラインをGoroutine間で伝播させる。
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done(): // 親コンテキストからのキャンセル通知を待つ
                fmt.Printf("Worker %d: stopping due to cancellation: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Worker %d: working...\n", id)
                time.Sleep(500 * time.Millisecond)
                // ここで ctx.Value("requestID") のような値を使うこともできる
            }
        }
    }
    
    func main() {
        // タイムアウト付きのコンテキストを作成 (例: 2秒後にキャンセル)
        // ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    
        // キャンセル関数付きのコンテキストを作成
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel() // main関数終了時に必ずキャンセルを呼ぶ
    
        // リクエストIDなどの値を付与する場合
        // requestID := "req-123"
        // ctx = context.WithValue(ctx, "requestID", requestID)
    
        go worker(ctx, 1)
        go worker(ctx, 2)
    
        // 3秒後にキャンセルシグナルを送る
        time.Sleep(3 * time.Second)
        fmt.Println("Main: sending cancellation signal...")
        cancel()
    
        // Workerが終了するのを少し待つ
        time.Sleep(1 * time.Second)
        fmt.Println("Main: finished.")
    }
  • **並行処理パターン例:**
    • **Generator:** チャネルを使って一連の値を生成する関数。
    • **Worker Pool:** 固定数のワーカーGoroutineでタスクキューを処理する。
    • **Pipeline:** 複数のステージ(Goroutine)をチャネルで繋ぎ、データを段階的に処理する。
    • **Fan-In/Fan-Out:** 複数のGoroutineにタスクを分散(Fan-Out)し、結果を一つのチャネルに集約(Fan-In)。
    • **Semaphore:** 同時にアクセスできるGoroutineの数を制限する。
  • モジュールの初期化: プロジェクトルートで実行。`go.mod`ファイルが生成される。
    go mod init example.com/myproject
  • 依存パッケージの追加: コード内で`import`し、`go build`や`go test`を実行すると自動的に`go.mod`と`go.sum`に追加される。明示的に追加・更新する場合は`go get`。
    # 最新バージョンを追加
    go get github.com/gin-gonic/gin
    
    # 特定のバージョンを追加
    go get github.com/gin-gonic/gin@v1.7.7
    
    # バージョンを更新
    go get -u github.com/gin-gonic/gin
    
    # 全ての依存関係を更新
    go get -u ./...
  • 依存関係の整理: `go.mod`を整理し、不要な依存を削除。`go.sum`も更新される。
    go mod tidy
  • 依存関係のダウンロード:
    go mod download
  • 依存関係の確認:
    go list -m all
  • ベンダーディレクトリの使用: 依存関係をプロジェクト内の`vendor`ディレクトリにコピー。ビルド時にここが参照される。
    go mod vendor
  • ファイル名と関数名の規約:
    • テストファイル: `*_test.go`
    • テスト関数: `func TestXxx(*testing.T)` (Xxxはテスト対象の関数名など、大文字で始まる)
    • ベンチマーク関数: `func BenchmarkXxx(*testing.B)`
    • Example関数: `func ExampleXxx()`
  • 基本的なテスト関数: `testing.T`を使ってエラー報告やテスト終了を行う。
    // mymath/mymath.go
    package mymath
    
    func Add(a, b int) int {
        return a + b
    }
    
    // mymath/mymath_test.go
    package mymath
    
    import "testing"
    
    func TestAdd(t *testing.T) {
        result := Add(2, 3)
        expected := 5
        if result != expected {
            t.Errorf("Add(2, 3) = %d; want %d", result, expected) // エラーを報告しテスト続行
            // t.Fatalf("Add(2, 3) = %d; want %d", result, expected) // エラーを報告しテスト終了
        }
    }
  • テストの実行:
    # カレントディレクトリのテストを実行
    go test
    
    # 詳細表示 (-v)
    go test -v
    
    # 特定のテスト関数を実行 (-run 正規表現)
    go test -v -run TestAdd
    
    # カバレッジ計測
    go test -cover
    go test -coverprofile=coverage.out
    go tool cover -html=coverage.out # HTMLで結果表示
  • テーブル駆動テスト: 複数のテストケースをまとめて記述する一般的なパターン。
    func TestAddTableDriven(t *testing.T) {
        testCases := []struct {
            name     string // テストケース名 (オプション)
            a, b     int
            expected int
        }{
            {"positive numbers", 2, 3, 5},
            {"negative numbers", -1, -5, -6},
            {"zero", 0, 0, 0},
            {"positive and negative", 10, -3, 7},
        }
    
        for _, tc := range testCases {
            // t.Run でサブテストを作成 (go test -run TestAddTableDriven/negative_numbers のように実行可能)
            t.Run(tc.name, func(t *testing.T) {
                result := Add(tc.a, tc.b)
                if result != tc.expected {
                    t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, result, tc.expected)
                }
            })
        }
    }
  • ベンチマークテスト: コードのパフォーマンスを測定。
    func BenchmarkAdd(b *testing.B) {
        // b.N はベンチマークフレームワークによって自動的に調整される実行回数
        for i := 0; i < b.N; i++ {
            Add(100, 200)
        }
    }
    # ベンチマーク実行 (-bench 正規表現)
    go test -bench .
    go test -bench BenchmarkAdd
  • Example関数: 関数の使用例を示し、godocに表示される。出力がコメントと一致するかテストされる。
    func ExampleAdd() {
        sum := Add(1, 5)
        fmt.Println(sum)
        // Output: 6
    }
  • ヘルパー関数とセットアップ/ティアダウン:
    • `t.Helper()`: テストヘルパー関数内で呼び出すと、エラー発生時のファイル/行番号がヘルパー呼び出し元になる。
    • `TestMain`: パッケージ内のテスト実行前後に一度だけ実行される特殊な関数。DB接続や一時ファイル作成などに使う。
      func TestMain(m *testing.M) {
          fmt.Println("Setting up tests...")
          // セットアップ処理
      
          exitCode := m.Run() // パッケージ内のテストを実行
      
          fmt.Println("Tearing down tests...")
          // ティアダウン処理
      
          os.Exit(exitCode)
      }
  • HTTPテスト: `net/http/httptest`パッケージでHTTPハンドラやクライアントをテスト。
    func MyHandler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, test!")
    }
    
    func TestMyHandler(t *testing.T) {
        req := httptest.NewRequest(http.MethodGet, "/test", nil)
        rr := httptest.NewRecorder() // http.ResponseWriterのモック
    
        handler := http.HandlerFunc(MyHandler)
        handler.ServeHTTP(rr, req)
    
        // ステータスコードをチェック
        if status := rr.Code; status != http.StatusOK {
            t.Errorf("handler returned wrong status code: got %v want %v",
                status, http.StatusOK)
        }
    
        // レスポンスボディをチェック
        expected := "Hello, test!\n"
        if rr.Body.String() != expected {
            t.Errorf("handler returned unexpected body: got %v want %v",
                rr.Body.String(), expected)
        }
    }
  • モックとスタブ: 外部依存(DB, APIなど)をテストダブルに置き換える。インターフェースを活用すると容易になる。`gomock`などのライブラリも利用される。
目的 パッケージ 関数/メソッド コード例 備考
ファイル全体を読み込む (推奨) `os` `ReadFile`
data, err := os.ReadFile("myfile.txt")
if err != nil { /*...*/ }
fmt.Println(string(data))
Go 1.16+。シンプルでメモリ効率が良い。
ファイル全体を書き込む (推奨) `os` `WriteFile`
data := []byte("Hello, Go!")
err := os.WriteFile("output.txt", data, 0644) // 0644はパーミッション
if err != nil { /*...*/ }
Go 1.16+。ファイルが存在しない場合は作成、存在する場合は上書き。
ファイルを開く (読み取り) `os` `Open`
file, err := os.Open("input.txt")
if err != nil { /*...*/ }
defer file.Close()
// fileを使って読み込み処理
読み取り専用 (`O_RDONLY`)。`defer file.Close()`を忘れずに。
ファイルを作成/開く (書き込み) `os` `Create`
file, err := os.Create("newfile.txt")
if err != nil { /*...*/ }
defer file.Close()
// fileを使って書き込み処理
書き込み用に開く (`O_WRONLY|O_CREATE|O_TRUNC`)。存在する場合は内容を切り捨てる。
ファイルを開く (詳細指定) `os` `OpenFile`
flag := os.O_WRONLY|os.O_CREATE|os.O_APPEND
perm := 0666
file, err := os.OpenFile("logfile.log", flag, perm)
if err != nil { /*...*/ }
defer file.Close()
`flag`で開くモード (追記、読み書きなど)、`perm`でパーミッションを指定。
ファイルから読み込む (`*os.File`) `os` `(*File).Read`
buf := make([]byte, 1024)
n, err := file.Read(buf)
if err != nil && err != io.EOF { /*...*/ }
fmt.Println(string(buf[:n]))
指定したバッファに読み込む。EOF (End Of File) はエラーではない場合がある。
ファイルへ書き込む (`*os.File`) `os` `(*File).Write`
data := []byte("Some data\n")
n, err := file.Write(data)
if err != nil { /*...*/ }
fmt.Printf("Wrote %d bytes\n", n)
バイトスライスを書き込む。
ファイルへ文字列を書き込む (`*os.File`) `os` `(*File).WriteString`
n, err := file.WriteString("Another line\n")
if err != nil { /*...*/ }
`Write([]byte(s))`の効率的な代替。
バッファリング読み込み (行単位など) `bufio` `NewScanner`
scanner := bufio.NewScanner(file)
for scanner.Scan() { // 1行ずつ読み込む
    fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil { /*...*/ }
大きなファイルを効率的に処理。区切り文字の変更も可能。
バッファリング書き込み `bufio` `NewWriter`
writer := bufio.NewWriter(file)
_, err := writer.WriteString("Buffered write.\n")
if err != nil { /*...*/ }
err = writer.Flush() // バッファの内容を書き出す
if err != nil { /*...*/ }
書き込み回数を減らし効率化。最後に`Flush()`が必要。
ファイル/ディレクトリの情報を取得 `os` `Stat`, `Lstat`
fileInfo, err := os.Stat("myfile.txt")
if err != nil { /*...*/ }
fmt.Println("Name:", fileInfo.Name())
fmt.Println("Size:", fileInfo.Size())
fmt.Println("Is Dir:", fileInfo.IsDir())
fmt.Println("Mode:", fileInfo.Mode())
fmt.Println("ModTime:", fileInfo.ModTime())
`Lstat`はシンボリックリンク自体の情報を返す。
ファイル/ディレクトリの存在確認 `os` `Stat`, `IsNotExist`
_, err := os.Stat("check.txt")
if os.IsNotExist(err) {
    fmt.Println("File does not exist.")
} else if err != nil {
    // その他のエラー (権限など)
    /*...*/
} else {
    fmt.Println("File exists.")
}
`Stat`のエラーを`errors.Is(err, os.ErrNotExist)`でチェックするのも一般的。
ディレクトリ作成 `os` `Mkdir`, `MkdirAll`
// 単一ディレクトリ作成
err := os.Mkdir("newdir", 0755)
// 中間ディレクトリもまとめて作成
err = os.MkdirAll("path/to/deep/dir", 0755)
`MkdirAll`は存在していてもエラーにならない。
ファイル/ディレクトリの削除 `os` `Remove`, `RemoveAll`
// ファイルまたは空のディレクトリを削除
err := os.Remove("file_to_delete.txt")
// ディレクトリとその中身を再帰的に削除
err = os.RemoveAll("dir_to_delete")
`RemoveAll`は慎重に使用。
ファイル/ディレクトリのリネーム/移動 `os` `Rename`
err := os.Rename("oldname.txt", "newname.txt")
err = os.Rename("file.txt", "archive/file.txt")
同じファイルシステム内でのみ動作。
  • Goの構造体 → JSON文字列 (エンコード/マーシャリング): `encoding/json.Marshal`
    type Config struct {
        Server   string `json:"serverAddress"` // タグでJSONキー名を指定
        Port     int    `json:"port"`
        Enabled  bool   `json:"enabled"`
        Users    []string `json:"users,omitempty"` // omitempty: スライスがnilか空なら出力しない
        Password string `json:"-"` // -: このフィールドはJSONに含めない
    }
    
    conf := Config{
        Server:  "localhost",
        Port:    8080,
        Enabled: true,
        // Users: []string{"admin", "guest"},
        Password: "secret",
    }
    
    jsonData, err := json.Marshal(conf)
    // jsonData, err := json.MarshalIndent(conf, "", "  ") // 整形して出力する場合
    if err != nil {
        log.Fatal("JSON marshal error:", err)
    }
    
    fmt.Println(string(jsonData))
    // 出力例 (Marshal): {"serverAddress":"localhost","port":8080,"enabled":true}
    // 出力例 (MarshalIndent):
    // {
    //   "serverAddress": "localhost",
    //   "port": 8080,
    //   "enabled": true
    // }
  • JSON文字列 → Goの構造体 (デコード/アンマーシャリング): `encoding/json.Unmarshal`
    jsonString := `{"serverAddress":"192.168.1.100","port":9000,"enabled":false,"users":["root"],"unknownField":"ignore"}`
    var loadedConf Config
    
    err := json.Unmarshal([]byte(jsonString), &loadedConf) // ポインタを渡す
    if err != nil {
        log.Fatal("JSON unmarshal error:", err)
    }
    
    fmt.Printf("Loaded Config: %+v\n", loadedConf)
    // 出力例: Loaded Config: {Server:192.168.1.100 Port:9000 Enabled:false Users:[root] Password:}
  • JSON文字列 → 不明な構造 (`map[string]interface{}`):
    jsonString := `{"name":"Alice","age":30,"city":"New York","extra":{"active":true}}`
    var data map[string]interface{} // mapまたはinterface{}にデコード
    
    err := json.Unmarshal([]byte(jsonString), &data)
    if err != nil { /*...*/ }
    
    name := data["name"].(string) // 型アサーションで値を取り出す
    age := data["age"].(float64) // JSONの数値はfloat64としてデコードされることが多い
    extraMap := data["extra"].(map[string]interface{})
    isActive := extraMap["active"].(bool)
    
    fmt.Println(name, age, isActive) // Alice 30 true
  • ストリーミングデコード (大きなJSONやリクエストボディ): `json.Decoder`
    // http.Request.Body (io.Reader) などから読み込む場合
    // dec := json.NewDecoder(r.Body)
    // err := dec.Decode(&myStruct)
    
    // ファイルから読み込む例
    file, _ := os.Open("large.json")
    defer file.Close()
    
    dec := json.NewDecoder(file) // io.Readerを渡す
    var v map[string]interface{} // または具体的な構造体
    
    err := dec.Decode(&v) // JSONオブジェクトを1つデコード
    if err != nil { /*...*/ }
    fmt.Println(v)
  • ストリーミングエンコード: `json.Encoder`
    // http.ResponseWriter (io.Writer) などに書き込む場合
    // enc := json.NewEncoder(w)
    // err := enc.Encode(myStruct)
    
    // ファイルに書き込む例
    file, _ := os.Create("output_stream.json")
    defer file.Close()
    
    enc := json.NewEncoder(file) // io.Writerを渡す
    enc.SetIndent("", "  ") // 整形出力する場合
    data := map[string]string{"hello": "world"}
    
    err := enc.Encode(data) // JSONオブジェクトを書き込む
    if err != nil { /*...*/ }
  • シンプルなHTTP GETリクエスト: `net/http.Get`
    resp, err := http.Get("https://example.com")
    if err != nil {
        log.Fatal("HTTP GET error:", err)
    }
    defer resp.Body.Close() // レスポンスボディは必ず閉じる
    
    fmt.Println("Status Code:", resp.StatusCode)
    
    if resp.StatusCode == http.StatusOK {
        bodyBytes, err := io.ReadAll(resp.Body)
        if err != nil {
            log.Fatal("Error reading response body:", err)
        }
        fmt.Println("Response Body:\n", string(bodyBytes))
    }
  • HTTPリクエストのカスタマイズ (`http.Client`, `http.NewRequest`):
    // カスタムクライアント (タイムアウト設定など)
    client := &http.Client{
        Timeout: 10 * time.Second,
    }
    
    // リクエストを作成 (メソッド, URL, ボディを指定)
    reqBody := strings.NewReader(`{"key":"value"}`) // io.Reader
    req, err := http.NewRequest(http.MethodPost, "https://httpbin.org/post", reqBody)
    if err != nil {
        log.Fatal("Error creating request:", err)
    }
    
    // ヘッダーを設定
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer your_token")
    req.Header.Set("User-Agent", "MyGoApp/1.0")
    
    // リクエストを実行
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal("HTTP Do error:", err)
    }
    defer resp.Body.Close()
    
    // レスポンス処理 (上記Getの例と同様)
    fmt.Println("Status Code:", resp.StatusCode)
    bodyBytes, _ := io.ReadAll(resp.Body)
    fmt.Println(string(bodyBytes))
  • シンプルなHTTPサーバー: `net/http.ListenAndServe`, `http.HandleFunc`
    func helloHandler(w http.ResponseWriter, r *http.Request) {
        // w: レスポンスを書き込むためのライター
        // r: リクエスト情報を持つポインタ
        fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) // URLパスから名前を取得 (例: /Alice -> Hello, Alice!)
    }
    
    func headersHandler(w http.ResponseWriter, r *http.Request) {
        for name, headers := range r.Header {
            for _, h := range headers {
                fmt.Fprintf(w, "%v: %v\n", name, h)
            }
        }
    }
    
    func main() {
        // URLパスとハンドラ関数を登録
        http.HandleFunc("/hello/", helloHandler)
        http.HandleFunc("/headers", headersHandler)
    
        fmt.Println("Starting server on :8080")
        // サーバーを起動 (アドレスとデフォルトのServeMuxを指定)
        err := http.ListenAndServe(":8080", nil)
        if err != nil {
            log.Fatal("ListenAndServe error: ", err)
        }
    }
  • HTTPサーバーのカスタマイズ (`http.Server`): タイムアウト設定など詳細な制御。
    func main() {
        mux := http.NewServeMux() // 新しいServeMuxを作成
        mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintln(w, "Custom Server Root")
        })
    
        server := &http.Server{
            Addr:         ":8081",
            Handler:      mux, // カスタムServeMuxを指定
            ReadTimeout:  10 * time.Second,
            WriteTimeout: 10 * time.Second,
            IdleTimeout:  120 * time.Second,
        }
    
        fmt.Println("Starting custom server on :8081")
        err := server.ListenAndServe()
        if err != nil && err != http.ErrServerClosed {
            log.Fatal("Server error: ", err)
        }
    }
  • TCPサーバー (低レベル): `net.Listen`, `listener.Accept`
    func handleConnection(conn net.Conn) {
        defer conn.Close()
        fmt.Printf("Accepted connection from %s\n", conn.RemoteAddr())
        buf := make([]byte, 1024)
        for {
            n, err := conn.Read(buf)
            if err != nil {
                if err != io.EOF {
                    fmt.Println("Read error:", err)
                }
                break // EOF またはエラーでループ終了
            }
            fmt.Printf("Received: %s", string(buf[:n]))
            // エコーバック
            _, err = conn.Write([]byte("Echo: " + string(buf[:n])))
            if err != nil {
                fmt.Println("Write error:", err)
                break
            }
        }
        fmt.Printf("Connection closed from %s\n", conn.RemoteAddr())
    }
    
    func main() {
        listener, err := net.Listen("tcp", ":8888")
        if err != nil {
            log.Fatal("Listen error:", err)
        }
        defer listener.Close()
        fmt.Println("TCP Server listening on :8888")
    
        for {
            conn, err := listener.Accept() // 新しい接続を待機
            if err != nil {
                fmt.Println("Accept error:", err)
                continue // エラーが発生しても次の接続を待つ
            }
            go handleConnection(conn) // 各接続を個別のGoroutineで処理
        }
    }
  • TCPクライアント (低レベル): `net.Dial`
    func main() {
        conn, err := net.Dial("tcp", "localhost:8888")
        if err != nil {
            log.Fatal("Dial error:", err)
        }
        defer conn.Close()
        fmt.Println("Connected to server.")
    
        // サーバーにメッセージを送信
        _, err = conn.Write([]byte("Hello from client!\n"))
        if err != nil {
            log.Fatal("Write error:", err)
        }
    
        // サーバーからの応答を受信
        buf := make([]byte, 1024)
        n, err := conn.Read(buf)
        if err != nil {
            log.Fatal("Read error:", err)
        }
        fmt.Printf("Server response: %s", string(buf[:n]))
    }