この記事の要点
• goroutineとchannelによる並行処理がGoの最大の強み
• エラー処理はif err != nilパターンが基本
• go mod tidyで依存管理、go test -raceでレース検出
概要
Go(Golang)は Google が開発した静的型付けのコンパイル言語で、シンプルな文法・強力な標準ライブラリ・並行処理モデル(goroutine/channel)が特徴です。本チートシートは Go 1.22+ を前提に、実務で頻出する構文とツールチェーンの使い方を整理しています。
go ツールチェーン
| コマンド | 説明 |
|---|---|
go version | バージョン確認 |
go mod init example.com/app | モジュール初期化 |
go mod tidy | 依存関係を整理 |
go mod download | 依存をダウンロード |
go get pkg@latest | 依存を追加/更新 |
go run . | カレントパッケージを実行 |
go run main.go | ファイルを指定して実行 |
go build ./... | ビルド |
go build -o bin/app | 出力名指定 |
go install ./cmd/app | $GOBIN にインストール |
go test ./... | 全テスト実行 |
go test -run TestX -v | 個別実行+詳細出力 |
go test -race ./... | レース検出 |
go test -cover | カバレッジ |
go vet ./... | 静的検査 |
gofmt -w . | フォーマット |
go fmt ./... | パッケージ単位整形 |
go doc pkg.Func | ドキュメント表示 |
go env | 環境変数表示 |
go work init | マルチモジュール作業環境 |
ポイント: go mod tidyは使っていない依存を削除し、必要な依存を追加します。コミット前に必ず実行する習慣をつけましょう。
クロスコンパイル
| コマンド | 説明 |
|---|---|
GOOS=linux GOARCH=amd64 go build -o bin/app-linux | Linux 用にビルド |
GOOS=darwin GOARCH=arm64 go build -o bin/app-mac | macOS (ARM) 用にビルド |
GOOS=windows GOARCH=amd64 go build -o bin/app.exe | Windows 用にビルド |
基本型
| 型 | 説明 |
|---|---|
bool | 真偽値 |
int / int8..64 | 整数 |
uint / uint8..64 | 符号なし整数 |
float32 / float64 | 浮動小数 |
complex64 / complex128 | 複素数 |
string | 文字列(不変) |
rune | Unicode コードポイント(int32) |
byte | uint8 の別名 |
error | 組込エラーインターフェース |
変数と定数
var x int = 1
var y = 2.5 // 型推論
z := "hello" // 短い宣言(関数内のみ)
const Pi = 3.14
const (
A = iota // 0
B // 1
C // 2
)
制御構文
if x > 0 {
// ...
} else if x == 0 {
// ...
} else {
// ...
}
// if に初期化式
if v, err := f(); err != nil {
return err
} else {
use(v)
}
for i := 0; i < 10; i++ { /* ... */ }
for i, v := range slice { /* ... */ }
for k, v := range m { /* ... */ }
for ch := range channel { /* ... */ }
for { break } // 無限ループ
switch x {
case 1, 2:
// ...
case 3:
fallthrough
default:
// ...
}
switch v := any.(type) { // 型スイッチ
case int: _ = v
case string: _ = v
}
関数
func add(a, b int) int { return a + b }
// 複数戻り値
func divmod(a, b int) (int, int) { return a / b, a % b }
// 名前付き戻り値
func parse(s string) (n int, err error) {
n, err = strconv.Atoi(s)
return
}
// 可変長引数
func sum(xs ...int) int {
total := 0
for _, v := range xs { total += v }
return total
}
// 関数値・クロージャ
add := func(a, b int) int { return a + b }
実践メモ: :=(短い宣言)は関数内でのみ使用可能。パッケージレベルではvarを使います。
エラー処理
import "errors"
import "fmt"
var ErrNotFound = errors.New("not found")
func find(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("find: invalid id %d: %w", id, ErrNotFound)
}
// ...
return u, nil
}
// 呼び出し側
u, err := find(0)
if err != nil {
if errors.Is(err, ErrNotFound) { /* ... */ }
var pe *os.PathError
if errors.As(err, &pe) { /* ... */ }
return err
}
注意: エラーは必ず%wでラップしましょう。fmt.Errorf("...: %w", err)にするとerrors.Is/Asでチェーンを辿れます。
構造体とメソッド
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func (u User) Greet() string { return "hi " + u.Name } // 値レシーバ
func (u *User) Rename(n string) { u.Name = n } // ポインタレシーバ
u := User{ID: 1, Name: "Ada"}
u.Rename("Grace")
インターフェース
type Stringer interface {
String() string
}
type Animal struct{ Name string }
func (a Animal) String() string { return a.Name }
var s Stringer = Animal{"cat"}
fmt.Println(s)
// 型アサーション
a, ok := s.(Animal)
ジェネリクス(1.18+)
func Map[T, U any](xs []T, f func(T) U) []U {
out := make([]U, len(xs))
for i, v := range xs { out[i] = f(v) }
return out
}
type Number interface { ~int | ~float64 }
func Sum[T Number](xs []T) T {
var s T
for _, v := range xs { s += v }
return s
}
スライス・マップ
// スライス
s := []int{1, 2, 3}
s = append(s, 4, 5)
sub := s[1:3]
s2 := make([]int, 0, 10) // len 0, cap 10
c := make([]int, len(s))
copy(c, s)
// マップ
m := map[string]int{"a": 1}
m["b"] = 2
v, ok := m["a"]
delete(m, "a")
for k, v := range m { _ = k; _ = v }
ポイント: Goのインターフェースは暗黙的に満たされます(implements宣言不要)。メソッドさえ一致すれば型は自動的にインターフェースを実装します。
並行処理(goroutine / channel)
// goroutine
go func() { fmt.Println("hello") }()
// チャネル
ch := make(chan int) // アンバッファ
buf := make(chan int, 10) // バッファ付き
ch <- 1 // 送信
v := <-ch // 受信
close(ch) // クローズ
for v := range ch { _ = v }
// select
select {
case v := <-ch1:
_ = v
case ch2 <- 42:
case <-time.After(1 * time.Second):
// タイムアウト
default:
// どれも準備できていない
}
注意: goroutineのリークに注意。送信先のないチャネルや、終了しないgoroutineはメモリリークの原因になります。必ずcloseやcontextで制御しましょう。
sync パッケージ
var mu sync.Mutex
mu.Lock(); defer mu.Unlock()
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
work(i)
}(i)
}
wg.Wait()
var once sync.Once
once.Do(func() { /* 初期化 */ })
context
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := http.DefaultClient.Do(req)
実践メモ: deferは関数の終了時に必ず実行されます。ファイルのClose、Mutexの Unlock、レスポンスBodyのCloseに活用しましょう。
実用スニペット集
1. HTTP サーバ
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
log.Fatal(http.ListenAndServe(":8080", mux))
}
2. HTTP クライアント
resp, err := http.Get("https://example.com")
if err != nil { return err }
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
3. JSON のエンコード/デコード
b, _ := json.Marshal(user)
var u User
_ = json.Unmarshal(b, &u)
4. ファイル読み書き
data, err := os.ReadFile("in.txt")
_ = os.WriteFile("out.txt", data, 0o644)
5. 行単位で読む
f, _ := os.Open("log.txt")
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() { fmt.Println(sc.Text()) }
6. コマンドライン引数
flag.StringVar(&name, "name", "world", "greet target")
flag.Parse()
7. 環境変数
port := os.Getenv("PORT")
if port == "" { port = "8080" }
8. 文字列操作
strings.Contains(s, "go")
strings.Split(s, ",")
strings.Join(parts, "-")
strings.ReplaceAll(s, "a", "b")
strings.ToLower(s)
9. 数値変換
n, err := strconv.Atoi("42")
s := strconv.Itoa(42)
f, _ := strconv.ParseFloat("3.14", 64)
10. 並行処理でエラー集約
import "golang.org/x/sync/errgroup"
g, ctx := errgroup.WithContext(ctx)
for _, url := range urls {
url := url
g.Go(func() error { return fetch(ctx, url) })
}
if err := g.Wait(); err != nil { return err }
11. テスト
func TestAdd(t *testing.T) {
got := Add(1, 2)
if got != 3 { t.Errorf("got %d", got) }
}
func TestTable(t *testing.T) {
cases := []struct{ a, b, want int }{
{1, 2, 3},
{0, 0, 0},
}
for _, c := range cases {
t.Run(fmt.Sprintf("%d+%d", c.a, c.b), func(t *testing.T) {
if got := Add(c.a, c.b); got != c.want {
t.Errorf("got %d want %d", got, c.want)
}
})
}
}
12. ベンチマーク
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ { Add(1, 2) }
}
13. embed で静的ファイルを埋め込む
import _ "embed"
//go:embed static/*
var static embed.FS
14. slog 構造化ログ
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("started", "port", 8080)
15. 依存注入(シンプル)
type Repo interface { Find(id int) (*User, error) }
type Service struct{ repo Repo }
func NewService(r Repo) *Service { return &Service{repo: r} }
よく使う標準ライブラリ
| パッケージ | 用途 |
|---|---|
fmt | 書式付き入出力 |
os | OS 操作・環境変数 |
io / io/fs | I/O 抽象化 |
bufio | バッファ付き I/O |
strings / strconv | 文字列・数値変換 |
bytes | []byte 操作 |
encoding/json | JSON |
net/http | HTTP サーバ/クライアント |
context | キャンセル・期限 |
time | 時刻 |
sync / sync/atomic | 並行制御 |
errors | エラーラッピング |
log/slog | 構造化ログ |
testing | テスト |
database/sql | SQL DB |
トラブルシューティング
| 状況 | 原因 / 対処 |
|---|---|
go: cannot find main module | go mod init を実行 |
undefined: X | import 漏れ。goimports を使うと自動追加 |
assignment mismatch | 戻り値の個数と受け取り数が不一致 |
concurrent map writes | sync.Mutex か sync.Map を使う |
nil pointer dereference | ポインタ初期化を確認 |
fatal error: all goroutines are asleep - deadlock! | 受信のないチャネル送信等。close やバッファを検討 |
go test が遅い | -run で対象を絞る、t.Parallel() を検討 |
Tips
- フォーマットは必ず
gofmt(もしくはgoimports)。CI で検査する。 go vetとstaticcheckを CI に入れると品質が上がる。- エラーは
fmt.Errorf("...: %w", err)でラップし、errors.Is/Asで判定する。 deferは関数末尾で確実に実行されるので、クローズ・ロック解放に使う。- 大きな struct は値渡しでなくポインタ渡しを検討(ただしレシーバ型は統一)。
context.Contextは関数の第 1 引数に置くのが慣習。- ゴルーチンリークに注意: 送信側が終了しないチャネルは必ず close する。