- 1. なぜGoなのか
- 2. 基本文法
- 3. ゴルーチンとチャネル
- 4. インターフェース
- 5. エラー処理
- 6. ジェネリクス(Go 1.18以降)
- 7. テスト
- 8. パッケージとモジュール
- 9. 実践パターン
- 10. 2026年のGo
- おわりに
1. なぜGoなのか
Go(Golang)は2009年にGoogleで作られた言語です。設計哲学はシンプルさ、高速コンパイル、強力な並行処理です。
Goが選ばれる理由
- Docker、Kubernetes、Terraform、Prometheus などクラウドインフラの中核ツールがすべてGoで書かれています
- コンパイル速度が非常に速いです。数十万行のプロジェクトも数秒でビルドされます
- 静的バイナリを生成するため、デプロイがシンプルです。別途のランタイムは不要です
- ゴルーチンにより数十万の同時タスクを効率的に処理します
- ガベージコレクションがありますが、レイテンシは非常に低いです
Go vs 他の言語
| 項目 | Go | Python | Java | Rust |
|---|---|---|---|---|
| コンパイル速度 | 非常に速い | インタプリタ | 遅い | 遅い |
| 実行速度 | 速い | 遅い | 速い | 非常に速い |
| 並行処理 | ゴルーチン | asyncio | スレッド | async/tokio |
| 学習コスト | 低い | 非常に低い | 高い | 非常に高い |
| メモリ管理 | GC | GC | GC | 所有権 |
Goは生産性とパフォーマンスの最適なバランスポイントに位置します。Rustほど速くはありませんが、チーム全体が素早く学び、メンテナンスできます。
2. 基本文法
変数と型
package main
import "fmt"
func main() {
// 明示的な宣言
var name string = "Gopher"
var age int = 10
// 短い宣言(型推論)
language := "Go"
version := 1.22
// 定数
const pi = 3.14159
fmt.Printf("%sは%d歳で%s %.2fを使っています\n",
name, age, language, version)
fmt.Println("円周率:", pi)
}
関数
// 基本関数
func add(a, b int) int {
return a + b
}
// 複数戻り値
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("ゼロで割ることはできません")
}
return a / b, nil
}
// 名前付き戻り値
func swap(a, b string) (first, second string) {
first = b
second = a
return // naked return
}
// 可変長引数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
構造体
type User struct {
Name string
Email string
Age int
}
// メソッド(値レシーバ)
func (u User) String() string {
return fmt.Sprintf("%s (%s)", u.Name, u.Email)
}
// メソッド(ポインタレシーバ - 値の変更可能)
func (u *User) SetEmail(email string) {
u.Email = email
}
func main() {
user := User{Name: "田中太郎", Email: "tanaka@example.com", Age: 30}
user.SetEmail("new@example.com")
fmt.Println(user) // 田中太郎 (new@example.com)
}
ポインタ
func main() {
x := 42
p := &x // xのアドレス
fmt.Println(*p) // 42(デリファレンス)
*p = 100
fmt.Println(x) // 100
}
// Goにはポインタ演算がありません - 安全です
// Cと違い、ダングリングポインタの心配が少ないです
スライスとマップ
func main() {
// スライス
nums := []int{1, 2, 3, 4, 5}
nums = append(nums, 6, 7)
sub := nums[1:4] // [2, 3, 4]
// makeで作成
buffer := make([]byte, 0, 1024) // len=0, cap=1024
// マップ
scores := map[string]int{
"Alice": 95,
"Bob": 87,
}
scores["Charlie"] = 92
// キーの存在確認
val, ok := scores["Dave"]
if !ok {
fmt.Println("Daveのスコアがありません")
}
// マップの反復
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
}
3. ゴルーチンとチャネル
Goの並行処理モデルはCSP(Communicating Sequential Processes) に基づいています。核心は2つ:ゴルーチンとチャネルです。
ゴルーチンの基礎
func worker(id int) {
fmt.Printf("Worker %d 開始\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d 完了\n", id)
}
func main() {
for i := 1; i <= 5; i++ {
go worker(i) // ゴルーチン起動
}
// メインゴルーチンが終了するとプログラムも終了
time.Sleep(2 * time.Second)
}
ゴルーチンはOSスレッドではありません。Goランタイムが数千のゴルーチンを少数のOSスレッドに多重化(multiplexing) します。ゴルーチン1つの初期スタックは約2KBで、数十万個を同時に実行できます。
チャネル通信
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i // チャネルに値を送信
fmt.Printf("送信: %d\n", i)
}
close(ch) // チャネルを閉じる
}
func consumer(ch <-chan int) {
for val := range ch { // チャネルが閉じるまで受信
fmt.Printf("受信: %d\n", val)
}
}
func main() {
ch := make(chan int, 3) // バッファサイズ3
go producer(ch)
consumer(ch) // メインゴルーチンで消費
}
select文
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
case msg := <-ch2:
fmt.Println("ch2:", msg)
case <-time.After(3 * time.Second):
fmt.Println("タイムアウト!")
}
}
}
WaitGroup
func main() {
var wg sync.WaitGroup
urls := []string{
"https://example.com",
"https://golang.org",
"https://github.com",
}
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, err := http.Get(u)
if err != nil {
fmt.Printf("エラー: %s - %v\n", u, err)
return
}
defer resp.Body.Close()
fmt.Printf("%s -> %s\n", u, resp.Status)
}(url)
}
wg.Wait() // 全ゴルーチンの完了を待つ
fmt.Println("全リクエスト完了")
}
デッドロック防止パターン
// 悪い例:デッドロック発生
func bad() {
ch := make(chan int) // バッファなしチャネル
ch <- 1 // 受信者がいないため永久にブロック
fmt.Println(<-ch)
}
// 良い例1:ゴルーチンで送信
func good1() {
ch := make(chan int)
go func() { ch <- 1 }()
fmt.Println(<-ch)
}
// 良い例2:バッファチャネルを使用
func good2() {
ch := make(chan int, 1)
ch <- 1
fmt.Println(<-ch)
}
ワーカープールパターン
func workerPool(jobs <-chan int, results chan<- int, id int) {
for j := range jobs {
fmt.Printf("Worker %d 処理中: job %d\n", id, j)
time.Sleep(time.Millisecond * 500)
results <- j * 2
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// ワーカー起動
for w := 1; w <= numWorkers; w++ {
go workerPool(jobs, results, w)
}
// ジョブ送信
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 結果収集
for r := 1; r <= numJobs; r++ {
result := <-results
fmt.Printf("結果: %d\n", result)
}
}
4. インターフェース
Goのインターフェースは暗黙的に実装されます。implementsキーワードは不要です。
基本インターフェース
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Shapeインターフェースをパラメータとして受け取る関数
func printInfo(s Shape) {
fmt.Printf("面積: %.2f, 周囲: %.2f\n", s.Area(), s.Perimeter())
}
io.Reader / io.Writer
Go標準ライブラリで最も強力なインターフェースです。
// io.Readerインターフェース
// type Reader interface {
// Read(p []byte) (n int, err error)
// }
// io.Writerインターフェース
// type Writer interface {
// Write(p []byte) (n int, err error)
// }
func countBytes(r io.Reader) (int, error) {
buf := make([]byte, 1024)
total := 0
for {
n, err := r.Read(buf)
total += n
if err == io.EOF {
return total, nil
}
if err != nil {
return total, err
}
}
}
func main() {
// 文字列から読み取り
r := strings.NewReader("Hello, Go!")
n, _ := countBytes(r)
fmt.Printf("%d バイト\n", n) // 10 バイト
// ファイルから読み取り
f, _ := os.Open("data.txt")
defer f.Close()
n, _ = countBytes(f)
fmt.Printf("%d バイト\n", n)
}
空インターフェースと型アサーション
func describe(i interface{}) {
// 型アサーション
if s, ok := i.(string); ok {
fmt.Println("文字列:", s)
return
}
// 型スイッチ
switch v := i.(type) {
case int:
fmt.Println("整数:", v)
case float64:
fmt.Println("実数:", v)
case bool:
fmt.Println("ブール:", v)
default:
fmt.Printf("不明な型: %T\n", v)
}
}
func main() {
describe(42)
describe("hello")
describe(3.14)
describe(true)
}
インターフェースの合成
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// インターフェース合成(埋め込み)
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
5. エラー処理
Goは例外(exception)の代わりに明示的なエラー戻り値を使います。
errors.Newとfmt.Errorf
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("無効なユーザーID: %d", id)
}
// ... DB検索
return nil, ErrNotFound
}
エラーラッピングとerrors.Is / errors.As
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("バリデーション失敗 - %s: %s", e.Field, e.Message)
}
func validateAge(age int) error {
if age < 0 || age > 150 {
return &ValidationError{
Field: "age",
Message: fmt.Sprintf("年齢は0-150の範囲でなければなりません(入力値: %d)", age),
}
}
return nil
}
func processUser(age int) error {
if err := validateAge(age); err != nil {
return fmt.Errorf("ユーザー処理失敗: %w", err) // エラーラッピング
}
return nil
}
func main() {
err := processUser(-5)
// errors.Is: エラーチェーンで特定のエラーを確認
if errors.Is(err, ErrNotFound) {
fmt.Println("ユーザーが見つかりません")
}
// errors.As: エラーチェーンで特定の型に変換
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("フィールド: %s, メッセージ: %s\n", valErr.Field, valErr.Message)
}
}
カスタムエラーパターン
type AppError struct {
Code int
Message string
Err error
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
func (e *AppError) Unwrap() error {
return e.Err
}
// エラー生成ヘルパー
func NewAppError(code int, msg string, err error) *AppError {
return &AppError{Code: code, Message: msg, Err: err}
}
6. ジェネリクス(Go 1.18以降)
Go 1.18からジェネリクスが導入され、型安全な汎用コードが書けるようになりました。
型パラメータ
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
fmt.Println(Min(3, 5)) // int
fmt.Println(Min(3.14, 2.71)) // float64
fmt.Println(Min("a", "b")) // string
}
制約
import "golang.org/x/exp/constraints"
// カスタム制約
type Number interface {
constraints.Integer | constraints.Float
}
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
}
func (s *Stack[T]) Len() int {
return len(s.items)
}
実践ジェネリクス例:マップユーティリティ
func Keys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func Values[K comparable, V any](m map[K]V) []V {
vals := make([]V, 0, len(m))
for _, v := range m {
vals = append(vals, v)
}
return vals
}
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, item := range slice {
if predicate(item) {
result = append(result, item)
}
}
return result
}
func Map[T any, U any](slice []T, transform func(T) U) []U {
result := make([]U, len(slice))
for i, item := range slice {
result[i] = transform(item)
}
return result
}
7. テスト
Goには強力なビルトインテストフレームワークがあります。外部ライブラリは不要です。
基本テスト
// math.go
package math
func Add(a, b int) int {
return a + b
}
func Fibonacci(n int) int {
if n <= 1 {
return n
}
return Fibonacci(n-1) + Fibonacci(n-2)
}
// math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}
テーブル駆動テスト
func TestFibonacci(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{"zero", 0, 0},
{"one", 1, 1},
{"two", 2, 1},
{"five", 5, 5},
{"ten", 10, 55},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := Fibonacci(tc.input)
if result != tc.expected {
t.Errorf("Fibonacci(%d) = %d; want %d",
tc.input, result, tc.expected)
}
})
}
}
ベンチマーク
func BenchmarkFibonacci(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(20)
}
}
// 実行: go test -bench=. -benchmem
// BenchmarkFibonacci-8 28735 41523 ns/op 0 B/op 0 allocs/op
httptest
func TestHealthHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/health", nil)
w := httptest.NewRecorder()
healthHandler(w, req)
resp := w.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("ステータスコード = %d; want %d",
resp.StatusCode, http.StatusOK)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != `{"status":"ok"}` {
t.Errorf("レスポンス = %s; want {\"status\":\"ok\"}", body)
}
}
8. パッケージとモジュール
go modの初期化
# 新規プロジェクト開始
mkdir myproject && cd myproject
go mod init github.com/username/myproject
# 依存関係を追加
go get github.com/gin-gonic/gin@latest
# 未使用の依存関係をクリーンアップ
go mod tidy
# 依存関係をダウンロード
go mod download
プロジェクト構成
myproject/
cmd/
server/
main.go # エントリポイント
internal/
handler/
user.go # HTTPハンドラ
user_test.go
service/
user.go # ビジネスロジック
repository/
user.go # データアクセス
pkg/
validator/
validator.go # 外部公開ユーティリティ
go.mod
go.sum
internalパッケージ
internalディレクトリ内のパッケージは、そのモジュール外部からインポートできません。これにより公開APIと内部実装を明確に分離します。
// internal/config/config.go
package config
type Config struct {
Port int
DBHost string
LogLevel string
}
func Load() (*Config, error) {
// 環境変数から設定をロード
return &Config{
Port: 8080,
DBHost: "localhost:5432",
LogLevel: "info",
}, nil
}
9. 実践パターン
Webサーバー(net/http)
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type Response struct {
Message string `json:"message"`
Timestamp string `json:"timestamp"`
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Message: "ok",
Timestamp: time.Now().Format(time.RFC3339),
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", healthHandler)
mux.HandleFunc("GET /api/users", getUsersHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Printf("サーバー起動: %s", server.Addr)
log.Fatal(server.ListenAndServe())
}
ミドルウェア
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("[%s] %s 開始", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("[%s] %s 完了 (%v)",
r.Method, r.URL.Path, time.Since(start))
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "認証が必要です", http.StatusUnauthorized)
return
}
// トークン検証ロジック...
next.ServeHTTP(w, r)
})
}
// ミドルウェアチェーン
func chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", healthHandler)
handler := chain(mux, loggingMiddleware, authMiddleware)
log.Fatal(http.ListenAndServe(":8080", handler))
}
Graceful Shutdown
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", healthHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// ゴルーチンでサーバーを起動
go func() {
log.Println("サーバー起動: :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("サーバーエラー: %v", err)
}
}()
// 終了シグナルを待つ
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("サーバー終了開始...")
// 30秒タイムアウトでgraceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("サーバー強制終了: %v", err)
}
log.Println("サーバー終了完了")
}
設定管理
type Config struct {
Server ServerConfig
Database DatabaseConfig
}
type ServerConfig struct {
Port int `json:"port"`
ReadTimeout time.Duration `json:"read_timeout"`
WriteTimeout time.Duration `json:"write_timeout"`
}
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
DBName string `json:"dbname"`
}
func (d DatabaseConfig) DSN() string {
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
d.Host, d.Port, d.User, d.Password, d.DBName)
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("設定ファイルの読み込み失敗: %w", err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("設定のパース失敗: %w", err)
}
return &cfg, nil
}
10. 2026年のGo
クラウドインフラ
Goはクラウドネイティブエコシステムの事実上の標準言語です。
- コンテナオーケストレーション: Kubernetes、Docker、containerd
- サービスメッシュ: Istio、Linkerd
- 監視: Prometheus、Grafana Agent、Thanos
- IaC: Terraform、Pulumi
- CI/CD: Drone、Tekton
CLIツール
Goで作ったCLIツールは単一バイナリで配布され、インストールが簡単です。
// cobraを使ったCLI例
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "mytool",
Short: "私のCLIツール",
}
var greetCmd = &cobra.Command{
Use: "greet [name]",
Short: "挨拶する",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("こんにちは、%sさん!\n", args[0])
},
}
func main() {
rootCmd.AddCommand(greetCmd)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
マイクロサービス
Goはマイクロサービスアーキテクチャに最適化されています。
- 高速な起動時間: コールドスタート遅延を最小化
- 低メモリ使用量: コンテナリソースを節約
- 静的バイナリ: 軽量Dockerイメージ(scratchベース可能)
- gRPCネイティブサポート: protobuf + gRPCでサービス間通信
- OpenTelemetry: 分散トレーシングとメトリクス収集
# マルチステージビルド
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /server ./cmd/server
FROM scratch
COPY /server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
最終イメージサイズは10-20MB程度で、JavaやPythonベースのサービスと比べて1/10以下です。
おわりに
Goはシンプルさこそが強さという言語です。複雑な機能を排除し、本当に必要なものだけを提供します。この哲学が、DockerやKubernetesのようなインフラツールからCLI、マイクロサービスまで幅広い領域でGoが選ばれる理由です。
始めるのに最も良い方法は、自分でコードを書くことです。シンプルなCLIツールやREST APIサーバーを作ってみてください。
クイズ:Go並行処理の理解度チェック
Q1. ゴルーチンとOSスレッドの違いは何ですか?
Goランタイムがゴルーチンを少数のOSスレッドに多重化します。ゴルーチンの初期スタックは約2KBで、OSスレッドの1-8MBよりはるかに軽量です。
Q2. バッファなしチャネルでの送信と受信の関係は?
送信者は受信者の準備ができるまでブロックし、受信者も送信者の準備ができるまでブロックします。同期的な通信方式です。
Q3. select文の役割は何ですか?
複数のチャネル操作を同時に待ちながら、準備ができたチャネルから先に処理します。タイムアウトやキャンセル処理に有用です。
현재 단락 (1/747)
Go(Golang)は2009年にGoogleで作られた言語です。設計哲学は**シンプルさ、高速コンパイル、強力な並行処理**です。