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スキルを参照してください。
ベストプラクティスまとめ
- コンストラクタは関数型オプションを使うべき — APIの進化に伴いスケールしやすい(オプションごとに1関数、破壊的変更なし)
- 関数型オプションは、バリデーションが失敗する可能性がある場合、エラーを返す必要がある — 実行時ではなく構築時に不正な設定をキャッチする
init()を避ける — 暗黙的に実行され、エラーを返せず、テストを予測不能にする。明示的なコンストラクタを使用する- 列挙型は1から開始するべき(または0にUnknownセンチネル) — Goのゼロ値は最初の列挙メンバーとして静かに通過する
- エラーケースは早期リターンで最初に処理する必要がある — ハッピーパスをフラットに保つ
- パニックはバグ用であり、予期されるエラー用ではない — 呼び出し元は返されたエラーを処理できる; パニックはプロセスをクラッシュさせる
- オープン直後に
defer Close()— 後のコード変更で誤ってクリーンアップをスキップする可能性がある runtime.SetFinalizerではなくruntime.AddCleanup— ファイナライザは予測不能でオブジェクトを復活させる可能性がある- すべての外部呼び出しはタイムアウトを持つべき — 遅いアップストリームはゴルーチンを無期限にハングさせる
- すべてを制限する(プールサイズ、キュー深度、バッファ) — 無制限のリソースはクラッシュするまで成長する
- リトライロジックは試行間にコンテキストキャンセルをチェックする必要がある
- ループ内の文字列連結には**
strings.Builderを使用する** →samber/cc-skills-golang@golang-code-styleを参照 - string vs []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) - 小さな再実装 > 大きな依存関係 — 各依存関係は攻撃対象領域とメンテナンス負担を増やす
- テスタビリティを考慮して設計する — インターフェースを受け入れ、依存関係を注入する
コンストラクタパターン: 関数型オプション 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 |
[]byte | I/O | io.Writerへの書き込み、文字列構築、変更 |
[]rune | Unicode操作 | 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スキルを参照