Communitygithub.com

golang-patterns

관용적인 Go 패턴, 모범 사례, 규칙을 사용하여 견고하고 효율적이며 유지 관리가 쉬운 Go 애플리케이션을 구축합니다. Go 코드를 작성하거나 리뷰할 때 관용적인 구조나 규칙이 궁금할 때 사용하세요.

golang-patterns란 무엇인가요?

이 스킬은 Go 개발자에게 견고하고 효율적이며 유지 관리가 쉬운 애플리케이션을 구축하기 위한 관용적인 Go 패턴과 모범 사례에 대한 포괄적인 가이드를 제공합니다. Go 코드를 처음 작성하는 경우, 기존 코드를 리뷰하는 경우, 또는 리팩토링을 진행하는 경우 이 스킬을 활성화합니다. 핵심 원칙으로는 단순성과 명확성, 제로 값 활용, 인터페이스 수용과 구체 타입 반환 등을 강조합니다. 오류 처리, 동시성(워커 풀, 컨텍스트 취소, errgroup), 인터페이스 설계, 패키지 구성, 구조체 설계(함수형 옵션), 메모리/성능 최적화 등 다양한 주제를 다루며 실제 Go 코드 예제와 함께 설명합니다. 또한 `go vet`, `golangci-lint` 같은 Go 도구 사용법과 일반적인 안티 패턴을 피하는 방법도 제공합니다. 이 스킬은 Cursor, Claude Code, Codex 등 모든 AI 코딩 도우미와 호환되며 Go 코드 품질을 높이고 일관된 관용적 스타일을 유지하는 데 도움이 됩니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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. 제로 값 활용

초기화 없이도 즉시 사용할 수 있도록 타입을 설계하세요.

// 좋음: 제로 값이 유용함
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() // 최선의 정리, 오류는 다른 곳에서 로깅됨

동시성 패턴

워커 풀

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)
}

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

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)
}

정상 종료

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
}

// 필요에 따라 인터페이스 합성
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

인터페이스는 사용하는 곳에서 정의하세요

// 공급자가 아닌 소비자 패키지에서
type 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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