Go基礎 #7 パッケージとモジュール (go mod)
基礎シリーズの最終回。これまでのコードは1つのファイルで生きていましたが、実プロジェクトは複数のファイルと外部ライブラリが一緒に動きます。今回は — Goのコード組織化システム。
2つの単位 — パッケージとモジュール #
| パッケージ (package) | モジュール (module) | |
|---|---|---|
| 単位 | 1フォルダ | 複数フォルダの束ね |
| 定義 | package 名前 宣言 | go.mod ファイル |
| 例え | クラス/名前空間 | プロジェクト/ライブラリ |
1フォルダ = 1パッケージ、複数フォルダ + go.mod = 1モジュール。モジュールがより大きい単位です。
パッケージ — 1フォルダが1単位 #
同じフォルダのファイルはすべて同じパッケージに属さなければなりません。
calculator/
├── add.go (package calculator)
├── multiply.go (package calculator)
└── helper.go (package calculator)すべて package calculator で始まらなければなりません。違うパッケージ名が混ざるとコンパイルエラー。
mainパッケージ — 実行可能なプログラム #
package main
import "fmt"
func main() {
fmt.Println("hi")
}package main + func main() があるフォルダは実行可能なプログラム。それ以外のパッケージはライブラリとして他のコードがimportして使うコードです。
可視性 — 大文字始まり #
Goの可視性は非常にシンプルです。
名前が大文字で始まれば外部に公開(exported)、小文字ならパッケージ内でのみ可視(unexported)。
package math
// 外部から使用可能
func Add(a, b int) int {
return a + b
}
// 同じパッケージ内のみ使用可能
func square(n int) int {
return n * n
}
// 外部公開定数
const Pi = 3.14159
// 内部定数
const initialBuffer = 1024他の言語のような public/private キーワードは別途ありません。名前の最初の文字を見るだけで可視性が分かります。
import — 他のパッケージを取り込む #
標準ライブラリ #
import "fmt"
import "strings"
import "time"
// またはグループ形式で
import (
"fmt"
"strings"
"time"
)複数のimportはグループ形式が推奨されます。アルファベット順のソートは goimports が自動でやってくれます。
外部パッケージ #
import (
"github.com/google/uuid"
"github.com/spf13/cobra"
)GitHub URLのような形がそのままimport pathになります。Goのモジュールシステムがurlベースである理由です。
エイリアスとドットimport #
import (
f "fmt" // エイリアス
. "strings" // ドット — パッケージ名なしで直接使用 (非推奨)
_ "github.com/.../init" // 空識別子 — 副作用のみ (init 関数を実行)
)
f.Println("hi")エイリアスは同じ名前の衝突時に有用。ドットimportはコードがどこから来た関数かわかりにくくなるのでほとんど使いません。空識別子(_)はパッケージのinit関数だけ実行したいとき(ドライバ登録など)。
モジュール — go.mod
#
複数のパッケージをまとめた単位。go.mod ファイルが1つのモジュールのエントリーポイントです。
mkdir myapp
cd myapp
go mod init github.com/curtis/myappgo.mod が作られます。
module github.com/curtis/myapp
go 1.22- module — このモジュールのimport path。他のコードが
import "github.com/curtis/myapp/..."のように取り込みます。 - go — 使用するGoの最小バージョン。
モジュール名 — なぜGitHubパスなのか? #
GoはモジュールをURLで識別します。通常GitHub/GitLabのリポジトリパスがそのままモジュール名になります。
module github.com/curtis/myapp
module gitlab.com/team/service
module example.com/internal/utils // 私的ドメインGitHubに上げる予定がなくてもこの形式に従います。Goが依存関係をダウンロードするとき、そのURLに行って取得します(公開リポジトリの場合)。
複数のパッケージを持つモジュール #
myapp/
├── go.mod (module github.com/curtis/myapp)
├── main.go (package main)
├── auth/
│ ├── login.go (package auth)
│ └── register.go (package auth)
└── db/
└── connection.go (package db)main.go で他のパッケージを使う:
package main
import (
"fmt"
"github.com/curtis/myapp/auth"
"github.com/curtis/myapp/db"
)
func main() {
conn := db.Connect()
user := auth.Login("カーティス", "secret")
fmt.Println(conn, user)
}同じモジュール内のパッケージは — モジュール名 + フォルダパスでimport。
外部パッケージ追加 — go get
#
go get github.com/google/uuidこのコマンドが:
- パッケージをダウンロード
go.modに依存関係を追加go.sumにチェックサムを記録
module github.com/curtis/myapp
go 1.22
require github.com/google/uuid v1.6.0go.sum は依存関係の正確なバージョンを記録したファイル — 他の人が同じコードをビルドしても正確に同じバージョンが使われるよう保証します。
使用 #
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
id := uuid.New()
fmt.Println(id)
}go mod tidy — 整理
#
go mod tidyコードを解析して:
- 使用中なのにgo.modにない依存関係 → 追加
- go.modにあるのに使ってない依存関係 → 削除
作業ごとに一度実行する習慣がよいです。
依存関係のバージョン — semver #
require github.com/google/uuid v1.6.0semver(セマンティックバージョニング) — vメジャー.マイナー.パッチ。Goモジュールシステムは次の規則に従います。
- メジャー0 — 安定保証なし (実験)
- メジャー1+ — 互換保証 (マイナー/パッチ更新は互換)
- メジャー2+ — import pathに
/v2、/v3のようなsuffix必須
import "github.com/some/lib/v2"ライブラリがv2に上がるとimport path自体が変わります。下位互換を強制する設計です。
バージョン更新 #
# パッケージを最新に
go get github.com/google/uuid@latest
# 特定バージョン
go get github.com/google/uuid@v1.5.0
# すべての依存関係を最新に
go get -u ./...
# マイナー/パッチのみ最新に
go get -u=patch ./...同じモジュール内 — internalパッケージ #
myapp/
├── go.mod
├── main.go
├── api/
│ └── handler.go (package api)
└── internal/
└── auth/
└── login.go (package auth)internal/ フォルダ内のパッケージは — そのモジュールまたはそのモジュールの子孫だけがimport可能。外部モジュールは取り込めません。
ライブラリを作るとき — 公開APIと実装詳細を分離する標準パターンです。
パッケージinit関数 #
各パッケージは — 最初にimportされるとき自動実行される init() 関数を持てます。
package config
import "log"
var Settings map[string]string
func init() {
Settings = loadFromFile()
log.Println("config 読み込み完了")
}副作用(設定の読み込み、ドライバ登録)に使います。1パッケージに複数の init も可能です。
濫用は避けましょう — 明示的な初期化関数(Init())の方が普通明確です。
vendorフォルダ — オプション #
go mod vendor コマンドで依存関係をプロジェクト内にコピーできます。
go mod vendorvendor/ フォルダにすべての依存関係コードが入ります。ビルド時にそこから取得します。
長所: 外部ダウンロードなしでビルド可能 (オフライン、私設ネットワークで)。短所: リポジトリが大きくなります。
最近は go.mod + ローカルキャッシュ($GOPATH/pkg/mod)で十分なので、vendorはあまり使わない傾向です。
replace — ローカルパスで置換
#
ライブラリ開発中 — 実際の依存関係をローカルパスに一時的に置換。
require github.com/curtis/lib v1.0.0
replace github.com/curtis/lib => ../lib../lib フォルダのコードが使われます。ライブラリとそれを使うアプリを同時に開発するときに便利です。コミット前に普通は削除します。
よく使うコマンドまとめ #
go mod init <名前> # 新規モジュール
go mod tidy # 依存関係の整理
go mod download # 明示された依存関係のダウンロードのみ
go get <パッケージ> # 依存関係の追加
go get -u <パッケージ> # 更新
go mod why <パッケージ> # なぜこの依存関係が必要か
go mod graph # 依存関係グラフの出力
go mod vendor # vendor フォルダを作成軽いガイド — パッケージ設計 #
大きな設計ガイドを全部整理するのは難しいですが、よく役立ついくつか。
- パッケージ名は短く小文字 —
auth、db、config - パッケージが複数のことをするなら 細かく分割
- 循環依存(A → B、B → A)はコンパイルエラー — 設計が絡まったサイン
- 外部公開は最小化 — できるだけ小文字始まり
util、commonのような雑多パッケージは避ける — 意図が見えない
まとめ #
基礎シリーズ7編を通して扱った内容:
- はじめてのプログラム — セットアップとHello World (#1)
- 変数、型、定数 —
:=、iota、untyped const (#2) - 制御フロー — forが唯一、switch fallthroughなし (#3)
- 関数、多値返却、error —
if err != nil、defer (#4) - コレクション — sliceの動作原理、mapのcomma-ok (#5)
- 構造体とメソッド — 値/ポインタレシーバ、埋め込み (#6)
- パッケージとモジュール — 可視性、go.mod、internal (今回の記事)
ここまで掴めば小さなプログラムやCLIツールは自信を持って作れます。次の中級シリーズでは — インターフェース、並行性(ゴルーチン/チャネル)、コンテキスト、標準ライブラリといったGoの真の強みを扱います。