Communitygithub.com

golang-patterns

관용적인 Go 패턴, 모범 사례 및 규칙으로, 견고하고 효율적이며 유지보수 가능한 Go 애플리케이션을 구축하기 위한 것입니다. Go 코드를 작성하거나 리뷰할 때, 관용적인 구조나 규칙에 의문이 있을 때 사용하세요.

golang-patterns란 무엇인가요?

이 스킬은 Go 개발자가 관용적인 Go 패턴과 모범 사례를 준수하여 견고하고 효율적이며 유지보수 가능한 코드를 작성할 수 있도록 안내합니다. 단순성, 명확성, 제로 값 활용성, 인터페이스 수용 및 구조체 반환 원칙 등 Go의 핵심 철학을 강조합니다. 또한 에러 래핑, 사용자 정의 에러 타입, 컨텍스트 기반 취소, 작업자 풀(worker pool), 그레이스풀 셧다운(graceful shutdown), 고루틴 누수 방지 등 일반적인 동시성 패턴을 포함한 포괄적인 에러 처리 전략을 다룹니다. 이 스킬은 함수형 옵션 패턴, 작고 집중된 인터페이스, 컴포지션을 위한 임베딩, 패키지 구성 지침 등 구조체 및 패키지 설계에 대한 실용적인 조언을 제공하며, 성능 최적화 기법(슬라이스 사전 할당, `sync.Pool`, `strings.Builder`) 및 필수 Go 도구(테스트, 정적 분석, 모듈 관리)도 포함합니다. Claude Code, Cursor, Codex 등 모든 AI 코딩 어시스턴트에서 사용할 수 있으며, Go 코드를 처음 작성하거나 리팩토링하거나 리뷰할 때 가장 효과적입니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/golang-patterns

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Go 개발 패턴

견고하고 효율적이며 유지보수 가능한 애플리케이션을 구축하기 위한 관용적인 Go 패턴과 모범 사례입니다.

활성화 시점

  • 새로운 Go 코드를 작성할 때
  • Go 코드를 리뷰할 때
  • 기존 Go 코드를 리팩토링할 때
  • Go 패키지/모듈을 설계할 때

핵심 원칙

1. 단순성과 명확성

Go는 영리함보다 단순함을 선호합니다. 코드는 명확하고 읽기 쉬워야 합니다.

// 좋음: 명확하고 직접적
func GetUser(id string) (*User, error) {
    user, err := db.FindUser(id)
    if err != nil {
        return nil, fmt.Errorf("get user %s: %w", id, err)
    }
    return user, nil
}

// 나쁨: 지나치게 영리함
func GetUser(id string) (*User, error) {
    return func() (*User, error) {
        if u, e := db.FindUser(id); e == nil {
            return u, nil
        } else {
            return nil, e
        }
    }()
}

2. 제로 값(Zero Value)을 유용하게 만들기

초기화 없이도 제로 값(Zero Value)을 바로 사용할 수 있도록 타입을 설계하세요.

// 좋음: 제로 값이 유용함
type Counter struct {
    mu    sync.Mutex
    count int // 제로 값은 0, 즉시 사용 가능
}

func (c *Counter) Inc() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

// 좋음: bytes.Buffer는 제로 값으로 동작함
var buf bytes.Buffer
buf.WriteString("hello")

// 나쁨: 초기화 필요
type BadCounter struct {
    counts map[string]int // nil 맵은 패닉을 일으킴
}

3. 인터페이스를 받고, 구조체를 반환하라

함수는 인터페이스 매개변수를 받고 구체적인 타입을 반환해야 합니다.

// 좋음: 인터페이스를 받고, 구체적인 타입을 반환함
func ProcessData(r io.Reader) (*Result, error) {
    data, err := io.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return &Result{Data: data}, nil
}

// 나쁨: 인터페이스를 반환함 (불필요하게 구현 세부사항을 숨김)
func ProcessData(r io.Reader) (io.Reader, error) {
    // ...
}

에러 처리 패턴

컨텍스트를 포함한 에러 래핑

// 좋음: 컨텍스트로 에러를 감쌈
func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("load config %s: %w", path, err)
    }

    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("parse config %s: %w", path, err)
    }

    return &cfg, nil
}

사용자 정의 에러 타입

// 도메인 특화 에러 정의
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

// 일반적인 경우를 위한 센티널 에러
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

errors.Is와 errors.As를 사용한 에러 확인

