golang-patterns
견고하고 효율적이며 유지보수 가능한 Go 애플리케이션 구축을 위한 관용적 Go 패턴, 모범 사례 및 규칙.
DeepseekModel
キュレーション済みスキル
品質 優秀 · 90
v1.0.0
取得
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-ko-kr-skills-golang-patterns-skill-md&format=skill
ダウンロード .skill
標準形式。system_prompt と model_config を収録し、任意の Agent で利用可能
.skill ファイルの system_prompt フィールドの実際の内容。
name golang-patterns description 견고하고 효율적이며 유지보수 가능한 Go 애플리케이션 구축을 위한 관용적 Go 패턴, 모범 사례 및 규칙. origin ECC Go 개발 패턴 견고하고 효율적이며 유지보수 가능한 애플리케이션 구축을 위한 관용적 Go 패턴과 모범 사례. 활성화 시점 새로운 Go 코드 작성 시 Go 코드 리뷰 시 기존 Go 코드 리팩토링 시 Go 패키지/모듈 설계 시 핵심 원칙 1. 단순성과 명확성 Go는 영리함보다 단순성을 선호합니다. 코드는 명확하고 읽기 쉬워야 합니다. // Good: Clear and direct 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 } // Bad: Overly clever 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. 제로 값을 유용하게 만들기 제로 값이 초기화 없이 즉시 사용 가능하도록 타입을 설계하세요. // Good: Zero value is useful type 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 value var buf bytes.Buffer buf.WriteString( "hello" ) // Bad: Requires initialization type BadCounter struct { counts map [ string ] int // nil map will panic } 3. 인터페이스를 받고 구조체를 반환하기 함수는 인터페이스 매개변수를 받고 구체적 타입을 반환해야 합니다. // Good: Accepts interface, returns concrete type func 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 ) { // ... } 에러 처리 패턴 컨텍스트가 있는 에러 래핑 // Good: Wrap errors with context 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 } 커스텀 에러 타입 // Define domain-specific errors type 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 cases var ( ErrNotFound = errors.New( "resource not found" ) ErrUnauthorized = errors.New( "unauthorized" ) ErrInvalidInput = errors.New( "invalid input" ) ) errors.Is와 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) } 에러를 절대 무시하지 말 것 // Bad: Ignoring error with blank identifier result, _ := doSomething() // Good: Handle or explicitly document why it's safe to ignore result, err := doSomething() if err != nil { return err } // Acceptable: When error truly doesn't matter (rare) _ = writer.Close() // Best-effort cleanup, error logged elsewhere 동시성 패턴 워커 풀 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) } 우아한 종료 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 // 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 } 고루틴 누수 방지 // Bad: Goroutine leak if context is cancelled func 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 cancellation func 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 } 인터페이스 설계 작고 집중된 인터페이스 // Good: Single-method interfaces 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 } // Compose interfaces as needed type ReadWriteCloser interface { Reader Writer Closer } 사용되는 곳에서 인터페이스 정의 // In the consumer package, not the provider package service // UserStore defines what this service needs type 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 타입 어서션을 통한 선택적 동작 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 } 패키지 구성 표준 프로젝트 레이아웃 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 └── Makefile 패키지 명명 // Good: Short, lowercase, no underscores package http package json package user // Bad: Verbose, mixed case, or redundant package httpHandler package json_parser package userService // Redundant 'Service' suffix 패키지 수준 상태 피하기 // Bad: Global mutable state var db *sql.DB func init () { db, _ = sql.Open( "postgres" , os.Getenv( "DATABASE_URL" )) } // Good: Dependency injection 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, // default logger: log.Default(), // default } for _, opt := range opts { opt(s) } return s } // Usage 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 // Embedding - Server gets Log method addr string } func NewServer (addr string ) *Server { return &Server{ Logger: &Logger{prefix: "SERVER" }, addr: addr, } } // Usage s := NewServer( ":8080" ) s.Log( "Starting..." ) // Calls embedded Logger.Log 메모리 및 성능 크기를 알 때 슬라이스 미리 할당 // Bad: Grows slice multiple times func processItems (items []Item) []Result { var results []Result for _, item := range items { results = append (results, process(item)) } return results } // Good: Single allocation 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) // Process... out := append ([] byte ( nil ), buf.Bytes()...) return out } 루프에서 문자열 연결 피하기 // Bad: Creates many string allocations func join (parts [] string ) string { var result string for _, p := range parts { result += p + "," } return result } // Good: Single allocation with strings.Builder func join (parts [] string ) string { var sb strings.Builder for i, p := range parts { if i > 0 { sb.WriteString( "," ) } sb.WriteString(p) }
このスキルを起動するキーワード。クリックでコピーできます。
このスキルにはトリガーワードがありません。
ダウンロードした .skill に含まれるフィールド。
| フィールド | 説明 |
|---|---|
| format | フォーマット識別子(skill/v1) |
| skill_id | スキル固有 ID |
| name | スキル名 |
| version | バージョン |
| description | 説明 |
| category | カテゴリ(配列) |
| trigger_words | トリガーワード |
| tags | タグ |
| source | ソース |
| source_url | ソース URL(本ページ) |
| exported_at | エクスポート日時(ダウンロード毎) |
| system_prompt | システムプロンプト本文 |
| model_config | モデル設定:provider / model / temperature / max_tokens / top_p |
| examples | サンプル |
| install_guide | 各プラットフォームの導入説明(Coze / Dify / Claude / カスタム) |