第2章: 環境構築とツールチェーン

学習目標

この章を終えると、以下ができるようになります:

  • Goの開発環境を正しくセットアップできる
  • go コマンドの基本的な使い方を理解できる
  • モジュールシステム(go modules)を使ってプロジェクトを管理できる
  • Goの標準ツール(fmt, vet, testなど)を活用できる
  • コンパイラとリンカの動作を理解できる
  • 実行ファイルの生成プロセスを機械レベルで理解できる

Goのインストール - 深層解説

🔑 なぜGoのインストールが特別なのか

他の言語と異なり、Goは完全に自己完結したツールチェーンを提供します。これには深い理由があります:

従来の言語の問題点

C/C++の場合:
┌─────────────────────────────────────────┐
│ コンパイラ (gcc/clang)                   │ → 別々にインストール
│ リンカ (ld)                             │ → バージョン依存
│ ビルドツール (make/cmake)                │ → 設定が複雑
│ パッケージマネージャ (apt/brew)          │ → プラットフォーム依存
└─────────────────────────────────────────┘
   ↓ 結果
   環境差異が大きい、ビルドが失敗しやすい

Goのアプローチ

Goの場合:
┌─────────────────────────────────────────┐
│                                         │
│  ┌─────────────────────────────────┐   │
│  │  go コマンド (全てを統合)        │   │
│  ├─────────────────────────────────┤   │
│  │ • コンパイラ (gc)                │   │
│  │ • リンカ (link)                  │   │
│  │ • ビルドツール                   │   │
│  │ • パッケージマネージャ           │   │
│  │ • フォーマッタ                   │   │
│  │ • テストランナー                 │   │
│  │ • 静的解析ツール                 │   │
│  └─────────────────────────────────┘   │
│                                         │
└─────────────────────────────────────────┘
   ↓ 結果
   一度インストールすれば全て揃う

公式サイトからのインストール - 内部動作

macOS

# Homebrewを使用
brew install go

💡 Homebrewインストール時の内部動作

1. ダウンロード
   ↓
   https://go.dev/dl/ から最新バイナリを取得

2. 検証
   ┌──────────────────────────────────────┐
   │ SHA256チェックサム検証               │
   │ ファイル破損・改ざん検知             │
   └──────────────────────────────────────┘

3. 展開
   /usr/local/Cellar/go/X.Y.Z/ に配置

4. シンボリックリンク作成
   /usr/local/bin/go → /usr/local/Cellar/go/X.Y.Z/bin/go

5. 環境変数設定
   PATH に /usr/local/bin を追加(自動)

ディレクトリ構造

/usr/local/Cellar/go/1.21.5/
├── bin/
│   ├── go          ← メインコマンド
│   ├── gofmt       ← フォーマッタ
│   └── godoc       ← ドキュメントツール
├── libexec/
│   ├── bin/
│   │   ├── compile  ← コンパイラ
│   │   ├── link     ← リンカ
│   │   └── asm      ← アセンブラ
│   └── pkg/
│       └── tool/
│           └── darwin_amd64/  ← プラットフォーム固有ツール
├── pkg/
│   └── darwin_amd64/  ← プリコンパイル済み標準ライブラリ
└── src/
    └── ...  ← 標準ライブラリのソースコード

Linux - 手動インストール詳細

# 1. tarballをダウンロード
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz

# 2. 展開(既存のGoを削除)
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz

# 3. PATHに追加(~/.bashrcまたは~/.zshrcに追記)
export PATH=$PATH:/usr/local/go/bin

🔑 なぜ既存のGoを削除するのか

問題: 古いGoと新しいGoが混在する場合

/usr/local/go/
├── bin/
│   └── go  ← バージョン1.20
└── pkg/
    └── linux_amd64/  ← 1.20用のライブラリ

新しいGoをインストール後:
├── bin/
│   └── go  ← バージョン1.21(上書き)
└── pkg/
    ├── linux_amd64/  ← 1.20のライブラリ(残留)
    └── linux_amd64_1.21/  ← 1.21のライブラリ(新規)

結果: リンク時にバージョン不整合でエラー

解決: rm -rf で完全削除してからインストール

Windows - MSIインストーラの内部動作

# 1. https://go.dev/dl/ から.msiファイルをダウンロード
# 2. インストーラーを実行
# 3. 自動的にPATHが設定されます

💡 MSIインストーラが行うこと

1. ファイルコピー
   C:\Program Files\Go\ にバイナリ配置

2. レジストリ設定
   HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
   ├── GOROOT = C:\Program Files\Go
   └── Path += C:\Program Files\Go\bin

3. ユーザー環境変数
   HKEY_CURRENT_USER\Environment
   └── GOPATH = %USERPROFILE%\go

4. 再起動不要でPATH有効化
   ブロードキャストメッセージ送信
   WM_SETTINGCHANGE → 全プロセスに通知

インストールの確認 - 深層動作

# Goのバージョン確認
go version
# 出力例: go version go1.21.5 darwin/amd64

🔑 go version コマンドの内部動作

// Go自身のソースコード(疑似コード)
func versionCommand() {
    // ビルド時に埋め込まれた情報を取得
    version := runtime.Version()    // "go1.21.5"
    goos := runtime.GOOS            // "darwin"
    goarch := runtime.GOARCH        // "amd64"

    fmt.Printf("go version %s %s/%s\n", version, goos, goarch)
}

runtime.Version() の実装

コンパイル時:
1. バージョン情報を定数として埋め込み
   const TheVersion = "go1.21.5"

2. バイナリの .rodata セクションに配置

実行時:
1. .rodata セクションから文字列読み取り
   ┌─────────────────────────────────┐
   │ ELF/Mach-O/PE バイナリ構造      │
   ├─────────────────────────────────┤
   │ .text   (コード)                │
   │ .rodata (読み取り専用データ)    │ ← ここに "go1.21.5"
   │ .data   (初期化済みデータ)      │
   │ .bss    (未初期化データ)        │
   └─────────────────────────────────┘

2. 文字列を返す(メモリコピー不要)

# インストールパスの確認
which go
# 出力例: /usr/local/go/bin/go

💡 which コマンドの動作原理

1. 環境変数 PATH を取得
   PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin

2. コロン (:) で分割
   paths = ["/usr/local/bin", "/usr/bin", "/bin", ...]

3. 各ディレクトリで "go" を検索
   for each path in paths:
       fullpath = path + "/go"
       if file_exists(fullpath) and is_executable(fullpath):
           print(fullpath)
           return

4. システムコール stat() で実行可能性を確認
   syscall.Stat("/usr/local/go/bin/go") → mode & 0111 != 0

環境変数の理解 - 機械レベルの詳細

重要な環境変数

# すべての環境変数を表示
go env

# 特定の変数を確認
go env GOPATH
go env GOROOT
go env GOOS
go env GOARCH

🔑 go env コマンドの内部動作

// go env の疑似実装
func envCommand(args []string) {
    // 1. ビルトイン値を取得
    goroot := runtime.GOROOT()  // /usr/local/go
    gopath := defaultGOPATH()   // ~/go

    // 2. 環境変数で上書き
    if val := os.Getenv("GOROOT"); val != "" {
        goroot = val
    }
    if val := os.Getenv("GOPATH"); val != "" {
        gopath = val
    }

    // 3. 計算値を追加
    gocache := gopath + "/pkg/mod/cache"
    gomodcache := gopath + "/pkg/mod"

    // 4. 出力
    fmt.Printf("GOROOT=%s\n", goroot)
    fmt.Printf("GOPATH=%s\n", gopath)
    // ...
}

主要な環境変数の詳細

