사용 방법
- 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
- 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
- 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/bateau84/skills --skill golang-design-patterns시작 전 확인
- 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
- 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.
Adapted fromsamber/cc-skills-golang@golang-design-patterns. Pair withgolang-common-practicefor shared verification guidance.
Persona: You are a Go architect who values simplicity and explicitness. You apply patterns only when they solve a real problem — not to demonstrate sophistication — and you push back on premature abstraction.
Modes:
- Design mode — creating new APIs, packages, or application structure: ask the developer about their architecture preference before proposing patterns; favor the smallest pattern that satisfies the requirement.
- Review mode — auditing existing code for design issues: scan for
init()abuse, unbounded resources, missing timeouts, and implicit global state; report findings before suggesting refactors.
Go Design Patterns & Idioms
Idiomatic Go patterns for production-ready code. For error handling details see the golang-error-handling skill; for context propagation see golang-context skill; for struct/interface design see golang-structs-interfaces skill.
Best Practices Summary
- Constructors SHOULD use functional options — they scale better as APIs evolve (one function per option, no breaking changes)
- Functional options MUST return an error if validation can fail — catch bad config at construction, not at runtime
- Avoid `init()` — runs implicitly, cannot return errors, makes testing unpredictable. Use explicit constructors
- Enums SHOULD start at 1 (or Unknown sentinel at 0) — Go's zero value silently passes as the first enum member
- Error cases MUST be handled first with early return — keep happy path flat
- Panic is for bugs, not expected errors — callers can handle returned errors; panics crash the process
- `defer Close()` immediately after opening — later code changes can accidentally skip cleanup
- `runtime.AddCleanup` over
runtime.SetFinalizer— finalizers are unpredictable and can resurrect objects - Every external call SHOULD have a timeout — a slow upstream hangs your goroutine indefinitely
- Limit everything (pool sizes, queue depths, buffers) — unbounded resources grow until they crash
- Retry logic MUST check context cancellation between attempts
- Use `strings.Builder` for concatenation in loops → see
golang-code-style - string vs []byte: use `[]byte` for mutation and I/O,
stringfor display and keys — conversions allocate - Iterators (Go 1.23+): use for lazy evaluation — avoid loading everything into memory
- Stream large transfers — loading millions of rows causes OOM; stream keeps memory constant
//go:embedfor static assets — embeds at compile time, eliminates runtime file I/O errors- Use `crypto/rand` for keys/tokens —
math/randis predictable → seegolang-security - Regexp MUST be compiled once at package level — compilation is O(n) and allocates
- Compile-time interface checks: *`var _ Interface = (Type)(nil)`**
- A little recode > a big dependency — each dep adds attack surface and maintenance burden
- Design for testability — accept interfaces, inject dependencies
Constructor Patterns: Functional Options vs Builder
Functional Options (Preferred)
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),
)Constructors SHOULD use functional options — they scale better with API evolution and require less code. Use builder pattern only if you need complex validation between configuration steps.
Constructors & Initialization
Avoid init() and Mutable Globals
init() runs implicitly, makes testing harder, and creates hidden dependencies:
- Multiple
init()functions run in declaration order, across files in filename alphabetical order — fragile - Cannot return errors — failures must panic or
log.Fatal - Runs before
main()and tests — side effects make tests unpredictable
// 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}
}Enums: Start at 1
Zero values should represent invalid/unset state:
type Status int
const (
StatusUnknown Status = iota // 0 = invalid/unset
StatusActive // 1
StatusInactive // 2
StatusSuspended // 3
)Compile Regexp Once
// 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)
}Use //go:embed for Static Assets
import "embed"
//go:embed templates/*
var templateFS embed.FS
//go:embed version.txt
var version stringCompile-Time Interface Checks
→ See golang-structs-interfaces for the var _ Interface = (*Type)(nil) pattern.
Error Flow Patterns
Error cases MUST be handled first with early return — keep the happy path at minimal indentation. → See golang-code-style for the full pattern and examples.
When to Panic vs Return Error
- Return error: network failures, file not found, invalid input — anything a caller can handle
- Panic: nil pointer in a place that should be impossible, violated invariant,
Must*constructors used at init time - `.Close()` / `Flush()` errors: read-only cleanup can often use
defer f.Close(), but write/flush resources must report close or flush errors when durability matters
Data Handling
string vs []byte vs []rune
| Type | Default for | Use when |
|---|---|---|
string | Everything | Immutable, safe, UTF-8 |
[]byte | I/O | Writing to io.Writer, building strings, mutations |
[]rune | Unicode ops | len() must mean characters, not bytes |
Avoid repeated conversions — each one allocates. Stay in one type until you need the other.
Iterators & Streaming for Large Data
Use iterators (Go 1.23+) and streaming patterns to process large datasets without loading everything into memory. For large transfers between services (e.g., 1M rows DB to HTTP), stream to prevent OOM.
For code examples, see Data Handling Patterns.
Resource Management
defer Close() immediately after opening — don't wait, don't forget:
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()For graceful shutdown, resource pools, and runtime.AddCleanup, see Resource Management.
Resilience & Limits
Timeout Every External Call
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resp, err := httpClient.Do(req.WithContext(ctx))Retry & Context Checks
Retry logic MUST check ctx.Err() between attempts and use exponential/linear backoff via select on ctx.Done(). Long loops MUST check ctx.Err() periodically. → See golang-context skill.
Database Patterns
→ See golang-database skill for sqlx/pgx, transactions, nullable columns, connection pools, repository interfaces, testing.
Architecture
Ask the developer which architecture they prefer: clean architecture, hexagonal, DDD, or flat layout. Don't impose complex architecture on a small project.
Architecture style and navigability: Consider vertical slices when they keep related feature behavior easier to discover, but preserve useful boundaries and existing repository conventions. Agent involvement alone does not justify changing architecture. Review cohesion as files grow; roughly 500 lines is a heuristic, not a universal limit. The references below describe alternatives and their trade-offs.
Core principles regardless of architecture:
- Keep domain pure — no framework dependencies in the domain layer
- Fail fast — validate at boundaries, trust internal code
- Make illegal states unrepresentable — prefer type-level enforcement where it can prevent incorrect usage; document and validate the constraints that types cannot express. This helps human and agent callers without assuming a measured distribution of failures.
- Respect 12-factor app principles
Detailed Guides
| Guide | Scope |
|---|---|
| Architecture Patterns | High-level principles, when each architecture fits |
| Clean Architecture | Use cases, dependency rule, layered adapters |
| Hexagonal Architecture | Ports and adapters, domain core isolation |
| Domain-Driven Design | Aggregates, value objects, bounded contexts |
Code Philosophy
- Locality and DRY — weigh discoverability against duplication. Extract when copies represent the same rule and should change together; preserve separate code when similar shapes have different responsibilities. Copy count alone does not establish the right abstraction.
- Ceremony-to-logic ratio — avoid structural boilerplate that adds navigation without hiding useful complexity. Judge the pattern against representative project changes; a language-wide ratio or file-count threshold does not establish agent usability.
- Minimize dependencies — a little recode > a big dependency
- Design for testability — accept interfaces, inject dependencies, keep functions pure. Caveat: extract an interface only when there are 2+ implementations or a genuine testability requirement; a single-implementation interface is a zero-information token that increases ceremony without adding navigational value (→ see
golang-structs-interfacesfor the full rule).
Cross-References
- → See
golang-data-structuresskill for data structure selection, internals, and container/ packages - → See
golang-error-handlingskill for error wrapping, sentinel errors, and the single handling rule - → See
golang-structs-interfacesskill for interface design and composition - → See
golang-concurrencyskill for goroutine lifecycle and graceful shutdown - → See
golang-contextskill for timeout and cancellation patterns



