golang-agent-skill
💡 Summary
This repository provides best practices for writing production-ready Go code, focusing on clarity, simplicity, and maintainability.
🎯 Target Audience
🤖 AI Roast: “Powerful, but the setup might scare off the impatient.”
Risk: Critical. Review: shell/CLI command execution; outbound network access (SSRF, data egress); filesystem read/write scope and path traversal. Run with least privilege and audit before enabling in production.
name: golang description: Best practices for writing production Go code. Use when writing, reviewing, or refactoring Go code. Covers error handling, concurrency, naming conventions, testing patterns, performance optimization, generics, and common pitfalls. Based on Google Go Style Guide, Uber Go Style Guide, Effective Go, and Go Code Review Comments. Updated for Go 1.25.
Go Best Practices
Battle-tested patterns from Google, Uber, and the Go team. These are practices proven in large-scale production systems, updated for modern Go (1.25).
Core Principles
Readable code prioritizes these attributes in order:
- Clarity: purpose and rationale are obvious to the reader
- Simplicity: accomplishes the goal in the simplest way
- Concision: high signal to noise ratio
- Maintainability: easy to modify correctly
- Consistency: matches surrounding codebase
Error Handling
Return Errors, Do Not Panic
Production code must avoid panics. Return errors and let callers decide how to handle them.
// Wrong func run(args []string) { if len(args) == 0 { panic("an argument is required") } } // Correct func run(args []string) error { if len(args) == 0 { return errors.New("an argument is required") } return nil } func main() { if err := run(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
Error Wrapping
Use %w when callers need to inspect the underlying error with errors.Is or errors.As. Use %v when you want to hide implementation details or at system boundaries.
// Preserve error chain for programmatic inspection if err != nil { return fmt.Errorf("load config: %w", err) } // Hide internal details at API boundaries if err != nil { return fmt.Errorf("database unavailable: %v", err) }
Keep context succinct. Avoid phrases like "failed to" that pile up as errors propagate.
// Wrong: produces "failed to x: failed to y: failed to create store: the error" return fmt.Errorf("failed to create new store: %w", err) // Correct: produces "x: y: new store: the error" return fmt.Errorf("new store: %w", err)
Joining Multiple Errors (Go 1.20+)
Use errors.Join when multiple operations can fail independently.
func validateUser(u User) error { var errs []error if u.Name == "" { errs = append(errs, errors.New("name required")) } if u.Email == "" { errs = append(errs, errors.New("email required")) } return errors.Join(errs...) } // Checking joined errors if err := validateUser(u); err != nil { if errors.Is(err, ErrNameRequired) { // handles even when joined with other errors } }
Error Types
Choose based on caller needs:
| Caller needs to match? | Message type | Approach |
|------------------------|--------------|----------|
| No | Static | errors.New("something bad") |
| No | Dynamic | fmt.Errorf("file %q not found", file) |
| Yes | Static | Exported var ErrNotFound = errors.New("not found") |
| Yes | Dynamic | Custom error type with Error() method |
Sentinel Errors and errors.Is
Define sentinel errors for conditions callers need to check.
var ( ErrNotFound = errors.New("not found") ErrInvalidUser = errors.New("invalid user") ) // Checking wrapped errors if errors.Is(err, ErrNotFound) { // handles ErrNotFound even when wrapped } // Custom error types use errors.As var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Println("failed path:", pathErr.Path) }
Error Naming
Exported error variables use Err prefix. Custom error types use Error suffix.
var ( ErrNotFound = errors.New("not found") ErrInvalidUser = errors.New("invalid user") ) type NotFoundError struct { Resource string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) }
Handle Errors Once
Do not log an error and also return it. The caller will likely log it again.
// Wrong: logs and returns, causing duplicate logs if err != nil { log.Printf("could not get user %q: %v", id, err) return err } // Correct: wrap and return, let caller decide if err != nil { return fmt.Errorf("get user %q: %w", id, err) } // Also correct: log and degrade gracefully without returning error if err := emitMetrics(); err != nil { log.Printf("could not emit metrics: %v", err) }
Error Strings
Do not capitalize error strings or end with punctuation. They often appear mid-sentence in logs.
// Wrong fmt.Errorf("Something bad happened.") // Correct fmt.Errorf("something bad happened")
Indent Error Flow
Keep the happy path at minimal indentation. Handle errors first.
// Wrong if err != nil { // error handling } else { // normal code } // Correct if err != nil { return err } // normal code continues
Concurrency
Channel Size
Channels should have size zero (unbuffered) or one. Any other size requires justification about what prevents filling under load.
// Wrong: arbitrary buffer c := make(chan int, 64) // Correct c := make(chan int) // unbuffered: synchronous handoff c := make(chan int, 1) // buffered: allows one pending send
Goroutine Lifetimes
Document when and how goroutines exit. Goroutines blocked on channels will not be garbage collected even if the channel is unreachable.
// Document exit conditions func (w *Worker) Run(ctx context.Context) error { for { select { case <-ctx.Done(): return ctx.Err() case job := <-w.jobs: w.process(job) } } }
Use errgroup for Concurrent Operations
Prefer errgroup.Group over manual sync.WaitGroup for error-returning goroutines.
import "golang.org/x/sync/errgroup" func processItems(ctx context.Context, items []Item) error { g, ctx := errgroup.WithContext(ctx) for _, item := range items { g.Go(func() error { return process(ctx, item) }) } return g.Wait() // returns first error, cancels others via ctx } // With concurrency limit func processItemsLimited(ctx context.Context, items []Item) error { g, ctx := errgroup.WithContext(ctx) g.SetLimit(10) // max 10 concurrent goroutines for _, item := range items { g.Go(func() error { return process(ctx, item) }) } return g.Wait() }
Prefer Synchronous Functions
Synchronous functions are easier to reason about and test. Let callers add concurrency when needed.
// Wrong: forces concurrency on caller func Fetch(url string) <-chan Result // Correct: caller can wrap in goroutine if needed func Fetch(url string) (Result, error)
Zero Value Mutexes
The zero value of sync.Mutex is valid. Do not use pointers to mutexes or embed them in exported structs.
// Wrong mu := new(sync.Mutex) // Wrong: exposes Lock/Unlock in API type SMap struct { sync.Mutex data map[string]string } // Correct type SMap struct { mu sync.Mutex data map[string]string }
Atomic Operations (Go 1.19+)
Use the standard library's typed atomics. External packages are no longer necessary.
import "sync/atomic" type Counter struct { value atomic.Int64 } func (c *Counter) Inc() { c.value.Add(1) } func (c *Counter) Value() int64 { return c.value.Load() } // Also available: atomic.Bool, atomic.Pointer[T], atomic.Uint32, etc.
sync.Map Performance (Go 1.24+)
The sync.Map implementation was significantly improved in Go 1.24. Modifications of disjoint sets of keys are much less likely to contend on larger maps, and there is no longer any ramp-up time required to achieve low-contention loads.
Naming
MixedCaps Always
Go uses MixedCaps, never underscores. This applies even when it breaks other language conventions.
// Wrong MAX_LENGTH, max_length, HTTP_Server // Correct MaxLength, maxLength, HTTPServer
Initialisms
Initialisms maintain consistent case: URL not Url, ID not Id, HTTP not Http.
// Wrong xmlHttpRequest, serverId, apiUrl // Correct xmlHTTPRequest, serverID, apiURL
Short Variable Names
Variables should be short, especially with limited scope. The further from declaration a name is used, the more descriptive it needs to be.
// Good for local scope for i, v := range items { } r := bufio.NewReader(f) // Global or struct fields need more context var DefaultTimeout = 30 * time.Second
Receiver Names
Use one or two letter abbreviations of the type. Be consistent across methods. Do not use generic names like this, self, or me.
// Wrong func (this *Client) Get() {} func (c *Client) Get() {} func (cl *Client) Post() {} // inconsistent // Correct func (c *Client) Get() {} func (c *Client) Post() {}
Pointer vs Value Receivers
| Use pointer receiver when | Use value receiver when | |---------------------------|-------------------------| | Method modifies the receiver | Struct is small and immutable | | Struct is large (avoid copying) | Method doesn't modify state | | Consistency with other methods | Receiver is a map, func, or chan | | Struct contains sync.Mutex | Basic types (int, string, etc.) |
// Pointer: modifies state func (s *Server) Shutdown() error { s.running = false return s.listener.Close() } // Value: small, read-only func (p Point) Distance(q Point) float64 { return math.Hypot(p.X-q.X, p.Y-q.Y) }
Package Names
Package names are lowercase, single words. Avoid util, common, misc, api, types. The package name becomes part of the identifier at call sites.
// Wrong package chubby type ChubbyFile struct{} // chubby.ChubbyFile is redundant // Correct package chubby type File struct{} // chubby.File reads well
Avoid Repetition in Names
Do not repea
Pros
- Comprehensive coverage of Go best practices
- Focus on error handling and concurrency
- Guidelines based on industry standards
Cons
- May be overwhelming for beginners
- Lacks examples for every guideline
- Not all practices may fit every project
Related Skills
pytorch
S“It's the Swiss Army knife of deep learning, but good luck figuring out which of the 47 installation methods is the one that won't break your system.”
agno
S“It promises to be the Kubernetes for agents, but let's see if developers have the patience to learn yet another orchestration layer.”
nuxt-skills
S“It's essentially a well-organized cheat sheet that turns your AI assistant into a Nuxt framework parrot.”
Disclaimer: This content is sourced from GitHub open source projects for display and rating purposes only.
Copyright belongs to the original author saisudhir14.