func HandleError(err error) {
    // 특정 에러 확인
    if errors.Is(err, sql.ErrNoRows) {
        log.Println("No records found")
        return
    }

    // 에러 타입 확인
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        log.Printf("Validation error on field %s: %s",
            validationErr.Field, validationErr.Message)
        return
    }

    // 알 수 없는 에러
    log.Printf("Unexpected error: %v", err)
}

에러를 절대 무시하지 마라

// 나쁨: 빈 식별자로 에러 무시
result, _ := doSomething()

// 좋음: 에러를 처리하거나 무시해도 안전한 이유를 명시적으로 문서화
result, err := doSomething()
if err != nil {
    return err
}

// 허용 가능: 에러가 정말로 중요하지 않은 경우 (드묾)
_ = writer.Close() // 최선 노력 정리, 에러는 다른 곳에 기록됨

동시성 패턴

작업자 풀 (Worker Pool)

func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
    var wg sync.WaitGroup

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- process(job)
            }
        }()
    }

    wg.Wait()
    close(results)
}

취소 및 타임아웃을 위한 컨텍스트 (Context)

func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch %s: %w", url, err)
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

그레이스풀 셧다운 (Graceful Shutdown)

func GracefulShutdown(server *http.Server) {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    <-quit
    log.Println("Shutting down server...")

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

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }

    log.Println("Server exited")
}

조정된 고루틴을 위한 errgroup

import "golang.org/x/sync/errgroup"

func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make([][]byte, len(urls))

    for i, url := range urls {
        i, url := i, url // 루프 변수 캡처
        g.Go(func() error {
            data, err := FetchWithTimeout(ctx, url)
            if err != nil {
                return err
            }
            results[i] = data
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

고루틴 누수 방지

// 나쁨: 컨텍스트가 취소되면 고루틴 누수 발생
func leakyFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte)
    go func() {
        data, _ := fetch(url)
        ch <- data // 수신자가 없으면 영원히 블로킹됨
    }()
    return ch
}

// 좋음: 취소를 적절히 처리함
func safeFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte, 1) // 버퍼 있는 채널
    go func() {
        data, err := fetch(url)
        if err != nil {
            return
        }
        select {
        case ch <- data:
        case <-ctx.Done():
        }
    }()
    return ch
}

인터페이스 설계

작고 집중된 인터페이스

// 좋음: 단일 메서드 인터페이스
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// 필요에 따라 인터페이스 구성
// ...
// func process(r io.Reader) { ... }

인터페이스는 사용되는 곳에서 정의하라

// 제공자 패키지가 아닌 소비자 패키지에서
package service

// UserStore는 이 서비스가 필요로 하는 것을 정의함
type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type Service struct {
    store UserStore
}

// 구체적인 구현은 다른 패키지에 있을 수 있음
// 이 인터페이스에 대해 알 필요가 없음

타입 단언을 통한 선택적 동작

type Flusher interface {
    Flush() error
}

func WriteAndFlush(w io.Writer, data []byte) error {
    if _, err := w.Write(data); err != nil {
        return err
    }

    // 지원되는 경우 Flush 실행
    if f, ok := w.(Flusher); ok {
        return f.Flush()
    }
    return nil
}

패키지 구성

표준 프로젝트 레이아웃

myproject/
├── cmd/
│   └── myapp/
│       └── main.go           # 진입점
├── internal/
│   ├── handler/              # HTTP 핸들러
│   ├── service/              # 비즈니스 로직
│   ├── repository/           # 데이터 접근
│   └── config/               # 설정
├── pkg/
│   └── client/               # 공개 API 클라이언트
├── api/
│   └── v1/                   # API 정의 (proto, OpenAPI)
├── testdata/                 # 테스트 픽스처
├── go.mod
├── go.sum
└── Makefile

패키지 이름 짓기

// 좋음: 짧고, 소문자이며, 밑줄 없음
package http
package json
package user

// 나쁨: 장황하거나, 대소문자 혼용하거나, 중복됨
package httpHandler
package json_parser
package userService // 중복된 'Service' 접미사

패키지 수준 상태 피하기

// 나쁨: 전역 가변 상태
var db *sql.DB

func init() {
    db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}

// 좋음: 의존성 주입
type Server struct {
    db *sql.DB
}

func NewServer(db *sql.DB) *Server {
    return &Server{db: db}
}

구조체 설계

함수형 옵션 패턴

type Server struct {
    addr    string
    timeout time.Duration
    logger  *log.Logger
}

type Option func(*Server)

func WithTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.timeout = d
    }
}

func WithLogger(l *log.Logger) Option {
    return func(s *Server) {
        s.logger = l
    }
}

