golang-design-patterns について
Go の API、パッケージ、アプリケーション構成の設計や既存コードのレビューに使うスキルです。シンプルで明示的なパターンを重視し、コンストラクター、リソース管理、耐障害性、アーキテクチャを扱います。
主な機能
- 01設計モードとレビュー モード
- 02関数オプション型コンストラクターの指針
- 03明示的な初期化とエラー処理
- 04タイムアウト・再試行・リソース制限
- 05アーキテクチャとテスト容易性の原則
活用シーン
- 01Go API のパターン選定
- 02暗黙状態やリソースリスクのレビュー
- 03Go アプリケーションの構成
- 04大規模データ転送の効率的な処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-design-patternsインストール後、Claude Code で/golang-design-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/architecture.mdclean-architecture.mddata-handling.mdddd.mdhexagonal-architecture.mdresource-management.md詳細
ペルソナ: シンプルさと明確さを重視する Go アーキテクトです。パターンは、洗練さを示すためではなく、実際の問題を解決するときだけ適用し、時期尚早な抽象化には異を唱えます。
モード:
- 設計モード — 新しい 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 スキルを参照してください。
ベストプラクティスの概要
- コンストラクターでは 関数オプションを使用すべきです — API の進化に合わせて拡張しやすくなります(オプションごとに関数を用意でき、互換性を損なう変更も不要です)
- 検証で失敗する可能性がある関数オプションは、エラーを返さなければなりません — 実行時ではなく、構築時に不正な設定を検出します
init()は避けてください — 暗黙的に実行され、エラーを返せず、テストの予測可能性を損ないます。明示的なコンストラクターを使用してください- 列挙型は 1 から始めるべきです(または 0 に Unknown センチネルを置きます)— Go のゼロ値は、最初の列挙メンバーとして意図せず通過してしまいます
- エラーケースは 先に処理し、早期リターンしてください — 正常系の処理をフラットに保ちます
- panic はバグに使うもので、想定されるエラーには使いません — 呼び出し元は返されたエラーを処理できますが、panic はプロセスをクラッシュさせます
- 開いた直後に
defer Close()を記述してください — 後のコード変更でクリーンアップが誤ってスキップされることを防げます runtime.SetFinalizerよりruntime.AddCleanupを使用してください — ファイナライザーは動作が予測しにくく、オブジェクトを復活させる可能性があります- 外部への呼び出しにはすべて タイムアウトを設定すべきです — 上流が遅いと、ゴルーチンが無期限に停止します
- すべてに上限を設けてください(プールサイズ、キューの深さ、バッファーなど)— 上限のないリソースはクラッシュするまで増大します
- リトライ処理では、試行の間に コンテキストのキャンセルを確認しなければなりません
- ループ内での文字列連結には
strings.Builderを使用してください →samber/cc-skills-golang@golang-code-styleを参照 - string と []byte の使い分け:変更や I/O には
[]byte、表示やキーにはstringを使用してください — 変換にはメモリ割り当てが発生します - イテレーター(Go 1.23 以降):遅延評価に使用してください — すべてをメモリに読み込むのは避けます
- 大容量の転送はストリーミングしてください — 数百万行を読み込むと OOM が発生しますが、ストリーミングならメモリ使用量を一定に保てます
- 静的アセットには
//go:embedを使用してください — コンパイル時に埋め込むことで、実行時のファイル I/O エラーをなくします - キーやトークンには
crypto/randを使用してください —math/randは予測可能です →samber/cc-skills-golang@golang-securityを参照 - 正規表現は パッケージレベルで一度だけコンパイルしなければなりません — コンパイルは O(n) であり、メモリ割り当ても発生します
- コンパイル時のインターフェース適合チェック:
var _ Interface = (*Type)(nil) - 大きな依存関係より、小さな再実装を選びましょう — 依存関係が増えるたびに、攻撃対象領域と保守負担が増えます
- テストしやすさを考慮して設計してください — インターフェースを受け取り、依存関係を注入します
コンストラクターパターン:関数オプションとビルダー
関数オプション(推奨)
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 {
// Default options
s := &Server{
addr: addr,
readTimeout: 5 * time.Second,
writeTimeout: 10 * time.Second,
maxConns: 100,
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
srv := NewServer(":8080",
WithReadTimeout(30*time.Second),
WithMaxConns(500),
)
コンストラクターでは 関数オプションを使用すべきです — API の進化に合わせて拡張しやすく、必要なコードも少なくて済みます。設定手順の間に複雑な検証が必要な場合に限り、ビルダーパターンを使用してください。
コンストラクターと初期化
init() と可変グローバル変数を避ける
init() は暗黙的に実行され、テストを難しくし、隠れた依存関係を生み出します。
- 複数の
init()関数は宣言順に実行され、ファイルをまたぐ場合は ファイル名のアルファベット順 に実行されます — 不安定な仕組みです - エラーを返せません — 失敗時は panic するか
log.Fatalを使う必要があります main()とテストの前に実行されます — 副作用によってテストの予測可能性が損なわれます
// Bad — hidden global state
var db *sql.DB
func init() {
var err error
db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
}
// Good — explicit initialization, injectable
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
列挙型:1 から始める
ゼロ値は無効または未設定の状態を表すようにします。
type Status int
const (
StatusUnknown Status = iota // 0 = invalid/unset
StatusActive // 1
StatusInactive // 2
StatusSuspended // 3
)
正規表現は一度だけコンパイルする
// Good — compiled once at package level
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 を参照してください。
panic とエラーのリターンを使い分ける
- エラーを返す:ネットワーク障害、ファイルが見つからない場合、無効な入力など、呼び出し元が対処できるもの
- panic:起こり得ないはずの場所での nil ポインター、インバリアント違反、初期化時に使う
Must*コンストラクター .Close()/Flush()のエラー:読み取り専用のクリーンアップではdefer f.Close()を使えることが多いですが、永続性が重要な場合、書き込み/フラッシュを行うリソースではクローズまたはフラッシュのエラーを報告する必要があります
データ処理
string と []byte と []rune
| 型 | デフォルトの用途 | 使用する場面 |
|---|---|---|
string | あらゆる用途 | 不変、安全、UTF-8 |
[]byte | I/O | io.Writer への書き込み、文字列の構築、変更 |
[]rune | Unicode 操作 | len() がバイト数ではなく文字数を表す必要がある場合 |
変換の繰り返しは避けてください。変換のたびにメモリが割り当てられます。別の型が必要になるまでは、同じ型を使い続けてください。
大量データにはイテレーターとストリーミングを使う
すべてをメモリに読み込まずに大規模なデータセットを処理するには、イテレーター(Go 1.23 以降)とストリーミングのパターンを使います。サービス間で大量のデータを転送する場合(例:DB の 100 万行を HTTP に転送する場合)は、OOM を防ぐためストリーミングしてください。
コード例については、データ処理のパターンを参照してください。
リソース管理
開いた直後に defer Close() を実行してください。後回しにせず、忘れないようにします。
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // right here, not 50 lines later
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スキルを参照してください