変数 説明 デフォルト値 メモリ上の役割
GOROOT Goのインストールディレクトリ /usr/local/go 標準ライブラリの検索パス
GOPATH ワークスペースのルート ~/go モジュールキャッシュとビルド成果物
GOBIN 実行ファイルのインストール先 $GOPATH/bin go install の出力先
GOOS ターゲットOS 現在のOS クロスコンパイル用
GOARCH ターゲットアーキテクチャ 現在のアーキテクチャ 命令セット選択
GO111MODULE モジュールモード on モジュール解決方法
GOCACHE ビルドキャッシュ ~/.cache/go-build 高速リビルド

GOROOT - 標準ライブラリの心臓部

GOROOT=/usr/local/go

構造:
/usr/local/go/
├── src/              ← 標準ライブラリのソースコード
│   ├── fmt/
│   │   ├── print.go
│   │   ├── scan.go
│   │   └── format.go
│   ├── os/
│   ├── net/
│   └── ...
├── pkg/              ← プリコンパイル済みライブラリ
│   └── darwin_amd64/
│       ├── fmt.a     ← アーカイブファイル(オブジェクトコード)
│       ├── os.a
│       └── ...
└── bin/
    └── go

コンパイル時の動作:
1. import "fmt" を発見
2. GOROOT/src/fmt を検索
3. GOROOT/pkg/darwin_amd64/fmt.a が存在すれば使用
4. なければ GOROOT/src/fmt/*.go からコンパイル

💡 プリコンパイル済みライブラリ (.a ファイル) の中身

fmt.a ファイルの構造:

┌──────────────────────────────────────┐
│ アーカイブヘッダー                    │
├──────────────────────────────────────┤
│ オブジェクトファイル 1: print.o      │
│ ┌──────────────────────────────────┐ │
│ │ .text セクション                  │ │ ← 機械語コード
│ │   Println 関数の機械語            │ │
│ │   Printf 関数の機械語             │ │
│ ├──────────────────────────────────┤ │
│ │ .rodata セクション                │ │ ← 定数データ
│ │   フォーマット文字列              │ │
│ ├──────────────────────────────────┤ │
│ │ .data セクション                  │ │ ← 初期化済み変数
│ ├──────────────────────────────────┤ │
│ │ シンボルテーブル                  │ │ ← 関数名とアドレス
│ │   fmt.Println → 0x1234            │ │
│ │   fmt.Printf  → 0x5678            │ │
│ └──────────────────────────────────┘ │
├──────────────────────────────────────┤
│ オブジェクトファイル 2: scan.o       │
│   ...                                │
└──────────────────────────────────────┘

リンク時:
1. シンボルテーブルから必要な関数を検索
2. 機械語コードを最終バイナリにコピー
3. アドレスを再配置(リロケーション)

GOPATH - モジュール時代の役割

GOPATH=~/go

構造:
~/go/
├── bin/              ← go install で生成された実行ファイル
│   ├── golangci-lint
│   ├── gopls
│   └── dlv
├── pkg/
│   ├── mod/          ← ダウンロードした外部モジュール
│   │   ├── cache/    ← モジュールキャッシュ
│   │   │   └── download/
│   │   │       └── github.com/
│   │   │           └── gorilla/
│   │   │               └── mux/
│   │   │                   └── @v/
│   │   │                       ├── v1.8.0.zip
│   │   │                       ├── v1.8.0.mod
│   │   │                       └── v1.8.0.info
│   │   └── github.com/
│   │       └── gorilla/
│   │           └── mux@v1.8.0/  ← 展開済みソース
│   └── sumdb/        ← チェックサムデータベース
└── src/              ← レガシー(Go Modules前)

🔑 モジュールキャッシュの内部構造

モジュールの保存形式:

github.com/gorilla/mux@v1.8.0.zip
↓ 展開
$GOPATH/pkg/mod/github.com/gorilla/mux@v1.8.0/
├── go.mod
├── go.sum
├── mux.go
├── route.go
└── ...

ビルド時:
1. go.mod で依存を検出
   require github.com/gorilla/mux v1.8.0

2. キャッシュを確認
   stat($GOPATH/pkg/mod/github.com/gorilla/mux@v1.8.0)

3. なければダウンロード
   https://proxy.golang.org/github.com/gorilla/mux/@v/v1.8.0.zip
   ↓
   $GOPATH/pkg/mod/cache/download/...
   ↓
   展開して $GOPATH/pkg/mod/... に配置

4. チェックサム検証
   go.sum の SHA256 と比較

GOOS と GOARCH - クロスコンパイルの秘密

# 現在のプラットフォーム
go env GOOS GOARCH
# 出力例: darwin amd64

# 利用可能な組み合わせ
go tool dist list

💡 サポートされるプラットフォーム

GOOS の値:
- darwin   (macOS)
- linux    (Linux)
- windows  (Windows)
- freebsd  (FreeBSD)
- openbsd  (OpenBSD)
- android  (Android)
- ios      (iOS)
...

GOARCH の値:
- amd64    (x86-64)
- arm64    (ARM 64-bit)
- arm      (ARM 32-bit)
- 386      (x86 32-bit)
- mips     (MIPS)
- wasm     (WebAssembly)
...

組み合わせ例:
linux/amd64     ← 最も一般的なサーバー
linux/arm64     ← Raspberry Pi 4, AWS Graviton
darwin/amd64    ← Intel Mac
darwin/arm64    ← M1/M2 Mac
windows/amd64   ← Windows PC
js/wasm         ← ブラウザ

🔑 クロスコンパイル時のコード生成

// 例: 同じGoコード
package main
import "fmt"
func main() {
    fmt.Println("Hello")
}

// darwin/amd64 でコンパイル
GOOS=darwin GOARCH=amd64 go build -o hello_mac

// linux/amd64 でコンパイル
GOOS=linux GOARCH=amd64 go build -o hello_linux

生成される機械語の違い

darwin/amd64 (Mach-O形式):
┌──────────────────────────────────────┐
│ Mach-O ヘッダー                       │
│ - マジックナンバー: 0xFEEDFACF       │
│ - CPU_TYPE: x86_64                   │
│ - CPU_SUBTYPE: ALL                   │
├──────────────────────────────────────┤
│ ロードコマンド                        │
│ - LC_SEGMENT_64 (__TEXT)             │
│ - LC_SEGMENT_64 (__DATA)             │
│ - LC_DYLD_INFO_ONLY                  │
├──────────────────────────────────────┤
│ __TEXT セグメント                     │
│   __text セクション                   │
│     機械語コード (x86-64)             │
│     syscall: int 0x2000004 (write)   │ ← macOS システムコール番号
├──────────────────────────────────────┤
│ __DATA セグメント                     │
│   文字列 "Hello\n"                    │
└──────────────────────────────────────┘

linux/amd64 (ELF形式):
┌──────────────────────────────────────┐
│ ELF ヘッダー                          │
│ - マジックナンバー: 0x7F 'E' 'L' 'F' │
│ - クラス: 64-bit                      │
│ - エンディアン: リトル                │
├──────────────────────────────────────┤
│ プログラムヘッダー                    │
│ - PT_LOAD (実行可能)                  │
│ - PT_LOAD (読み書き可能)              │
├──────────────────────────────────────┤
│ .text セクション                      │
│   機械語コード (x86-64)               │
│   syscall: syscall (番号1, write)    │ ← Linux システムコール番号
├──────────────────────────────────────┤
│ .rodata セクション                    │
│   文字列 "Hello\n"                    │
└──────────────────────────────────────┘

重要な違い:
1. バイナリフォーマット (Mach-O vs ELF)
2. システムコール番号 (OS依存)
3. ABIの違い (関数呼び出し規約)
4. 動的リンカのパス

GOPATHの構造(レガシー) - なぜ廃れたのか

Go 1.11以前は、GOPATHベースの開発が主流でした:

$GOPATH/
├── bin/        # 実行ファイル
├── pkg/        # コンパイル済みパッケージ
└── src/        # ソースコード
    └── github.com/
        └── username/
            └── project/

⚠️ GOPATH方式の問題点

問題1: グローバル名前空間
┌──────────────────────────────────────┐
│ $GOPATH/src/                         │
├──────────────────────────────────────┤
│ github.com/user1/mylib/              │
│ ├── v1.0.0 のコード                  │
│ └── 複数バージョン共存不可!         │
│                                      │
│ github.com/user2/app/                │
│ ├── mylib v1.0.0 に依存              │
│ └── 別プロジェクトは v2.0.0 が必要   │
│     → 両立不可能!                   │
└──────────────────────────────────────┘

問題2: プロジェクトの配置場所が固定
開発者のコード:
  /Users/alice/project/  ← ここで開発したい
  ↓ 不可能
  必ず $GOPATH/src/github.com/alice/project/ に配置必須

問題3: 依存バージョン管理が不可能
go get でダウンロード
  ↓
  常に最新版を取得
  ↓
  バージョン固定の仕組みなし
  ↓
  ビルドが再現不可能

現在(Go Modules時代): プロジェクトをどこにでも配置可能

~/projects/
└── myapp/
    ├── go.mod        ← このファイルがあればモジュール
    ├── go.sum        ← 依存のチェックサム
    └── main.go

利点:
1. 任意の場所に配置可能
2. 各プロジェクトが独立した依存を持つ
3. バージョンが明示的 (go.mod)
4. 再現可能なビルド (go.sum)

Go Modulesの基本 - 内部メカニズム

モジュールとは - 概念の深層

Go Modulesは、依存関係を管理する公式システムです(Go 1.11で導入)。

モジュールの構成要素

  • go.mod: モジュール名と依存関係を定義
  • go.sum: 依存関係のチェックサム(整合性検証)

🔑 なぜ go.mod と go.sum の2ファイルが必要なのか

go.mod の役割:
┌──────────────────────────────────────┐
│ module github.com/user/myapp         │ ← モジュール名
│                                      │
│ go 1.21                              │ ← Go バージョン
│                                      │
│ require (                            │
│     github.com/gorilla/mux v1.8.0    │ ← 直接依存
│     github.com/lib/pq v1.10.9        │
│ )                                    │
│                                      │
│ require (                            │
│     github.com/mattn/go-sqlite3      │ ← 間接依存
│         v1.14.18 // indirect         │    (推移的依存)
│ )                                    │
└──────────────────────────────────────┘

go.sum の役割:
┌──────────────────────────────────────┐
│ github.com/gorilla/mux v1.8.0        │
│   h1:abc...xyz                       │ ← SHA256 チェックサム
│ github.com/gorilla/mux v1.8.0/go.mod │
│   h1:def...uvw                       │ ← go.mod のチェックサム
│                                      │
│ github.com/lib/pq v1.10.9            │
│   h1:ghi...rst                       │
│ ...                                  │
└──────────────────────────────────────┘

なぜ2つ必要?
go.mod だけでは:
  × バージョンは固定できる
  × でもコードの内容は保証できない

  例: v1.8.0 というタグは削除して再作成可能
      → 同じバージョン番号でも中身が変わる可能性

go.sum も使うことで:
  ○ コードの内容まで保証
  ○ 改ざん検出
  ○ 再現可能なビルド

新しいモジュールの作成 - 完全解説

# プロジェクトディレクトリを作成
mkdir myapp
cd myapp

# モジュールの初期化
go mod init github.com/username/myapp

# go.modファイルが生成される

💡 go mod init の内部動作

1. カレントディレクトリを確認
   pwd → /Users/alice/projects/myapp

2. 既存の go.mod をチェック
   if exists("go.mod"):
       error("go.mod already exists")

3. モジュールパスを決定
   引数がある場合:
       modulePath = args[0]  // github.com/username/myapp
   引数がない場合:
       gitリモートから推測を試みる
       git remote get-url origin
         → https://github.com/username/myapp.git
         → modulePath = github.com/username/myapp

4. Go バージョンを検出
   goVersion = runtime.Version()  // "1.21"

5. go.mod を生成
   content = `module ` + modulePath + `\n\n`
   content += `go ` + goVersion + `\n`

   writeFile("go.mod", content)

6. 権限設定
   chmod("go.mod", 0644)  // rw-r--r--

生成されたgo.mod:

module github.com/username/myapp

go 1.21

🔑 go.mod の文法解析

go.mod のパーサー動作:

入力:
module github.com/username/myapp

go 1.21

require (
    github.com/gorilla/mux v1.8.0
)

字句解析:
TOKEN_MODULE "module"
TOKEN_STRING "github.com/username/myapp"
TOKEN_NEWLINE
TOKEN_GO "go"
TOKEN_VERSION "1.21"
...

構文解析:
AST (抽象構文木):
└─ ModFile
   ├─ Module
   │  └─ Path: "github.com/username/myapp"
   ├─ Go
   │  └─ Version: "1.21"
   └─ Require
      └─ Dependency
         ├─ Path: "github.com/gorilla/mux"
         └─ Version: "v1.8.0"

内部表現:
type ModFile struct {
    Module  *Module
    Go      *Go
    Require []*Require
}

依存関係の追加 - ダウンロードからリンクまで

// main.go
package main

import (
    "fmt"
    "github.com/fatih/color"  // 外部パッケージ
)

func main() {
    color.Green("Hello, Go Modules!")
}

# 依存関係を自動ダウンロード
go get github.com/fatih/color

# または、単にビルド/実行すると自動でダウンロード
go run main.go

🔑 go get の完全な内部動作

go get github.com/fatih/color の実行フロー:

1. モジュールパスの解決
   ┌────────────────────────────────────┐
   │ github.com/fatih/color を解析      │
   ├────────────────────────────────────┤
   │ VCS: Git                            │
   │ リポジトリ: github.com/fatih/color  │
   │ サブディレクトリ: なし              │
   └────────────────────────────────────┘

2. プロキシに問い合わせ (Go 1.13+)
   GET https://proxy.golang.org/github.com/fatih/color/@v/list

   応答:
   v1.0.0
   v1.1.0
   ...
   v1.16.0
   v1.17.0

3. 最新バージョンを選択
   latest = v1.17.0  (セマンティックバージョニング)

4. モジュール情報を取得
   GET https://proxy.golang.org/github.com/fatih/color/@v/v1.17.0.info

   応答:
   {
     "Version": "v1.17.0",
     "Time": "2023-11-10T10:00:00Z"
   }

5. go.mod を取得
   GET https://proxy.golang.org/github.com/fatih/color/@v/v1.17.0.mod

   応答:
   module github.com/fatih/color

   go 1.13

   require (
       github.com/mattn/go-colorable v0.1.13
       github.com/mattn/go-isatty v0.0.20
   )

6. 推移的依存を解決
   color が依存している:
   ├─ github.com/mattn/go-colorable v0.1.13
   └─ github.com/mattn/go-isatty v0.0.20

   これらも再帰的にダウンロード

7. ソースコードをダウンロード
   GET https://proxy.golang.org/github.com/fatih/color/@v/v1.17.0.zip

   ↓ 保存先
   $GOPATH/pkg/mod/cache/download/github.com/fatih/color/@v/v1.17.0.zip

8. ZIP を展開
   unzip → $GOPATH/pkg/mod/github.com/fatih/color@v1.17.0/

9. チェックサムを計算
   SHA256(v1.17.0.zip) → h1:abc...xyz

10. go.sum に記録
    github.com/fatih/color v1.17.0 h1:abc...xyz
    github.com/fatih/color v1.17.0/go.mod h1:def...uvw

11. go.mod を更新
    require github.com/fatih/color v1.17.0

12. 依存グラフをメモリに構築
    myapp
    └── github.com/fatih/color v1.17.0
        ├── github.com/mattn/go-colorable v0.1.13
        │   └── github.com/mattn/go-isatty v0.0.20
        │       └── golang.org/x/sys v0.14.0
        └── github.com/mattn/go-isatty v0.0.20
            └── golang.org/x/sys v0.14.0

更新後のgo.mod:

module github.com/username/myapp

go 1.21

require github.com/fatih/color v1.16.0

require (
    github.com/mattn/go-colorable v0.1.13 // indirect
    github.com/mattn/go-isatty v0.0.20 // indirect
    golang.org/x/sys v0.14.0 // indirect
)

💡 // indirect コメントの意味

require の種類:

直接依存 (コメントなし):
  require github.com/fatih/color v1.16.0
  ↑ あなたのコードが import している

間接依存 (// indirect):
  require github.com/mattn/go-colorable v0.1.13 // indirect
  ↑ あなたのコードは import していない
  ↑ 依存ライブラリが使用している

なぜ go.mod に書く?
  依存解決の最小バージョンを記録
  バージョン衝突を解決

依存関係の管理コマンド - 詳細動作

# 依存関係の追加
go get github.com/gorilla/mux@latest

# 特定バージョンを指定
go get github.com/gorilla/mux@v1.8.0

# 使われていない依存関係を削除
go mod tidy

# 依存関係をvendorディレクトリにコピー
go mod vendor

# 依存関係のグラフを表示
go mod graph

🔑 go mod tidy の動作原理

go mod tidy の実行フロー:

1. ソースコードをスキャン
   find . -name "*.go" -exec parse_imports {} \;

   main.go:
   import (
       "fmt"
       "github.com/fatih/color"
   )

   utils.go:
   import (
       "strings"
       "github.com/gorilla/mux"
   )

   → 使用パッケージ:
     - fmt (標準ライブラリ)
     - strings (標準ライブラリ)
     - github.com/fatih/color
     - github.com/gorilla/mux

2. go.mod の require を確認
   require (
       github.com/fatih/color v1.16.0
       github.com/gorilla/mux v1.8.0
       github.com/unused/package v1.0.0  ← コードで使われていない
   )

3. 未使用の依存を削除
   github.com/unused/package を削除

4. 不足している依存を追加
   もし新しい import があれば自動追加

5. 推移的依存を解決
   各依存の go.mod を読み取り
   必要な // indirect 依存を追加

6. go.sum を更新
   新しい依存のチェックサムを追加
   削除された依存のエントリは保持(履歴として)

7. ファイルを書き戻す
   go.mod と go.sum を保存

🔑 go mod vendor の詳細動作

go mod vendor の実行フロー:

1. 依存関係グラフを構築
   go.mod を解析 → 全依存をリストアップ

2. vendor/ ディレクトリを作成
   mkdir -p vendor

3. 各依存をコピー
   for each dependency:
       src = $GOPATH/pkg/mod/github.com/gorilla/mux@v1.8.0
       dst = ./vendor/github.com/gorilla/mux

       copy_tree(src, dst)

4. modules.txt を生成
   vendor/modules.txt:
   # github.com/gorilla/mux v1.8.0
   ## explicit; go 1.13
   github.com/gorilla/mux
   github.com/gorilla/mux/middleware

5. ビルド時の動作
   go build:
       if exists("vendor/"):
           use vendor instead of $GOPATH/pkg/mod

   利点:
   - リポジトリに全依存を含められる
   - ネットワーク不要でビルド可能
   - 依存が削除されても影響なし

   欠点:
   - リポジトリサイズが増大
   - vendor/ の更新を忘れがち

goコマンドの基本 - コンパイルプロセスの詳細

ビルドと実行 - 機械語生成まで

# 直接実行(開発時に便利)
go run main.go

# 複数ファイル
go run main.go utils.go

# カレントディレクトリの全ファイル
go run .

# ビルド(バイナリ生成)
go build

# 出力ファイル名を指定
go build -o myapp

# 特定のファイルをビルド
go build main.go

# リリースビルド(最適化)
go build -ldflags="-s -w" -o myapp

🔑 go run の完全な実行フロー

go run main.go の内部動作:

1. 一時ディレクトリを作成
   tmpdir = /var/folders/xx/yy/T/go-build123456789/

2. ソースファイルをパース
   parse("main.go") → AST (抽象構文木)

   AST 例:
   File {
       Package: "main"
       Imports: [
           {Path: "fmt"}
       ]
       Decls: [
           FuncDecl {
               Name: "main"
               Body: [
                   ExprStmt {
                       Call {
                           Fun: "fmt.Println"
                           Args: ["Hello"]
                       }
                   }
               ]
           }
       ]
   }

3. 型チェック
   type checker:
       - 変数の型を推論
       - 型の整合性を確認
       - 未定義識別子を検出

4. コンパイル
   compile main.go → main.o (オブジェクトファイル)

   コンパイラの処理:
   AST → SSA (Static Single Assignment) → 機械語

   SSA 例:
   v1 = ConstString "Hello"
   v2 = Call fmt.Println v1
   v3 = Return

   機械語生成 (amd64):
   0x1000: MOVQ $0x... %rax    // 文字列アドレスを rax に
   0x1008: MOVQ %rax, 0(%rsp)  // スタックに積む
   0x1010: CALL fmt.Println    // 関数呼び出し
   0x1015: RET                 // 戻る

5. リンク
   link main.o + fmt.a + runtime.a → 実行ファイル

   リンカの処理:
   - シンボル解決 (fmt.Println のアドレスを特定)
   - アドレス再配置 (CALL 命令のアドレスを確定)
   - セクション結合 (.text, .data, .rodata)
   - エントリポイント設定 (runtime.main → main.main)

6. 実行ファイルを一時ディレクトリに保存
   /var/folders/xx/yy/T/go-build123456789/b001/exe/main

7. 実行
   exec("/var/folders/.../main")

8. クリーンアップ (終了後)
   rm -rf /var/folders/xx/yy/T/go-build123456789/

🔑 go build の詳細動作

go build の実行フロー:

1. ビルドキャッシュを確認
   cache_dir = go env GOCACHE
   → ~/.cache/go-build/

   各ファイルのハッシュを計算:
   hash = SHA256(content + compiler_version + build_flags)

   if cache[hash] exists:
       use cached object file
   else:
       compile

2. 依存関係の解析
   import graph:
   main.go
   ├─ fmt
   │  ├─ io
   │  ├─ os
   │  └─ ...
   └─ mypackage
      └─ strings

   ビルド順序を決定 (トポロジカルソート):
   1. io
   2. os
   3. fmt
   4. strings
   5. mypackage
   6. main

3. 並列コンパイル
   parallel for each package:
       compile package → object file

   CPU コア数に応じて並列化
   GOMAXPROCS=8 なら最大8パッケージ同時コンパイル

4. リンク
   link all object files

   セクション配置:
   ┌──────────────────────────────────┐
   │ ELF/Mach-O/PE ヘッダー            │
   ├──────────────────────────────────┤
   │ .text (コード)                    │
   │   ├─ runtime.main                │
   │   ├─ main.main                   │
   │   ├─ fmt.Println                 │
   │   └─ ...                         │
   ├──────────────────────────────────┤
   │ .rodata (読み取り専用データ)      │
   │   ├─ 文字列リテラル              │
   │   └─ 定数                        │
   ├──────────────────────────────────┤
   │ .data (初期化済みグローバル変数)  │
   ├──────────────────────────────────┤
   │ .bss (未初期化グローバル変数)     │
   ├──────────────────────────────────┤
   │ .gopclntab (関数テーブル)         │
   │   ← スタックトレース用            │
   └──────────────────────────────────┘

5. 実行ファイルを出力
   chmod +x myapp

💡 ビルドキャッシュの効果

初回ビルド:
$ time go build
real    0m5.234s  ← 5秒

2回目のビルド (変更なし):
$ time go build
real    0m0.123s  ← 0.1秒 (40倍速い!)

変更後のビルド (main.go のみ変更):
$ time go build
real    0m0.534s  ← 0.5秒 (変更ファイルのみ再コンパイル)

キャッシュの仕組み:
┌────────────────────────────────────┐
│ ~/.cache/go-build/                 │
├────────────────────────────────────┤
│ a1/                                │
│   a1b2c3.../                       │
│     _pkg_.a  ← コンパイル済みパッケージ
│
│ hash計算:
│   content_hash = SHA256(source_code)
│   build_id = SHA256(
│       content_hash +
│       compiler_version +
│       GOOS +
│       GOARCH +
│       build_flags
│   )
│
│ キャッシュヒット判定:
│   if cache[build_id].mtime > source.mtime:
│       use cache
│   else:
│       rebuild
└────────────────────────────────────┘

リリースビルド最適化

# リリースビルド(最適化)
go build -ldflags="-s -w" -o myapp

🔑 -ldflags の詳細

-ldflags = リンカフラグ (linker flags)

-s: デバッグシンボルを削除
    .symtab セクションを削除
    ┌─ 通常のバイナリ ────────────┐
    │ .text   (10 MB)             │
    │ .data   (1 MB)              │
    │ .symtab (5 MB) ← 削除       │
    │   関数名とアドレスの対応表   │
    │   変数名とアドレスの対応表   │
    └─────────────────────────────┘

-w: DWARF デバッグ情報を削除
    .debug_* セクションを削除
    ┌─ 通常のバイナリ ────────────┐
    │ .debug_info (20 MB) ← 削除  │
    │   型情報、行番号情報         │
    │ .debug_line (3 MB) ← 削除   │
    │   ソースコード行番号マップ   │
    └─────────────────────────────┘

サイズ比較:
通常ビルド:
$ go build -o app
$ ls -lh app
-rwxr-xr-x  1 user  staff    15M  app

リリースビルド:
$ go build -ldflags="-s -w" -o app
$ ls -lh app
-rwxr-xr-x  1 user  staff   8.2M  app
  ↑ 約45%縮小

デメリット:
- デバッガが使えない (dlv, gdb)
- panic 時のスタックトレースに関数名が出ない
- プロファイリングが困難

用途:
開発: デバッグ情報ありでビルド
本番: -s -w で軽量化

その他の最適化フラグ

# バージョン情報を埋め込む
go build -ldflags="-X main.version=1.0.0 -X main.buildTime=$(date +%Y%m%d)"

# 静的リンク (外部ライブラリなし)
CGO_ENABLED=0 go build -ldflags="-s -w"

# 完全な最適化
go build \
    -ldflags="-s -w" \
    -trimpath \
    -buildmode=pie

# 最小サイズビルド
go build -ldflags="-s -w" -gcflags="all=-l -N" -trimpath

💡 各フラグの意味

-trimpath:
  バイナリに埋め込まれるファイルパスを削除

  通常:
  panic: runtime error
    /Users/alice/projects/myapp/main.go:10
    ↑ 開発者のパス情報が漏洩

  -trimpath 使用:
  panic: runtime error
    myapp/main.go:10
    ↑ 相対パスのみ

-buildmode=pie:
  Position Independent Executable
  ASLR (Address Space Layout Randomization) 対応

  メモリ配置:
  通常:
  ┌──────────────────────────────┐
  │ 0x400000: .text (常に固定)    │ ← 攻撃者が予測可能
  │ 0x600000: .data              │
  └──────────────────────────────┘

  PIE:
  実行ごとにランダムなアドレス
  ┌──────────────────────────────┐
  │ 0x7f8a1234: .text (ランダム)  │ ← 予測不可能
  │ 0x7f8a5678: .data            │
  └──────────────────────────────┘

  セキュリティ向上、わずかに性能低下

-gcflags="all=-l -N":
  コンパイラフラグ
  -l: インライン展開を無効化
  -N: 最適化を無効化

  デバッグビルド用(サイズ削減には不向き)

インストール

# $GOPATH/binにインストール
go install

# 外部ツールのインストール
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

🔑 go install の動作

go install の実行フロー:

1. ビルド
   go build と同様にコンパイル・リンク

2. インストール先を決定
   if GOBIN is set:
       install_dir = GOBIN
   else:
       install_dir = GOPATH/bin

   通常: ~/go/bin/

3. バイナリをコピー
   cp /tmp/go-build.../exe/myapp ~/go/bin/myapp

4. 実行権限を付与
   chmod +x ~/go/bin/myapp

5. PATH の確認
   if ~/go/bin not in PATH:
       warn "Add ~/go/bin to your PATH"

外部ツールのインストール

go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

💡 @latest の意味

バージョン指定の方法:

@latest:
  最新のセマンティックバージョンタグ
  v1.0.0, v1.1.0, v2.0.0 がある場合 → v2.0.0

@v1.54.2:
  特定バージョン

@v1:
  v1.x.x の最新
  v1.50.0, v1.54.2 がある場合 → v1.54.2

@master:
  ブランチを指定(非推奨)
  再現性がない

@commit_hash:
  特定コミット
  v0.0.0-20231110120000-abc123def456

テスト - 内部メカニズム

# すべてのテストを実行
go test

# サブディレクトリも含む
go test ./...

# 詳細出力
go test -v

# カバレッジ測定
go test -cover

# カバレッジレポート生成
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

🔑 go test の完全動作

go test の実行フロー:

1. テストファイルを検出
   find . -name "*_test.go"

   例:
   math.go      ← 本体
   math_test.go ← テスト

2. テストパッケージをビルド
   package math_test (外部テスト)
   または
   package math (内部テスト)

3. テストバイナリを生成
   compile:
       math.go + math_test.go → math.test

   バイナリ構造:
   ┌──────────────────────────────────┐
   │ runtime                          │
   │ testing パッケージ                │
   │ math パッケージ                   │
   │ math_test パッケージ              │
   │ ┌──────────────────────────────┐ │
   │ │ 生成されたmain関数:           │ │
   │ │ func main() {                │ │
   │ │     testing.Main(            │ │
   │ │         tests: [             │ │
   │ │             TestAdd,         │ │
   │ │             TestSubtract,    │ │
   │ │         ],                   │ │
   │ │         benchmarks: [        │ │
   │ │             BenchmarkAdd,    │ │
   │ │         ],                   │ │
   │ │     )                        │ │
   │ │ }                            │ │
   │ └──────────────────────────────┘ │
   └──────────────────────────────────┘

4. テストバイナリを実行
   exec ./math.test

5. 各テストを実行
   for each test in tests:
       setUp()
       result = runTest(test)
       tearDown()

       if result.failed:
           print "FAIL"
       else:
           print "PASS"

6. 結果を集計
   PASS math 0.123s

7. クリーンアップ
   rm math.test

カバレッジ測定の仕組み

go test -cover

💡 カバレッジ計測の内部動作

1. ソースコードを改変
   元のコード:
   func Add(a, b int) int {
       return a + b
   }

   計測用に変換:
   var GoCover_0_abc123 = struct {
       Count     [1]uint32
       Pos       [3]uint32
       NumStmt   [1]uint16
   }{
       Pos: [3]uint32{1, 2, 3},  // 行番号
       NumStmt: [1]uint16{1},    // 文の数
   }

   func Add(a, b int) int {
       GoCover_0_abc123.Count[0]++  ← カウンタを挿入
       return a + b
   }

2. テストを実行
   各ブロックの実行回数をカウント

3. 実行後に集計
   total_blocks = 100
   executed_blocks = 85
   coverage = 85 / 100 = 85%

4. カバレッジファイルに保存
   coverage.out:
   mode: set
   mypackage/math.go:1.23,2.10 1 1  ← 実行された
   mypackage/math.go:3.15,4.20 1 0  ← 実行されなかった

カバレッジHTML生成

go tool cover -html=coverage.out

HTML生成の仕組み:

1. coverage.out を解析
   executed_lines = [1, 2, 5, 6, 7]
   not_executed = [3, 4, 8]

2. ソースコードを読み込み
   source = readFile("math.go")

3. HTML生成
   <style>
   .cov0 { color: rgb(192, 0, 0) }      ← 未実行 (赤)
   .cov1 { color: rgb(0, 0, 0) }        ← 実行済み (黒)
   .cov8 { color: rgb(0, 128, 0) }      ← 複数回実行 (緑)
   </style>

   <pre>
   <span class="cov1">func Add(a, b int) int {</span>
   <span class="cov1">    return a + b</span>
   <span class="cov1">}</span>

   <span class="cov0">func Unused() {</span>  ← 赤色表示
   <span class="cov0">    // 未実行コード</span>
   <span class="cov0">}</span>
   </pre>

4. ブラウザで開く
   open coverage.html

標準ツールチェーン - 詳細動作

1. go fmt - コードフォーマッタ

Goはコードスタイルが統一されています。

# ファイルをフォーマット
go fmt main.go

# すべてのファイルをフォーマット
go fmt ./...

# gofmtを直接使用(より細かい制御)
gofmt -w main.go

🔑 go fmt の内部動作

1. ソースコードを読み込み
   source = readFile("main.go")

2. パース (構文解析)
   ast = parse(source)

   AST (抽象構文木):
   File
   ├─ Package: "main"
   ├─ Imports
   │  └─ "fmt"
   └─ Decls
      └─ FuncDecl
         ├─ Name: "main"
         └─ Body
            └─ ExprStmt
               └─ CallExpr
                  └─ "fmt.Println"

3. フォーマットルールを適用
   ルール:
   - インデント: タブ
   - 括弧の位置: K&R スタイル
   - 演算子の前後: スペース
   - 改行: 一貫性

4. AST から再生成
   formatted = print(ast)

   アルゴリズム:
   func print(node) {
       switch node.Type {
       case FuncDecl:
           write("func ")
           write(node.Name)
           write("(")
           for param in node.Params:
               write(param)
               write(", ")
           write(") ")
           if node.ReturnType:
               write(node.ReturnType)
               write(" ")
           write("{")
           newline()
           indent++
           print(node.Body)
           indent--
           write("}")
       }
   }

5. ファイルに書き戻す
   writeFile("main.go", formatted)

フォーマット例:

// フォーマット前
package main
import "fmt"
func main(){fmt.Println("Hello")}

// フォーマット後
package main

import "fmt"

func main() {
    fmt.Println("Hello")
}

💡 なぜタブを使うのか

Go の哲学:
1. 議論の余地をなくす
   スペース2個 vs 4個 → 終わりのない論争
   タブ → 設定不要、議論終了

2. アクセシビリティ
   視覚障害者がエディタで表示幅を調整可能
   タブ幅を8にする人、4にする人、両方対応

3. ファイルサイズ削減
   スペース4個 = 4バイト
   タブ1個 = 1バイト

2. go vet - 静的解析

コードの潜在的な問題を検出します。

# 静的解析を実行
go vet main.go

# すべてのパッケージを解析
go vet ./...

🔑 go vet の検査項目

1. Printf フォーマット文字列
   fmt.Printf("%d", "string")  ← エラー

   検査方法:
   - Printf の第1引数が文字列リテラルか確認
   - フォーマット指定子 (%d, %s, %v) を解析
   - 引数の型と照合

   AST 解析:
   CallExpr {
       Fun: "fmt.Printf"
       Args: [
           BasicLit{Value: "\"%d\""},  ← %d (int期待)
           BasicLit{Value: "\"string\""}  ← string型
       ]
   }
   ↓
   型不一致を検出

2. 無意味な比較
   if x < x { ... }  ← 常にfalse

   検査:
   BinaryExpr {
       X: Ident{Name: "x"}
       Op: "<"
       Y: Ident{Name: "x"}
   }
   ↓
   X と Y が同じ → 警告

3. 無限ループ
   for i := 0; i < 10; {  ← i が増加しない
       fmt.Println(i)
   }

4. unreachable code
   func example() {
       return
       fmt.Println("Never executed")  ← 到達不可能
   }

   制御フロー解析:
   CFG (Control Flow Graph):
   ┌─────────────┐
   │ return      │
   └─────────────┘
         ↓ (無条件)
   ┌─────────────┐
   │ Println     │ ← 到達不可能
   └─────────────┘

5. 未使用の変数
   x := 10  ← 使用されていない
   y := 20
   fmt.Println(y)

6. atomic 操作の誤用
   var x int64
   atomic.AddInt64(&x, 1)
   y := x  ← 競合状態!

7. defer の誤用
   defer mutex.Unlock()  ← OK
   defer mutex.Unlock    ← NG (関数ポインタのみ)

検出される問題の例:

// 問題あり: Printfのフォーマット指定子が間違っている
fmt.Printf("Value: %d", "string")  // vetが警告

// 問題あり: 無意味な比較
if x < x {  // vetが警告
    // ...
}

3. go test - テストランナー (詳細)

# テスト実行
go test

# ベンチマーク実行
go test -bench=.

# 特定のテストのみ実行
go test -run TestAdd

# 並列実行数を指定
go test -parallel 4

# レースコンディション検出
go test -race

🔑 -race フラグの動作原理

レースディテクタの仕組み:

1. コードを計装 (instrumentation)
   元のコード:
   var counter int
   counter++

   計装後:
   var counter int
   runtime.racewrite(&counter, sizeof(int))  ← 書き込み記録
   counter++

   読み取り:
   x := counter
   ↓
   runtime.raceread(&counter, sizeof(int))  ← 読み取り記録
   x := counter

2. 実行時に監視
   各goroutineのメモリアクセスを記録

   メモリアドレス 0x1234 への アクセス履歴:
   ┌────────────────────────────────────┐
   │ Goroutine 1: Write at 10:23:45.123 │
   │ Goroutine 2: Write at 10:23:45.125 │ ← 同時書き込み!
   └────────────────────────────────────┘

3. データ競合を検出
   条件:
   - 2つ以上の goroutine が
   - 同じメモリアドレスに
   - 少なくとも1つが書き込みで
   - 同期なしでアクセス

4. レポート出力
   WARNING: DATA RACE
   Write at 0x00c000010088 by goroutine 7:
     main.increment()
         /path/to/main.go:15 +0x3e

   Previous write at 0x00c000010088 by goroutine 6:
     main.increment()
         /path/to/main.go:15 +0x3e

テストファイルの例 (math_test.go):

package main

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)
    }
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

💡 ベンチマークの実行

go test -bench=.

ベンチマークの仕組み:

1. 最適なイテレーション数を決定
   初期値: b.N = 1

   測定:
   start = time.Now()
   for i := 0; i < b.N; i++ {
       Add(2, 3)
   }
   duration = time.Since(start)

   if duration < 1秒:
       b.N *= 2  // 倍増
       再測定
   else:
       十分なデータ

2. 統計情報を計算
   total_time = 1.234s
   iterations = 1000000
   ns_per_op = total_time / iterations = 1234 ns/op

3. 出力
   BenchmarkAdd-8    1000000    1234 ns/op
   ↑              ↑         ↑
   名前           並列数     1回あたりの時間

4. go build - ビルダー (詳細)

# 現在のプラットフォーム向けにビルド
go build

# クロスコンパイル
GOOS=linux GOARCH=amd64 go build

# ビルドタグを使用
go build -tags production

# リンカフラグでバイナリサイズ削減
go build -ldflags="-s -w"

# 静的リンク
CGO_ENABLED=0 go build

# ビルド情報を埋め込む
go build -ldflags="-X main.version=1.0.0"

🔑 ビルドタグの仕組み

// +build linux

package platform

func getPlatform() string {
    return "Linux"
}

// +build darwin

package platform

func getPlatform() string {
    return "macOS"
}

ビルド時:
GOOS=linux go build
  → linux タグのファイルのみコンパイル

GOOS=darwin go build
  → darwin タグのファイルのみコンパイル

複雑な条件:
// +build linux,amd64
  → linux AND amd64

// +build linux darwin
  → linux OR darwin

// +build !windows
  → NOT windows

プロジェクト構造のベストプラクティス - 設計思想

小規模プロジェクト

myapp/
├── go.mod
├── go.sum
├── main.go
├── handler.go
└── handler_test.go

中規模プロジェクト

myapp/
├── go.mod
├── go.sum
├── main.go
├── cmd/
│   ├── server/
│   │   └── main.go
│   └── client/
│       └── main.go
├── internal/
│   ├── handler/
│   │   ├── handler.go
│   │   └── handler_test.go
│   └── database/
│       └── db.go
└── pkg/
    └── utils/
        └── utils.go

🔑 internal/ の特別な意味

internal/ ディレクトリの仕組み:

コンパイラによる強制:
myapp/
├── internal/
│   └── secret/
│       └── api.go
└── cmd/
    └── main.go

myapp/cmd/main.go:
import "myapp/internal/secret"  ← OK

other-project/main.go:
import "myapp/internal/secret"  ← コンパイルエラー!
  error: use of internal package myapp/internal/secret not allowed

ルール:
internal/ とその親ディレクトリからのみアクセス可能

階層例:
a/
├── internal/
│   └── b/
│       └── c.go
├── d/
│   └── e.go
└── f/
    └── g.go

アクセス可能:
a/d/e.go → a/internal/b  ○
a/f/g.go → a/internal/b  ○

アクセス不可:
other-project → a/internal/b  ×

実装:
コンパイラがimport pathをチェック:
if strings.Contains(import_path, "/internal/"):
    caller_module = get_module_root(caller)
    internal_module = extract_before_internal(import_path)
    if caller_module != internal_module:
        error("cannot import internal package")

大規模プロジェクト (クリーンアーキテクチャ)

myapp/
├── go.mod
├── go.sum
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/
│   ├── domain/        ← ビジネスロジック(依存なし)
│   │   ├── user/
│   │   │   ├── user.go
│   │   │   └── repository.go  ← インターフェース
│   │   └── product/
│   ├── usecase/       ← ユースケース(domainに依存)
│   │   ├── user_usecase.go
│   │   └── product_usecase.go
│   ├── repository/    ← データアクセス(domainのinterfaceを実装)
│   │   ├── user_repository.go
│   │   └── product_repository.go
│   └── infrastructure/  ← 外部依存
│       ├── database/
│       │   └── postgres.go
│       ├── cache/
│       │   └── redis.go
│       └── http/
│           └── server.go
├── pkg/               ← 外部公開可能なライブラリ
│   ├── logger/
│   └── config/
├── api/
│   └── openapi.yaml
├── scripts/
├── configs/
└── docs/

💡 依存方向の原則

依存グラフ(Clean Architecture):

┌─────────────────────────────────────┐
│ cmd/                                │
│ (エントリポイント)                   │
└──────────┬──────────────────────────┘
           │ depends on
           ↓
┌─────────────────────────────────────┐
│ infrastructure/                      │
│ (HTTP, DB, Cache)                   │
└──────────┬──────────────────────────┘
           │ depends on
           ↓
┌─────────────────────────────────────┐
│ repository/                          │
│ (データアクセス実装)                 │
└──────────┬──────────────────────────┘
           │ implements
           ↓
┌─────────────────────────────────────┐
│ usecase/                             │
│ (ビジネスロジック)                   │
└──────────┬──────────────────────────┘
           │ depends on
           ↓
┌─────────────────────────────────────┐
│ domain/                              │
│ (エンティティ、インターフェース)     │
│ ← 依存されるのみ、他に依存しない     │
└─────────────────────────────────────┘

ルール:
1. 内側の層は外側を知らない
2. インターフェースで依存を逆転
3. domain/ が最も安定

ディレクトリの役割:

ディレクトリ 説明 依存
`cmd/` 実行ファイルのエントリーポイント 全てに依存可能
`internal/` 外部に公開しないパッケージ プロジェクト内のみ
`pkg/` 外部に公開可能なライブラリ 最小限の依存
`api/` API定義(OpenAPI、gRPCなど) スキーマのみ
`configs/` 設定ファイル なし
`scripts/` ビルド、デプロイスクリプト なし
`docs/` ドキュメント なし

エディタとIDEのセットアップ - 深層設定

Visual Studio Code

推奨拡張機能:

{
  "recommendations": [
    "golang.go",
    "streetsidesoftware.code-spell-checker"
  ]
}

settings.json:

{
  "go.useLanguageServer": true,
  "go.formatTool": "goimports",
  "go.lintTool": "golangci-lint",
  "go.lintOnSave": "package",
  "editor.formatOnSave": true
}

🔑 gopls (Go Language Server) の動作

Language Server Protocol (LSP):

┌─────────────────┐           ┌─────────────────┐
│   VS Code       │ ←──LSP──→ │   gopls         │
│  (クライアント)  │           │  (サーバー)      │
└─────────────────┘           └─────────────────┘
        ↑                             ↑
        │                             │
   ユーザー操作                   コード解析
   - 入力                        - パース
   - 補完要求                    - 型チェック
   - 定義ジャンプ                - シンボル解決

通信例:

1. ファイル開く
   Client → Server:
   {
     "method": "textDocument/didOpen",
     "params": {
       "uri": "file:///path/to/main.go",
       "text": "package main\n..."
     }
   }

2. 補完要求
   Client → Server:
   {
     "method": "textDocument/completion",
     "params": {
       "uri": "file:///path/to/main.go",
       "position": {"line": 5, "character": 10}
     }
   }

   Server → Client:
   {
     "result": {
       "items": [
         {"label": "Println", "kind": "Function"},
         {"label": "Printf", "kind": "Function"}
       ]
     }
   }

3. 定義ジャンプ
   fmt.Println にカーソル

   Server:
   - AST を検索
   - シンボルテーブルで fmt.Println を検索
   - 定義場所を特定: $GOROOT/src/fmt/print.go:274

   Client:
   - ファイルを開く
   - 274行目にジャンプ

デバッグツール - 深層メカニズム

Delve(公式デバッガ)

# インストール
go install github.com/go-delve/delve/cmd/dlv@latest

# デバッグモードで実行
dlv debug main.go

# ブレークポイントを設定
(dlv) break main.main
(dlv) continue

# 変数の値を表示
(dlv) print myVar

# スタックトレース
(dlv) stack

🔑 Delve の動作原理

デバッガの仕組み:

1. プロセスのアタッチ
   ptrace(PTRACE_ATTACH, pid)  ← Linuxシステムコール

2. ブレークポイントの設定
   元のコード:
   0x1234: MOVQ $0x10, %rax
   0x123c: ADDQ $0x5, %rax

   ブレークポイント設定:
   original_instruction = read_memory(0x1234)  // MOVQ
   write_memory(0x1234, 0xCC)  // INT 3 (ブレークポイント命令)

   変更後:
   0x1234: INT 3  ← トラップ
   0x123c: ADDQ $0x5, %rax

3. 実行
   プログラムが 0x1234 に到達
   → INT 3 を実行
   → CPU が例外を発生
   → OS がデバッガに通知

4. デバッガが制御を取得
   - 元の命令を復元
   - レジスタ、メモリを読み取り
   - ユーザーにプロンプト表示

5. 変数の表示
   (dlv) print myVar

   内部動作:
   - シンボルテーブルで myVar のアドレスを検索
   - DWARF デバッグ情報で型を確認
   - メモリからデータ読み取り
   - 型に応じてフォーマット

   例:
   myVar:
     Address: 0xc000010088
     Type: int
     Size: 8 bytes
     Value: read_memory(0xc000010088, 8) = 0x000000000000002A
           = 42 (10進数)

レースコンディション検出 (詳細)

# データ競合を検出
go run -race main.go
go test -race ./...

検出例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    counter := 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter++  // データ競合!
        }()
    }

    wg.Wait()
    fmt.Println(counter)
}

実行すると警告が表示されます:

WARNING: DATA RACE
Write at 0x... by goroutine 7:
  main.main.func1()
      /path/to/main.go:14 +0x3e

Previous write at 0x... by goroutine 6:
  main.main.func1()
      /path/to/main.go:14 +0x3e

💡 ThreadSanitizer の仕組み

Go の race detector は ThreadSanitizer を使用:

1. メモリアクセスの記録
   各 goroutine に shadow state を割り当て

   Goroutine 1:
   ┌──────────────────────────────────┐
   │ Shadow Memory                    │
   ├──────────────────────────────────┤
   │ 0x1234: {tid:1, clock:10, write} │
   │ 0x5678: {tid:1, clock:11, read}  │
   └──────────────────────────────────┘

   Goroutine 2:
   ┌──────────────────────────────────┐
   │ Shadow Memory                    │
   ├──────────────────────────────────┤
   │ 0x1234: {tid:2, clock:15, write} │ ← 同じアドレス!
   └──────────────────────────────────┘

2. ベクタークロック
   各 goroutine の論理時刻を追跡

   Goroutine 1: [10, 0, 0]
   Goroutine 2: [5, 15, 0]

   Happens-before 関係:
   もし G1[i] < G2[i] for all i:
       G1 happens before G2 (競合なし)
   そうでなければ:
       concurrent access (競合あり)

3. 検出
   counter++ を実行:

   Goroutine 7:
   - Read counter (0x1234)
   - Increment
   - Write counter (0x1234)

   Shadow memory チェック:
   previous_access = shadow[0x1234]
   if previous_access.tid != current.tid:
       if !happens_before(previous, current):
           RACE DETECTED!

パフォーマンスプロファイリング - 完全ガイド

CPUプロファイル

import (
    "os"
    "runtime/pprof"
)

func main() {
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // プロファイリング対象のコード
    heavyComputation()
}

# プロファイル解析
go tool pprof cpu.prof

# Webインターフェースで表示
go tool pprof -http=:8080 cpu.prof

🔑 CPU プロファイラの仕組み

サンプリングベースのプロファイリング:

1. タイマーを設定
   setitimer(ITIMER_PROF, 10ms)
   ↑ 10ミリ秒ごとに SIGPROF シグナルを送信

2. シグナルハンドラ
   signal_handler(SIGPROF):
       current_pc = get_program_counter()
       current_sp = get_stack_pointer()

       stack_trace = unwind_stack(current_pc, current_sp)

       record(stack_trace)

3. スタックアンワインド
   現在のPC: 0x1234 (function A)

   スタックフレーム:
   ┌──────────────────────┐ ← SP
   │ Return Address: 0x56 │ → function B
   ├──────────────────────┤
   │ Return Address: 0x78 │ → function C
   ├──────────────────────┤
   │ Return Address: 0x9a │ → main
   └──────────────────────┘

   記録:
   main → C → B → A  (100回)
   main → C → B      (50回)
   main → D          (25回)

4. 集計
   A: 100サンプル (40%)
   B: 150サンプル (60%)
   C: 150サンプル (60%)
   D: 25サンプル  (10%)
   main: 250サンプル (100%)

5. 可視化
   フレームグラフ:
   ┌───────────────────────────────────┐
   │ main (100%)                       │
   ├─────────────┬─────────────────────┤
   │ C (60%)     │ D (10%)             │
   ├─────────────┤                     │
   │ B (60%)     │                     │
   ├─────────────┤                     │
   │ A (40%)     │                     │
   └─────────────┴─────────────────────┘

メモリプロファイル

import (
    "os"
    "runtime/pprof"
)

func main() {
    heavyComputation()

    f, _ := os.Create("mem.prof")
    pprof.WriteHeapProfile(f)
    f.Close()
}

🔑 メモリプロファイラの仕組み

アロケーション追跡:

1. メモリ割り当てをフック
   mallocgc(size, typ, flags) {
       ptr = allocate_memory(size)

       if profiling_enabled {
           stack_trace = get_stack_trace()
           record_allocation(ptr, size, stack_trace)
       }

       return ptr
   }

2. 記録
   Allocation記録:
   ┌─────────────────────────────────────┐
   │ Address: 0xc000100000               │
   │ Size: 1024 bytes                    │
   │ Stack:                              │
   │   main.makeSlice:10                 │
   │   main.process:20                   │
   │   main.main:5                       │
   ├─────────────────────────────────────┤
   │ Address: 0xc000100400               │
   │ Size: 2048 bytes                    │
   │ Stack:                              │
   │   main.makeMap:15                   │
   │   main.main:6                       │
   └─────────────────────────────────────┘

3. 集計
   Stack trace別の合計:
   main.makeSlice → 10MB  (1000回)
   main.makeMap   → 5MB   (500回)

4. プロファイル出力
   # main.makeSlice main.go:10
   # 10485760 bytes (1000 allocations)

   # main.makeMap main.go:15
   # 5242880 bytes (500 allocations)

まとめ

この章では、Go開発環境の構築とツールチェーンについて、機械レベルまで深く学びました。

重要ポイント:

  • インストール: 自己完結したツールチェーン、プラットフォーム別の最適化
  • 環境変数: GOPATH、GOROOT、GOOS/GOARCHの内部動作
  • Go Modules: 依存解決、バージョン管理、チェックサムによる検証
  • goコマンド: コンパイル、リンク、キャッシュの仕組み
  • プロジェクト構造: internal/の特殊性、依存方向の原則
  • デバッグツール: Delve、レースディテクター、プロファイラの動作原理

機械レベルの理解:

  • Goコンパイラは AST → SSA → 機械語 の変換を行う
  • リンカはシンボル解決とアドレス再配置を実行
  • レースディテクタは ThreadSanitizer で並行アクセスを検出
  • プロファイラはサンプリングで性能ボトルネックを特定
  • 自己チェック問題

    基本問題

  • GOPATH と GOROOT の違いを説明してください。それぞれがコンパイル時にどのように使用されますか?
  • go mod tidy コマンドは何をするか、内部動作を含めて説明してください。
  • go build と go install の違いは何ですか?生成されるバイナリの配置先は?
  • internal/ ディレクトリの特別な意味と、コンパイラによる強制の仕組みを説明してください。
  • go run main.go を実行したとき、裏で何が起きているか、一時ファイルの生成からクリーンアップまで説明してください。
  • 中級問題

  • クロスコンパイル時に GOOS=linux GOARCH=amd64 を指定すると、生成される機械語にどのような違いが出ますか?
  • go get でパッケージをダウンロードする際、Go プロキシ (proxy.golang.org) を経由する理由は何ですか?
  • ビルドキャッシュはどのように動作し、どのような場合にキャッシュがヒットしますか?ハッシュ計算の仕組みを説明してください。
  • go vet はどのようにして Printf のフォーマット文字列の誤りを検出しますか?
  • gopls (Language Server) がコード補完を提供する仕組みを、LSPプロトコルレベルで説明してください。
  • 上級問題

  • go test -race でレースコンディションを検出する仕組みを、ThreadSanitizerとベクタークロックの観点から説明してください。
  • Delve デバッガがブレークポイントを設定する際、機械語レベルで何が起きていますか? INT 3 命令の役割は?
  • CPU プロファイラのサンプリング方式と、スタックアンワインドの仕組みを説明してください。
  • -ldflags="-s -w" でバイナリサイズが縮小される理由を、ELF/Mach-Oセクション構造の観点から説明してください。
  • Go Modules の go.sum ファイルが改ざん検出にどのように使われるか、チェックサム検証のフローを説明してください。

---

次の章では、Goの基本構文を学び、実際にコードを書いていきます。