func NewServer(addr string, opts ...Option) *Server {
    s := &Server{
        addr:    addr,
        timeout: 30 * time.Second, // 기본값
        logger:  log.Default(),    // 기본값
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// 사용법
server := NewServer(":8080",
    WithTimeout(60*time.Second),
    WithLogger(customLogger),
)

컴포지션을 위한 임베딩

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

type Server struct {
    *Logger // 임베딩 - Server가 Log 메서드를 얻음
    addr    string
}

func NewServer(addr string) *Server {
    return &Server{
        Logger: &Logger{prefix: "SERVER"},
        addr:   addr,
    }
}

// 사용법
s := NewServer(":8080")
s.Log("Starting...") // 임베디드 Logger.Log 호출

메모리 및 성능

크기를 알 때 슬라이스 미리 할당하기

// 나쁨: 슬라이스가 여러 번 커짐
func processItems(items []Item) []Result {
    var results []Result
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

// 좋음: 단일 할당
func processItems(items []Item) []Result {
    results := make([]Result, 0, len(items))
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

빈번한 할당을 위해 sync.Pool 사용하기

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func ProcessRequest(data []byte) []byte {
    buf := bufferPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()
        bufferPool.Put(buf)
    }()

    buf.Write(data)
    // 처리...
    return buf.Bytes()
}

루프에서 문자열 연결 피하기

// 나쁨: 많은 문자열 할당 생성
func join(parts []string) string {
    var result string
    for _, p := range parts {
        result += p + ","
    }
    return result
}

// 좋음: strings.Builder로 단일 할당
func join(parts []string) string {
    var sb strings.Builder
    for i, p := range parts {
        if i > 0 {
            sb.WriteString(",")
        }
        sb.WriteString(p)
    }
    return sb.String()
}

// 최고: 표준 라이브러리 사용
func join(parts []string) string {
    return strings.Join(parts, ",")
}

Go 도구 통합

필수 명령어

# 빌드 및 실행
go build ./...
go run ./cmd/myapp

# 테스트
go test ./...
go test -race ./...
go test -cover ./...

# 정적 분석
go vet ./...
staticcheck ./...
golangci-lint run

# 모듈 관리
go mod tidy
go mod verify

# 포매팅
gofmt -w .
goimports -w .

권장 린터 설정 (.golangci.yml)

linters:
  enable:
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck
    - unused
    - gofmt
    - goimports
    - misspell
    - unconvert
    - unparam

linters-settings:
  errcheck:
    check-type-assertions: true
  govet:
    enable:
      - shadow

issues:
  exclude-use-default: false

빠른 참조: Go 관용구

관용구설명
인터페이스를 받고, 구조체를 반환하라함수는 인터페이스 매개변수를 받고 구체적인 타입을 반환함
에러는 값이다에러를 예외가 아닌 일급 값으로 취급하라
메모리 공유로 소통하지 마라고루틴 간 조정을 위해 채널을 사용하라
제로 값을 유용하게 만들어라타입은 명시적 초기화 없이도 동작해야 함
약간의 복사가 약간의 의존성보다 낫다불필요한 외부 의존성을 피하라
명확함이 영리함보다 낫다가독성을 영리함보다 우선시하라
gofmt는 누구도 좋아하지 않지만 모두의 친구다항상 gofmt/goimports로 포매팅하라
일찍 반환하라에러를 먼저 처리하고 행복한 경로를 들여쓰기 없이 유지하라

피해야 할 안티 패턴

// 나쁨: 긴 함수에서 네이키드 리턴
func process() (result int, err error) {
    // ... 50줄 ...
    return // 무엇이 반환되는가?
}

// 나쁨: 제어 흐름에 panic 사용
func GetUser(id string) *User {
    user, err := db.Find(id)
    if err != nil {
        panic(err) // 이렇게 하지 마세요
    }
    return user
}

// 나쁨: 구조체에 컨텍스트 전달
type Request struct {
    ctx context.Context // 컨텍스트는 첫 번째 매개변수여야 함
    ID  string
}

// 좋음: 컨텍스트를 첫 번째 매개변수로
func ProcessRequest(ctx context.Context, id string) error {
    // ...
}

// 나쁨: 값과 포인터 리시버 혼용
type Counter struct{ n int }
func (c Counter) Value() int { return c.n }    // 값 리시버
func (c *Counter) Increment() { c.n++ }        // 포인터 리시버
// 한 가지 스타일을 선택하고 일관성을 유지하라

기억하세요: Go 코드는 가장 좋은 방식으로 지루해야 합니다 - 예측 가능하고, 일관되며, 이해하기 쉬워야 합니다. 의심스러우면 단순하게 유지하세요.

Individual skills in this repo

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

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

관련 스킬