golang-patterns
affaan-m/everything-claude-code
견고하고 효율적이며 유지보수가 용이한 Go 애플리케이션을 구축하기 위한 Go의 관용적 패턴, 모범 사례 및 관례.
...모든 것을 확장하십시오소개 golang-patterns
golang-patterns 는 특정 워크플로우에 초점을 맞춘 재사용 가능한 AI 스킬입니다. 이름: golang-patterns
이 스킬은 지침, 관례 및 작업별 안내를 포함하여 에이전트가 작업을 보다 일관성 있게 수행할 수 있도록 지원합니다. 설명: 견고하고 효율적이며 유지보수가 용이한 Go 애플리케이션을 구축하기 위한 관용적인 Go 패턴, 모범 사례 및 관례. 견고하고 효율적이며 유지보수가 용이한 애플리케이션을 구축하기 위한 관용적인 Go 패턴 및 모범 사례. - 새로운 Go 코드 작성
실무에서 이 스킬은 설정 단계가 적고 모호성이 적은 상태에서 반복적으로 작업을 수행해야 하는 사용자에게 가장 적합합니다. - 기존 Go 코드 리팩토링 - Go 패키지/모듈 설계 Go는 기발함보다 단순함을 중시합니다. 코드는 직관적이고 읽기 쉬워야 합니다. // 좋은 예: 명확하고 직관적
자주 묻는 질문
golang-patterns 는 어떤 도움을 주나요?
golang-patterns 에이전트가 소스 문서에 설명된 집중적인 워크플로를 따르도록 도와주어, 모호성을 줄이고 실행이 의도된 작업과 일치하도록 유지합니다.
이 스킬은 언제 사용해야 하나요?
작업이 스킬 문서에 설명된 워크플로, 도메인 또는 운영 규칙과 일치할 때, 특히 일관된 실행이 중요한 경우에 사용하십시오.
주요 제한 사항은 무엇인가요?
이 스킬은 원본 지침의 품질과 범위에 제약을 받습니다. 기본 문서가 불완전한 경우, 상담원은 여전히 추가적인 맥락 정보나 수동 검증이 필요할 수 있습니다.
Go Development Patterns
Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.
When to Activate
- Writing new Go code
- Reviewing Go code
- Refactoring existing Go code
- Designing Go packages/modules
Core Principles
1. Simplicity and Clarity
Go favors simplicity over cleverness. Code should be obvious and easy to read.
// Good: Clear and directfunc 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}// Bad: Overly cleverfunc 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. Make the Zero Value Useful
Design types so their zero value is immediately usable without initialization.
// Good: Zero value is usefultype Counter struct { mu sync.Mutex count int // zero value is 0, ready to use}func (c *Counter) Inc() { c.mu.Lock() c.count++ c.mu.Unlock()}// Good: bytes.Buffer works with zero valuevar buf bytes.Bufferbuf.WriteString("hello")// Bad: Requires initializationtype BadCounter struct { counts map[string]int // nil map will panic}
3. Accept Interfaces, Return Structs
Functions should accept interface parameters and return concrete types.
// Good: Accepts interface, returns concrete typefunc ProcessData(r io.Reader) (*Result, error) { data, err := io.ReadAll(r) if err != nil { return nil, err } return &Result{Data: data}, nil}// Bad: Returns interface (hides implementation details unnecessarily)func ProcessData(r io.Reader) (io.Reader, error) { // ...}
Error Handling Patterns
Error Wrapping with Context
// Good: Wrap errors with contextfunc 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}
Custom Error Types
// Define domain-specific errorstype ValidationError struct { Field string Message string}func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)}// Sentinel errors for common casesvar ( ErrNotFound = errors.New("resource not found") ErrUnauthorized = errors.New("unauthorized") ErrInvalidInput = errors.New("invalid input"))
Error Checking with errors.Is and errors.As
func HandleError(err error) { // Check for specific error if errors.Is(err, sql.ErrNoRows) { log.Println("No records found") return } // Check for error type var validationErr *ValidationError if errors.As(err, &validationErr) { log.Printf("Validation error on field %s: %s", validationErr.Field, validationErr.Message) return } // Unknown error log.Printf("Unexpected error: %v", err)}
Never Ignore Errors
// Bad: Ignoring error with blank identifierresult, _ := doSomething()// Good: Handle or explicitly document why it's safe to ignoreresult, err := doSomething()if err != nil { return err}// Acceptable: When error truly doesn't matter (rare)_ = writer.Close() // Best-effort cleanup, error logged elsewhere
Concurrency Patterns
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 for Cancellation and Timeouts
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 for Coordinated Goroutines
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 // Capture loop variables 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}
Avoiding Goroutine Leaks
// Bad: Goroutine leak if context is cancelledfunc leakyFetch(ctx context.Context, url string) <-chan []byte { ch := make(chan []byte) go func() { data, _ := fetch(url) ch <- data // Blocks forever if no receiver }() return ch}// Good: Properly handles cancellationfunc safeFetch(ctx context.Context, url string) <-chan []byte { ch := make(chan []byte, 1) // Buffered channel go func() { data, err := fetch(url) if err != nil { return } select { case ch <- data: case <-ctx.Done(): } }() return ch}
Interface Design
Small, Focused Interfaces
// Good: Single-method interfacestype Reader interface { Read(p []byte) (n int, err error)}type Writer interface { Write(p []byte) (n int, err error)}type Closer interface { Close() error}// Compose interfaces as neededtype ReadWriteCloser interface { Reader Writer Closer}
Define Interfaces Where They're Used
// In the consumer package, not the providerpackage service// UserStore defines what this service needstype UserStore interface { GetUser(id string) (*User, error) SaveUser(user *User) error}type Service struct { store UserStore}// Concrete implementation can be in another package// It doesn't need to know about this interface
Optional Behavior with Type Assertions
type Flusher interface { Flush() error}func WriteAndFlush(w io.Writer, data []byte) error { if _, err := w.Write(data); err != nil { return err } // Flush if supported if f, ok := w.(Flusher); ok { return f.Flush() } return nil}
Package Organization
Standard Project Layout
myproject/├── cmd/│ └── myapp/│ └── main.go # Entry point├── internal/│ ├── handler/ # HTTP handlers│ ├── service/ # Business logic│ ├── repository/ # Data access│ └── config/ # Configuration├── pkg/│ └── client/ # Public API client├── api/│ └── v1/ # API definitions (proto, OpenAPI)├── testdata/ # Test fixtures├── go.mod├── go.sum└── MakefilePackage Naming
// Good: Short, lowercase, no underscorespackage httppackage jsonpackage user// Bad: Verbose, mixed case, or redundantpackage httpHandlerpackage json_parserpackage userService // Redundant 'Service' suffix
Avoid Package-Level State
// Bad: Global mutable statevar db *sql.DBfunc init() { db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))}// Good: Dependency injectiontype Server struct { db *sql.DB}func NewServer(db *sql.DB) *Server { return &Server{db: db}}
Struct Design
Functional Options Pattern
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, // default logger: log.Default(), // default } for _, opt := range opts { opt(s) } return s}// Usageserver := NewServer(":8080", WithTimeout(60*time.Second), WithLogger(customLogger),)
Embedding for Composition
type Logger struct { prefix string}func (l *Logger) Log(msg string) { fmt.Printf("[%s] %s", l.prefix, msg)}type Server struct { *Logger // Embedding - Server gets Log method addr string}func NewServer(addr string) *Server { return &Server{ Logger: &Logger{prefix: "SERVER"}, addr: addr, }}// Usages := NewServer(":8080")s.Log("Starting...") // Calls embedded Logger.Log
Memory and Performance
Preallocate Slices When Size is Known
// Bad: Grows slice multiple timesfunc processItems(items []Item) []Result { var results []Result for _, item := range items { results = append(results, process(item)) } return results}// Good: Single allocationfunc processItems(items []Item) []Result { results := make([]Result, 0, len(items)) for _, item := range items { results = append(results, process(item)) } return results}
Use sync.Pool for Frequent Allocations
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) // Process... return buf.Bytes()}
Avoid String Concatenation in Loops
// Bad: Creates many string allocationsfunc join(parts []string) string { var result string for _, p := range parts { result += p + "," } return result}// Good: Single allocation with strings.Builderfunc join(parts []string) string { var sb strings.Builder for i, p := range parts { if i > 0 { sb.WriteString(",") } sb.WriteString(p) } return sb.String()}// Best: Use standard libraryfunc join(parts []string) string { return strings.Join(parts, ",")}
Go Tooling Integration
Essential Commands
# Build and rungo build ./...go run ./cmd/myapp# Testinggo test ./...go test -race ./...go test -cover ./...# Static analysisgo vet ./...staticcheck ./...golangci-lint run# Module managementgo mod tidygo mod verify# Formattinggofmt -w .goimports -w .
Recommended Linter Configuration (.golangci.yml)
linters: enable: - errcheck - gosimple - govet - ineffassign - staticcheck - unused - gofmt - goimports - misspell - unconvert - unparamlinters-settings: errcheck: check-type-assertions: true govet: check-shadowing: trueissues: exclude-use-default: false
Quick Reference: Go Idioms
| Idiom | Description |
|---|---|
| Accept interfaces, return structs | Functions accept interface params, return concrete types |
| Errors are values | Treat errors as first-class values, not exceptions |
| Don't communicate by sharing memory | Use channels for coordination between goroutines |
| Make the zero value useful | Types should work without explicit initialization |
| A little copying is better than a little dependency | Avoid unnecessary external dependencies |
| Clear is better than clever | Prioritize readability over cleverness |
| gofmt is no one's favorite but everyone's friend | Always format with gofmt/goimports |
| Return early | Handle errors first, keep happy path unindented |
Anti-Patterns to Avoid
// Bad: Naked returns in long functionsfunc process() (result int, err error) { // ... 50 lines ... return // What is being returned?}// Bad: Using panic for control flowfunc GetUser(id string) *User { user, err := db.Find(id) if err != nil { panic(err) // Don't do this } return user}// Bad: Passing context in structtype Request struct { ctx context.Context // Context should be first param ID string}// Good: Context as first parameterfunc ProcessRequest(ctx context.Context, id string) error { // ...}// Bad: Mixing value and pointer receiverstype Counter struct{ n int }func (c Counter) Value() int { return c.n } // Value receiverfunc (c *Counter) Increment() { c.n++ } // Pointer receiver// Pick one style and be consistent
Remember: Go code should be boring in the best way - predictable, consistent, and easy to understand. When in doubt, keep it simple.
golang-patterns 설치
스킬 파일을 다운로드하여 .claude/skills/ 디렉터리에 압축을 풀어주세요.
ZIP 다운로드저장소를 클론하고 스킬 파일을 프로젝트에 복사하세요.
git clone https://github.com/affaan-m/ECC/blob/main/skills/golang-patterns/SKILL.md # Copy SKILL.md to your .claude/skills/ directory
복사





집
