Communityコーディング&開発github.com

viccom/golang-design-patterns

Go言語の慣用的なデザインパターン集 — 関数型オプション、コンストラクタ、エラーフロー、リソース管理、グレースフルシャットダウン、回復性、アーキテクチャ、依存性注入、データ処理、ストリーミングなど。アーキテクチャパターンの選択、関数型オプションの実装、コンストラクタAPIの設計、グレースフルシャットダウンの設定、回復性パターンの適用、または特定の問題に適したGoの慣用的なパターンを尋ねる際に使用します。

golang-design-patterns とは?

このスキルは、Go言語でプロダクション品質のコードを書くための慣用的なデザインパターンとベストプラクティスを包括的に提供します。関数型オプション、エラーハンドリング、リソース管理、回復性、ストリーミングデータ処理など、実際の問題を解決するためのパターンに焦点を当てており、不必要な抽象化には反対します。Claude Code、Cursor、CodexなどのAIエージェントプラットフォームで動作し、「デザインモード」と「レビューモード」の2つのモードをサポートします。デザインモードではアーキテクチャの選択を開発者に促し、最小限のパターンを提案します。レビューモードでは既存コードの`init()`乱用やリソース制限なしなどの設計上の問題をスキャンします。関数型オプションパターンはAPIの進化に強く推奨され、`init()`関数の代わりに明示的なコンストラクタを使うべきとされています。このスキルは他のGoスキル(エラーハンドリング、コンテキスト、構造体/インターフェース設計など)と連携し、コードベース全体にわたって一貫したGoのイディオムを適用するためのリファレンスガイドや詳細なリソースも提供します。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/golang-design-patterns

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Persona: あなたはシンプルさと明示性を重視するGoアーキテクトです。熟練を示すためではなく、実際の問題を解決する場合にのみパターンを適用し、時期尚早な抽象化には反対します。

Modes:

  • デザインモード — 新しいAPI、パッケージ、またはアプリケーション構造を作成する場合: パターンを提案する前に開発者にアーキテクチャの好みを尋ね、要件を満たす最小のパターンを優先します。
  • レビューモード — 設計上の問題について既存のコードを監査する場合: init()の乱用、無制限のリソース、タイムアウトの欠如、暗黙的なグローバル状態をスキャンし、リファクタリングを提案する前に結果を報告します。

コミュニティデフォルト。 明示的にsamber/cc-skills-golang@golang-design-patternsスキルを上書きする会社のスキルが優先されます。

Go デザインパターン & イディオム

プロダクション対応コードのための慣用的なGoパターン。エラーハンドリングの詳細はsamber/cc-skills-golang@golang-error-handlingスキルを、コンテキスト伝播はsamber/cc-skills-golang@golang-contextスキルを、構造体/インターフェース設計はsamber/cc-skills-golang@golang-structs-interfacesスキルを参照してください。

ベストプラクティスまとめ

  1. コンストラクタは関数型オプションを使うべき — APIの進化に伴いスケールしやすい(オプションごとに1関数、破壊的変更なし)
  2. 関数型オプションは、バリデーションが失敗する可能性がある場合、エラーを返す必要がある — 実行時ではなく構築時に不正な設定をキャッチする
  3. init()を避ける — 暗黙的に実行され、エラーを返せず、テストを予測不能にする。明示的なコンストラクタを使用する
  4. 列挙型は1から開始するべき(または0にUnknownセンチネル) — Goのゼロ値は最初の列挙メンバーとして静かに通過する
  5. エラーケースは早期リターンで最初に処理する必要がある — ハッピーパスをフラットに保つ
  6. パニックはバグ用であり、予期されるエラー用ではない — 呼び出し元は返されたエラーを処理できる; パニックはプロセスをクラッシュさせる
  7. オープン直後にdefer Close() — 後のコード変更で誤ってクリーンアップをスキップする可能性がある
  8. runtime.SetFinalizerではなくruntime.AddCleanup — ファイナライザは予測不能でオブジェクトを復活させる可能性がある
  9. すべての外部呼び出しはタイムアウトを持つべき — 遅いアップストリームはゴルーチンを無期限にハングさせる
  10. すべてを制限する(プールサイズ、キュー深度、バッファ) — 無制限のリソースはクラッシュするまで成長する
  11. リトライロジックは試行間にコンテキストキャンセルをチェックする必要がある
  12. ループ内の文字列連結には**strings.Builderを使用する** → samber/cc-skills-golang@golang-code-styleを参照
  13. string vs []byte: 変更とI/Oには[]byte、表示とキーにはstringを使用する — 変換はメモリを割り当てる
  14. イテレータ(Go 1.23+): 遅延評価に使用する — すべてをメモリにロードするのを避ける
  15. 大規模転送はストリーミングする — 数百万行のロードはOOMを引き起こす; ストリーミングはメモリを一定に保つ
  16. 静的なアセットには**//go:embed** — コンパイル時に埋め込み、実行時のファイルI/Oエラーを排除する
  17. キー/トークンには**crypto/randを使用する** — math/randは予測可能 → samber/cc-skills-golang@golang-securityを参照
  18. 正規表現はパッケージレベルで一度だけコンパイルする必要がある — コンパイルはO(n)でメモリを割り当てる
  19. コンパイル時のインターフェースチェック: var _ Interface = (*Type)(nil)
  20. 小さな再実装 > 大きな依存関係 — 各依存関係は攻撃対象領域とメンテナンス負担を増やす
  21. テスタビリティを考慮して設計する — インターフェースを受け入れ、依存関係を注入する

