Community코딩 & 개발github.com

viccom/golang-design-patterns

관용적인 Go 디자인 패턴 — 함수형 옵션, 생성자, 오류 흐름 및 캐스케이딩, 리소스 관리 및 수명 주기, 정상 종료, 탄력성, 아키텍처, 의존성 주입, 데이터 처리, 스트리밍 등. 아키텍처 패턴 중 명시적으로 선택해야 할 때, 함수형 옵션 구현, 생성자 API 설계, 정상 종료 설정, 탄력성 패턴 적용, 또는 특정 문제에 맞는 관용적인 Go 패턴을 물어볼 때 사용하세요.

golang-design-patterns란 무엇인가요?

이 스킬은 Go 언어로 프로덕션 수준의 애플리케이션을 개발할 때 유용한 관용적인 디자인 패턴과 모범 사례를 제공합니다. 함수형 옵션 패턴을 활용한 확장 가능한 생성자 설계, 명시적인 오류 처리 및 캐스케이딩, 안전한 리소스 관리와 정상 종료, 그리고 외부 호출에 대한 타임아웃 및 재시도와 같은 탄력성 패턴을 중점적으로 다룹니다. 또한, `init()` 함수의 사용 자제, 열거형에서 0값을 초기화되지 않은 상태로 사용하는 방법, 정규식의 패키지 수준 컴파일 등 Go 고유의 관용구를 강조합니다. 이 스킬은 Claude Code, Cursor, Codex 등 다양한 AI 에이전트 플랫폼에서 사용할 수 있으며, 개발자가 아키텍처 패턴, 생성자 설계, 데이터 처리 방식 등을 선택할 때 구체적인 지침과 코드 예제를 제공합니다. 특히, 무분별한 추상화를 경계하고 실제 문제를 해결하기 위해 패턴을 적용하며, 설계 모드와 리뷰 모드 두 가지 모드로 작동하여 개발자의 요구에 맞춰 최소한의 패턴을 제안합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/golang-design-patterns

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

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

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

문서

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 스킬을 참조하세요.

모범 사례 요약

  1. 생성자는 함수형 옵션을 사용해야 합니다. API가 발전함에 따라 더 잘 확장됩니다(옵션당 하나의 함수, 호환성을 깨지 않음).
  2. 함수형 옵션은 유효성 검사가 실패할 수 있다면 오류를 반환해야 합니다. 런타임이 아닌 구성 시점에 잘못된 설정을 잡아내십시오.
  3. init()를 피하십시오 — 암시적으로 실행되고, 오류를 반환할 수 없으며, 테스트를 예측 불가능하게 만듭니다. 명시적인 생성자를 사용하십시오.
  4. 열거형은 1부터 시작해야 합니다(또는 0에 Unknown 센티널 사용). Go의 제로 값은 첫 번째 열거형 멤버로 자동 전달됩니다.
  5. 오류 케이스는 먼저 처리하고 조기 반환해야 합니다. 행복 경로를 평평하게 유지하십시오.
  6. 패닉은 버그를 위한 것이지 예상된 오류를 위한 것이 아닙니다 — 호출자는 반환된 오류를 처리할 수 있습니다. 패닉은 프로세스를 중단시킵니다.
  7. 열자마자 defer Close()를 호출하십시오 — 나중에 코드 변경으로 인해 정리가 실수로 생략될 수 있습니다.
  8. runtime.SetFinalizer 대신 **runtime.AddCleanup**을 사용하십시오. 파이널라이저는 예측 불가능하고 객체를 부활시킬 수 있습니다.
  9. 모든 외부 호출에는 타임아웃이 있어야 합니다 — 느린 업스트림은 고루틴을 무기한 중단시킵니다.
  10. 모든 것을 제한하십시오(풀 크기, 큐 깊이, 버퍼). 무제한 리소스는 충돌할 때까지 커집니다.
  11. 재시도 로직은 시도 사이에 컨텍스트 취소를 확인해야 합니다.
  12. 루프에서 문자열 연결에 strings.Builder를 사용하십시오. → samber/cc-skills-golang@golang-code-style 참조
  13. string vs []byte: 변경 및 I/O에는 []byte를, 표시 및 키에는 string을 사용하십시오. 변환은 메모리를 할당합니다.
  14. 반복자(Go 1.23+): 지연 평가를 위해 사용하십시오. 모든 것을 메모리에 로드하지 마십시오.
  15. 대용량 전송을 스트리밍하십시오. 수백만 행을 로드하면 OOM이 발생합니다. 스트리밍은 메모리를 일정하게 유지합니다.
  16. 정적 자산에는 **//go:embed**를 사용하십시오. 컴파일 타임에 포함되며, 런타임 파일 I/O 오류를 제거합니다.
  17. 키/토큰에는 crypto/rand를 사용하십시오. math/rand는 예측 가능합니다. → samber/cc-skills-golang@golang-security 참조
  18. 정규식은 패키지 수준에서 한 번 컴파일되어야 합니다. 컴파일은 O(n)이며 메모리를 할당합니다.
  19. 컴파일 타임 인터페이스 검사: var _ Interface = (*Type)(nil)
  20. 약간의 재작성이 큰 의존성보다 낫습니다 — 각 의존성은 공격 표면과 유지 관리 부담을 추가합니다.
  21. 테스트 용이성을 위해 설계하십시오 — 인터페이스를 수용하고, 의존성을 주입하십시오.

생성자 패턴: 함수형 옵션 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
[]byteI/Oio.Writer에 쓰기, 문자열 빌드, 변경
[]rune유니코드 연산len()이 바이트가 아닌 문자를 의미해야 할 때

반복적인 변환을 피하십시오. 각 변환은 메모리를 할당합니다. 다른 유형이 필요할 때까지 한 유형으로 유지하십시오.

대용량 데이터를 위한 반복자 및 스트리밍

반복자(Go 1.23+)와 스트리밍 패턴을 사용하여 모든 것을 메모리에 로드하지 않고 대용량 데이터 세트를 처리하십시오. 서비스 간 대용량 전송(예: DB에서 HTTP로 1M 행)의 경우 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, 트랜잭션, nullable 열, 연결 풀, 리포지토리 인터페이스, 테스트에 대해서는 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 스킬을 참조하세요

Individual skills in this repo

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

관련 스킬