パッケージとモジュール
レッスン11:パッケージとモジュール
現実世界のアナロジー: 大きな図書館を想像してください。各本はカテゴリー別に異なる棚に置かれています — 文学、科学、歴史... これが「パッケージ」です。図書館の索引システムは各棚がどこにあるかを教えてくれます — これが「モジュール」です。すべての本を一つの部屋に詰め込む必要はありません。カテゴリー分けすることで効率的に必要なものを見つけられます。Goのパッケージシステムは、コードの世界における図書館の分類です。
コアコンセプト
| コンセプト | 説明 |
|---|---|
| パッケージ | Goソースコードの組織単位。ディレクトリ内のすべての .go ファイルが同じパッケージに属する |
| モジュール | 関連パッケージの集合で、go.mod ファイルによって定義される。依存関係管理の最小単位 |
| import | 他のパッケージからエクスポートされた識別子を使用する |
| エクスポートルール | 大文字で始まる識別子は外部パッケージからアクセス可能。小文字はパッケージプライベート |
| internal パッケージ | 特別なディレクトリ名。internalパッケージは親ディレクトリツリーのコードからのみインポート可能 |
| go get | リモートリポジトリからサードパーティパッケージをダウンロードしてインストール |
パッケージとモジュールの関係
my-module/ ← モジュールルート、go.mod を含む
├── go.mod
├── main.go ← package main
├── mathutil/ ← サブパッケージ mathutil
│ └── calc.go ← package mathutil
└── internal/ ← internal パッケージ
└── secret.go ← package internal
基本構文と使い方
1. パッケージ宣言
すべてのGoソースファイルの最初の行は package 宣言でなければなりません。
package main // 実行可能プログラムのエントリーパッケージ
package mathutil // ユーティリティパッケージ
.go ファイルは同じパッケージ名を宣言する必要があります(通常はディレクトリ名と一致)。
2. モジュールの初期化
# プロジェクトルートディレクトリで実行
go mod init my-module
# go.mod ファイルが生成される:
# module my-module
#
# go 1.24
github.com/username/my-module のようにすると、他の人が参照しやすくなります。
3. 依存関係の整理
# 不足している依存関係を追加し、未使用のものを削除
go mod tidy
go mod tidy を実行してください。
4. import 文
import "fmt" // 標準ライブラリ
import "github.com/gin-gonic/gin" // サードパーティパッケージ
// グループ化されたimport(推奨)
import (
"fmt"
"log"
"os"
)
5. エクスポートルール
package mathutil
var Pi = 3.14159 // 大文字で始まる → エクスポート、外部から参照可能
var version = "1.0" // 小文字で始まる → アンエクスポート、パッケージ内のみ
func Add(a, b int) int { // 大文字 → エクスポート
return a + b
}
func subtract(a, b int) int { // 小文字 → アンエクスポート
return a - b
}
6. internal パッケージ
myapp/
├── internal/
│ └── auth/ ← myapp/ とそのサブディレクトリのみインポート可能
│ └── auth.go
└── cmd/
└── server/
└── main.go ← ✅ myapp/internal/auth をインポート可能
internal はコンパイラが強制するアクセス制御であり、慣例ではありません — 親ディレクトリツリー外のパッケージはインポートできません。
7. サードパーティパッケージのインストール
# パッケージをダウンロードし、go.mod / go.sum を更新
go get github.com/gin-gonic/gin
# バージョンを指定
go get github.com/gin-gonic/gin@v1.9.1
# 未使用の依存関係を削除
go mod tidy
サンプルコード
例:カスタムパッケージの作成と使用(難易度⭐)
プロジェクト構造:
greet-app/
├── go.mod
├── main.go
└── greeting/
└── greet.go
greeting/greet.go:
package greeting
import "fmt"
// Hello は挨拶を返す(大文字、エクスポート)
func Hello(name string) string {
return fmt.Sprintf("こんにちは、%sさん!Goプログラミングへようこそ。", name)
}
// farewell は別れのメッセージを返す(小文字、アンエクスポート)
func farewell(name string) string {
return fmt.Sprintf("さようなら、%sさん!", name)
}
// Goodbye は farewell のエクスポートされたラッパー(大文字、エクスポート)
func Goodbye(name string) string {
return farewell(name)
}
main.go:
package main
import (
"fmt"
"greet-app/greeting" // ローカルのサブパッケージをインポート
)
func main() {
// エクスポートされた関数を使用
fmt.Println(greeting.Hello("Alice"))
fmt.Println(greeting.Goodbye("Alice"))
// ❌ 以下のコードはエラーになります:farewell はアンエクスポート
// fmt.Println(greeting.farewell("Alice"))
}
実行:
go run main.go
出力:
こんにちは、Aliceさん!Goプログラミングへようこそ。
さようなら、Aliceさん!
例:サードパーティパッケージの使用(難易度⭐⭐)
プロジェクトの初期化:
mkdir http-demo && cd http-demo
go mod init http-demo
go get github.com/gin-gonic/gin
main.go:
package main
import (
"net/http"
"github.com/gin-gonic/gin" // サードパーティのWebフレームワーク
)
// User はユーザー構造体を定義
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
r := gin.Default() // デフォルトエンジンを作成
// GETリクエスト:ウェルカムメッセージを返す
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Goパッケージ管理チュートリアルへようこそ!",
})
})
// GETリクエスト:ユーザー情報を返す
r.GET("/user", func(c *gin.Context) {
user := User{
Name: "Alice",
Email: "alice@example.com",
}
c.JSON(http.StatusOK, user)
})
// サーバーを起動し、ポート8080でリッスン
r.Run(":8080")
}
実行:
go run main.go
# サーバー起動後、http://localhost:8080 にアクセス
例:internal パッケージとマルチパッケージの連携(難易度⭐⭐⭐)
プロジェクト構造:
bank-system/
├── go.mod
├── main.go
├── account/
│ └── account.go ← Account パッケージ(公開)
├── internal/
│ └── validator/
│ └── validator.go ← Validator パッケージ(internal のみ)
└── transaction/
└── transaction.go ← Transaction パッケージ(公開)
go.mod:
module bank-system
go 1.24
internal/validator/validator.go:
package validator
import "errors"
// ValidateAmount はトランザクション金額を検証する(internalパッケージのみ)
func ValidateAmount(amount float64) error {
if amount <= 0 {
return errors.New("金額はゼロより大きくなければなりません")
}
if amount > 1000000 {
return errors.New("1回のトランザクションは100万を超えることはできません")
}
return nil
}
// ValidateAccountID はアカウントIDを検証する
func ValidateAccountID(id string) error {
if len(id) < 6 {
return errors.New("アカウントIDは6文字以上でなければなりません")
}
return nil
}
account/account.go:
package account
import (
"fmt"
"bank-system/internal/validator" // internal パッケージをインポート
)
// Account 構造体
type Account struct {
ID string
Name string
Balance float64
}
// NewAccount は新しいアカウントを作成する
func NewAccount(id, name string, balance float64) (*Account, error) {
// internal パッケージの検証関数を使用
if err := validator.ValidateAccountID(id); err != nil {
return nil, fmt.Errorf("アカウント作成失敗: %w", err)
}
if balance < 0 {
return nil, fmt.Errorf("初期残高はマイナスにできません")
}
return &Account{
ID: id,
Name: name,
Balance: balance,
}, nil
}
// Deposit は入金する
func (a *Account) Deposit(amount float64) error {
// internal パッケージの検証関数を使用
if err := validator.ValidateAmount(amount); err != nil {
return fmt.Errorf("入金失敗: %w", err)
}
a.Balance += amount
return nil
}
// GetBalance は残高を取得する
func (a *Account) GetBalance() float64 {
return a.Balance
}
transaction/transaction.go:
package transaction
import (
"fmt"
"bank-system/account"
"bank-system/internal/validator" // internal パッケージもインポート可能
)
// Transfer は振込関数
func Transfer(from, to *account.Account, amount float64) error {
// internal パッケージで金額を検証
if err := validator.ValidateAmount(amount); err != nil {
return fmt.Errorf("振込失敗: %w", err)
}
if from.GetBalance() < amount {
return fmt.Errorf("振込失敗: 残高不足(現在: %.2f)", from.GetBalance())
}
// 振込を実行
if err := from.Deposit(-amount); err != nil {
return fmt.Errorf("引落し失敗: %w", err)
}
if err := to.Deposit(amount); err != nil {
// ロールバック
from.Deposit(amount)
return fmt.Errorf("入金失敗: %w", err)
}
return nil
}
main.go:
package main
import (
"fmt"
"bank-system/account"
"bank-system/transaction"
// ❌ 以下のインポートは失敗します:internal パッケージは外部からインポート不可
// "bank-system/internal/validator"
)
func main() {
// 2つのアカウントを作成
acc1, err := account.NewAccount("ACC001", "Alice", 10000)
if err != nil {
fmt.Println("エラー:", err)
return
}
acc2, err := account.NewAccount("ACC002", "Bob", 5000)
if err != nil {
fmt.Println("エラー:", err)
return
}
fmt.Printf("振込前 - Alice: %.2f、Bob: %.2f\n", acc1.GetBalance(), acc2.GetBalance())
// 振込を実行
err = transaction.Transfer(acc1, acc2, 3000)
if err != nil {
fmt.Println("エラー:", err)
return
}
fmt.Printf("振込後 - Alice: %.2f、Bob: %.2f\n", acc1.GetBalance(), acc2.GetBalance())
// 無効な金額のテスト
err = acc1.Deposit(-100)
if err != nil {
fmt.Println("予想されるエラー:", err)
}
}
実行:
go run main.go
出力:
振込前 - Alice: 10000.00、Bob: 5000.00
振込後 - Alice: 7000.00、Bob: 8000.00
予想されるエラー: 入金失敗: 金額はゼロより大きくなければなりません
現実世界の使用場面
場面1:再利用可能なロガーパッケージの構築
my-app/
├── go.mod
├── main.go
└── logger/
└── logger.go
logger/logger.go:
package logger
import (
"fmt"
"os"
"time"
)
// Level はログレベル
type Level int
const (
LevelDebug Level = iota // 0
LevelInfo // 1
LevelWarn // 2
LevelError // 3
)
// Logger 構造体
type Logger struct {
prefix string
level Level
}
// New はロガーインスタンスを作成する
func New(prefix string, level Level) *Logger {
return &Logger{
prefix: prefix,
level: level,
}
}
// log はログを出力する内部メソッド
func (l *Logger) log(level Level, tag, msg string) {
if level < l.level {
return
}
timestamp := time.Now().Format("2006-01-02 15:04:05")
line := fmt.Sprintf("[%s] [%s] [%s] %s\n", timestamp, tag, l.prefix, msg)
os.Stdout.WriteString(line)
}
// Debug はデバッグログ
func (l *Logger) Debug(msg string) {
l.log(LevelDebug, "DEBUG", msg)
}
// Info は情報ログ
func (l *Logger) Info(msg string) {
l.log(LevelInfo, "INFO", msg)
}
// Warn は警告ログ
func (l *Logger) Warn(msg string) {
l.log(LevelWarn, "WARN", msg)
}
// Error はエラーログ
func (l *Logger) Error(msg string) {
l.log(LevelError, "ERROR", msg)
}
main.go:
package main
import (
"my-app/logger"
)
func main() {
log := logger.New("APP", logger.LevelInfo)
log.Debug("これはInfoレベル以下のため表示されません")
log.Info("アプリケーションが起動しました")
log.Warn("ディスク容量が不足しています")
log.Error("データベース接続に失敗しました")
}
出力:
[2026-06-26 10:30:00] [INFO] [APP] アプリケーションが起動しました
[2026-06-26 10:30:00] [WARN] [APP] ディスク容量が不足しています
[2026-06-26 10:30:00] [ERROR] [APP] データベース接続に失敗しました
場面2:レイヤードアーキテクチャでのパッケージ構成
典型的なWebサービスプロジェクトの構造:
web-service/
├── go.mod
├── go.sum
├── main.go
├── config/
│ └── config.go ← 設定管理
├── internal/
│ ├── model/
│ │ └── user.go ← データモデル
│ ├── repository/
│ │ └── user_repo.go ← データアクセス層
│ └── service/
│ └── user_service.go ← ビジネスロジック層
└── handler/
└── user_handler.go ← HTTPハンドラ層
config/config.go:
package config
// Config はアプリケーション設定
type Config struct {
Port string
DBHost string
DBPort int
LogLevel string
}
// Load は設定を読み込む(簡略化した例)
func Load() *Config {
return &Config{
Port: "8080",
DBHost: "localhost",
DBPort: 5432,
LogLevel: "info",
}
}
internal/model/user.go:
package model
// User モデル
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
internal/repository/user_repo.go:
package repository
import (
"fmt"
"web-service/internal/model"
)
// UserRepository はユーザーデータリポジトリ
type UserRepository struct {
users map[int]*model.User
nextID int
}
// NewUserRepository はリポジトリインスタンスを作成する
func NewUserRepository() *UserRepository {
return &UserRepository{
users: make(map[int]*model.User),
nextID: 1,
}
}
// Create はユーザーを作成する
func (r *UserRepository) Create(name, email string) *model.User {
user := &model.User{
ID: r.nextID,
Name: name,
Email: email,
}
r.users[r.nextID] = user
r.nextID++
return user
}
// FindByID はIDでユーザーを検索する
func (r *UserRepository) FindByID(id int) (*model.User, bool) {
user, ok := r.users[id]
return user, ok
}
// FindAll はすべてのユーザーを検索する
func (r *UserRepository) FindAll() []*model.User {
result := make([]*model.User, 0, len(r.users))
for _, u := range r.users {
result = append(result, u)
}
return result
}
// String はリポジトリの概要を返す
func (r *UserRepository) String() string {
return fmt.Sprintf("UserRepository{合計%d人のユーザー}", len(r.users))
}
main.go:
package main
import (
"fmt"
"web-service/config"
"web-service/internal/repository"
)
func main() {
// 設定を読み込む
cfg := config.Load()
fmt.Println("ポート:", cfg.Port)
// リポジトリを使用
repo := repository.NewUserRepository()
repo.Create("Alice", "alice@example.com")
repo.Create("Bob", "bob@example.com")
fmt.Println(repo)
// すべてのユーザーを検索
for _, u := range repo.FindAll() {
fmt.Printf(" ID=%d、Name=%s、Email=%s\n", u.ID, u.Name, u.Email)
}
}
出力:
ポート: 8080
UserRepository{合計2人のユーザー}
ID=1、Name=Alice、Email=alice@example.com
ID=2、Name=Bob、Email=bob@example.com
❓ よくある質問
質問1:パッケージ名とディレクトリ名は一致させる必要がある?
強制ではありませんが、強く推奨されます。Goの慣例では、パッケージ名はディレクトリ名と一致させるべきです。異なっていても動作しますが、混乱を招きます。
// myutil/calc.go
package calculator // パッケージ名がディレクトリ名と一致しない
// インポートはパスを使用し、使用時はパッケージ名を使用
import "my-module/myutil" // パス
calculator.Add(1, 2) // パッケージ名を使用
質問2:go mod init のモジュールパスは何でもいい?
はい、ただしリポジトリパスの使用が推奨されます:
# ✅ 推奨:他の人が参照しやすい
go mod init github.com/yourname/yourproject
# ⚠️ 問題なし:ローカルプロジェクト
go mod init myproject
# ❌ 避けるべき:特殊文字やスペース
go mod init "my project"
質問3:なぜ go.sum ファイルが必要?
go.sum は各依存関係の暗号化ハッシュを記録し、ダウンロードされたコードが改ざんされていないことを保証します。go.mod と一緒にバージョン管理にコミットすべきです:
# go.mod — 依存関係とバージョンを宣言
# go.sum — 依存関係の完全性を検証
# 両方とも不可欠
git add go.mod go.sum
質問4:同じパッケージ内の複数ファイルはどうやって整理する?
同じディレクトリ内のすべての .go ファイルは同じパッケージを共有し、インポートせずに直接互いを参照できます:
// mathutil/add.go
package mathutil
func Add(a, b int) int { return a + b }
// mathutil/multiply.go — 同じパッケージ、Add を直接使用可能
package mathutil
func Multiply(a, b int) int {
result := 0
for i := 0; i < b; i++ {
result = Add(result, a) // 直接呼び出し、インポート不要
}
return result
}
📖 まとめ
| 重要ポイント | 内容 |
|---|---|
| パッケージ宣言 | すべての .go ファイルは最初の行にパッケージ名を宣言する必要がある |
| モジュール初期化 | go mod init <module-path> で go.mod を作成 |
| 依存関係管理 | go mod tidy で依存関係を整理、go get でサードパーティパッケージをインストール |
| エクスポートルール | 大文字 = エクスポート、小文字 = パッケージプライベートのみ |
| internal パッケージ | コンパイラが強制するアクセス制御、親ディレクトリツリーからのみインポート可能 |
| ベストプラクティス | パッケージ名はディレクトリ名と一致、責任別に整理、循環依存を回避 |
📝 演習
演習1:文字列ユーティリティパッケージの作成
以下のエクスポートされた関数を持つ stringutil パッケージを作成してください:
Reverse(s string) string— 文字列を逆順にするToUpper(s string) string— 大文字に変換するWordCount(s string) int— 単語数を数える
そして main.go でインポートして使用してください。
演習2:internal パッケージの練習
設定を読み込む internal/config パッケージ(構造体として)と、その設定を使用する app パッケージを持つプロジェクトを作成してください。検証:app パッケージは internal/config をインポートできますが、外部パッケージはインポートできないことを確認してください。
演習3:マルチパッケージ電卓
以下のパッケージを持つ電卓プロジェクトを設計してください:
calc/operation— 基本演算(加算、減算、乗算、除算)calc/scientific— 科学演算(累乗、階乗)internal/validator— 入力検証(ゼロ除算チェック、負数チェックなど)
要件:scientific パッケージは operation パッケージの関数を呼び出すことができ、すべての検証ロジックは internal/validator に配置します。