コンストラクタパターン: 関数型オプション vs ビルダー

関数型オプション(推奨)

type Server struct {
    addr         string
    readTimeout  time.Duration
    writeTimeout time.Duration
    maxConns     int
}

type Option func(*Server)

func WithReadTimeout(d time.Duration) Option {
    return func(s *Server) { s.readTimeout = d }
}

func WithWriteTimeout(d time.Duration) Option {
    return func(s *Server) { s.writeTimeout = d }
}

func WithMaxConns(n int) Option {
    return func(s *Server) { s.maxConns = n }
}

func NewServer(addr string, opts ...Option) *Server {
    // デフォルトオプション
    s := &Server{
        addr:         addr,
        readTimeout:  5 * time.Second,
        writeTimeout: 10 * time.Second,
        maxConns:     100,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// 使用法
srv := NewServer(":8080",
    WithReadTimeout(30*time.Second),
    WithMaxConns(500),
)

コンストラクタは関数型オプションを使うべきです — APIの進化に伴いスケールしやすく、より少ないコードで済みます。設定ステップ間で複雑なバリデーションが必要な場合にのみビルダーパターンを使用してください。

コンストラクタと初期化

init()とミュータブルなグローバルを避ける

init()は暗黙的に実行され、テストを難しくし、隠れた依存関係を作り出します:

  • 複数のinit()関数は宣言順に実行され、ファイルをファイル名のアルファベット順で横断します — 脆弱
  • エラーを返せない — 失敗はパニックまたはlog.Fatalでなければならない
  • main()およびテストの前に実行される — 副作用によりテストが予測不能になる
// 悪い例 — 隠れたグローバル状態
var db *sql.DB

func init() {
    var err error
    db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err != nil {
        log.Fatal(err)
    }
}

// 良い例 — 明示的な初期化、注入可能
func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}

列挙型: 1から開始

ゼロ値は無効/未設定の状態を表すべきです:

type Status int

const (
    StatusUnknown Status = iota // 0 = 無効/未設定
    StatusActive                // 1
    StatusInactive              // 2
    StatusSuspended             // 3
)

正規表現は一度だけコンパイル

// 良い例 — パッケージレベルで一度だけコンパイル
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

func ValidateEmail(email string) bool {
    return emailRegex.MatchString(email)
}

静的なアセットには//go:embedを使用

import "embed"

//go:embed templates/*
var templateFS embed.FS

//go:embed version.txt
var version string

コンパイル時のインターフェースチェック

→ var _ Interface = (*Type)(nil)パターンについてはsamber/cc-skills-golang@golang-structs-interfacesを参照。

エラーフローパターン

エラーケースは早期リターンで最初に処理する必要があります — ハッピーパスは最小限のインデントに保ちます。→ 完全なパターンと例についてはsamber/cc-skills-golang@golang-code-styleを参照。

パニック vs エラーを返す場合

  • エラーを返す: ネットワーク障害、ファイルが見つからない、無効な入力 — 呼び出し元が処理できるものすべて
  • パニック: 不可能なはずの場所でのnilポインタ、不変条件違反、初期化時に使用されるMust*コンストラクタ
  • .Close() / Flush()エラー: 読み取り専用のクリーンアップは多くの場合defer f.Close()で問題ないが、耐久性が重要な場合、書き込み/フラッシュリソースはクローズまたはフラッシュエラーを報告する必要がある

データ処理

string vs []byte vs []rune

型デフォルト使用する状況
stringすべて不変、安全、UTF-8
[]byteI/Oio.Writerへの書き込み、文字列構築、変更
[]runeUnicode操作len()がバイト数ではなく文字数を意味する必要がある場合

繰り返しの変換は避けてください — それぞれメモリを割り当てます。他の型が必要になるまで、1つの型に留まってください。

大規模データのためのイテレータとストリーミング

イテレータ(Go 1.23+)とストリーミングパターンを使用して、大規模データセットをメモリにすべてロードすることなく処理します。サービス間の大規模転送(例: DBからHTTPへの100万行)では、OOMを防ぐためにストリーミングしてください。

コード例については、データ処理パターンを参照してください。

リソース管理

オープン直後にdefer Close() — 待たず、忘れずに:

f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close() // ここで、50行後ではない

rows, err := db.QueryContext(ctx, query)
if err != nil {
    return err
}
defer rows.Close()

グレースフルシャットダウン、リソースプール、runtime.AddCleanupについては、リソース管理を参照してください。

回復性と制限

すべての外部呼び出しにタイムアウト

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

resp, err := httpClient.Do(req.WithContext(ctx))

リトライとコンテキストチェック

リトライロジックは試行間にctx.Err()をチェックし、ctx.Done()のselectを介して指数/線形バックオフを使用する必要があります。長いループは定期的にctx.Err()をチェックする必要があります。→ samber/cc-skills-golang@golang-contextスキルを参照。

データベースパターン

→ sqlx/pgx、トランザクション、NULL可能カラム、コネクションプール、リポジトリインターフェース、テストについてはsamber/cc-skills-golang@golang-databaseスキルを参照。

アーキテクチャ

開発者にどのアーキテクチャを好むか尋ねてください: クリーンアーキテクチャ、ヘキサゴナル、DDD、またはフラットレイアウト。小規模プロジェクトに複雑なアーキテクチャを押し付けないでください。

アーキテクチャに関係なく、コアとなる原則:

  • ドメインを純粋に保つ — ドメインレイヤーにフレームワーク依存関係を持たせない
  • 早期に失敗する — 境界で検証し、内部コードを信頼する
  • 不正な状態を表現不可能にする — 型を使用して不変条件を強制する
  • 12ファクターアプリの原則を尊重する — → samber/cc-skills-golang@golang-project-layoutを参照

詳細ガイド

ガイド範囲
アーキテクチャパターン高レベルの原則、各アーキテクチャが適している場合
クリーンアーキテクチャユースケース、依存関係ルール、階層化アダプター
ヘキサゴナルアーキテクチャポートとアダプター、ドメインコアの分離
ドメイン駆動設計集約、値オブジェクト、境界づけられたコンテキスト

コード哲学

  • 繰り返しコードを避ける — ただし時期尚早に抽象化しない
  • 依存関係を最小限にする — 小さな再実装 > 大きな依存関係
  • テスタビリティを考慮して設計する — インターフェースを受け入れ、依存関係を注入し、関数を純粋に保つ

相互参照

  • → データ構造の選択、内部構造、およびcontainer/パッケージについてはsamber/cc-skills-golang@golang-data-structuresスキルを参照
  • → エラーラッピング、センチネルエラー、および単一ハンドリングルールについてはsamber/cc-skills-golang@golang-error-handlingスキルを参照
  • → インターフェース設計とコンポジションについてはsamber/cc-skills-golang@golang-structs-interfacesスキルを参照
  • → ゴルーチンのライフサイクルとグレースフルシャットダウンについてはsamber/cc-skills-golang@golang-concurrencyスキルを参照
  • → タイムアウトとキャンセルパターンについてはsamber/cc-skills-golang@golang-contextスキルを参照
  • → アーキテクチャとディレクトリ構造についてはsamber/cc-skills-golang@golang-project-layoutスキルを参照
  • → 既存のコードベース全体でこれらのパターンのいずれか(オプション構造体、DI、コンシューマーサイドインターフェース)への移行を安全にステージングする方法についてはsamber/cc-skills-golang@golang-refactoringスキルを参照

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

関連スキル