go

Idiomatic programming style, patterns, and conventions for Go (Golang) development. Trigger when: - Writing, refactoring, reviewing, or debugging Go code. - Files matching the pattern **/*.go (includi

SKILL.md

GitHub

Go Language Idioms

Go favors simplicity, readability, and explicit behavior. Work with the language's conventions, not against them.


Core Philosophy

  • Simplicity over cleverness — clear, boring code is better than clever, obscure code
  • Explicit over implicit — errors are values, not exceptions
  • Composition over inheritance — embed types, implement interfaces
  • Share by communicating — don't communicate by sharing memory

Formatting

Let gofmt handle it. Don't fight the formatter.

gofmt -w .        # Format all files
go fmt ./...      # Format package

All Go code in the ecosystem follows gofmt. There is no debate.


Naming

General Rules

Scope Style Example
Exported PascalCase ReadFile, HTTPClient
Unexported camelCase readFile, httpClient
Packages lowercase, single word bytes, http, bufio
Acronyms All caps HTTP, URL, ID (not Http, Url, Id)

Package Names

  • Short, concise, evocative
  • No underscores or mixedCaps
  • The package name is part of the type name: bufio.Reader, not bufio.BufReader

Getters and Setters

// ❌ Don't prefix getters with "Get"
func (u *User) GetName() string

// ✅ Just use the field name
func (u *User) Name() string

// ✅ Setters use "Set" prefix
func (u *User) SetName(name string)

Interface Names

One-method interfaces use method name + -er suffix:

Method Interface
Read Reader
Write Writer
Close Closer
String Stringer

Functions

Multiple Return Values

Go functions return (result, error) instead of throwing:

func ReadFile(path string) ([]byte, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    return io.ReadAll(f)
}

Named Return Values

Use for documentation, but avoid naked returns in non-trivial functions:

// Named returns document what's being returned
func ParseDuration(s string) (duration time.Duration, err error) {
    // ...
    return duration, nil  // Explicit, not naked
}

Defer

Use defer for cleanup. Defers execute LIFO at function exit:

func CopyFile(dst, src string) error {
    r, err := os.Open(src)
    if err != nil {
        return err
    }
    defer r.Close()

    w, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer w.Close()

    _, err = io.Copy(w, r)
    return err
}

Interfaces

Keep Interfaces Small

Accept interfaces, return concrete types:

// ✅ Function accepts interface (flexible)
func Process(r io.Reader) error

// ✅ Function returns concrete type (useful)
func NewClient() *Client

Interface Satisfaction

Interfaces are satisfied implicitly—no implements keyword:

type Stringer interface {
    String() string
}

type User struct{ Name string }

// User satisfies Stringer automatically
func (u User) String() string {
    return u.Name
}

Compile-Time Check

Verify interface satisfaction at compile time:

var _ io.Reader = (*MyReader)(nil)

Pointers vs. Values

When to Use Pointer Receivers

Use Pointer When
*T Method modifies the receiver
*T Struct is large (avoid copying)
*T Consistency (if any method uses pointer, all should)

When to Use Value Receivers

Use Value When
T Method doesn't modify receiver
T Type is small (int, small struct)
T Type is immutable by design

Concurrency

Goroutines

Lightweight, start with go:

go func() {
    // runs concurrently
}()

Channels

Use channels for communication between goroutines:

ch := make(chan int)       // Unbuffered
ch := make(chan int, 100)  // Buffered

ch <- value   // Send
v := <-ch     // Receive

Share by Communicating

"Don't communicate by sharing memory; share memory by communicating."

// ❌ Shared state with mutex
var count int
var mu sync.Mutex

// ✅ Channel-based coordination
type result struct{ value int }
results := make(chan result)

Common Patterns

Pattern Use Case
sync.WaitGroup Wait for N goroutines to complete
context.Context Cancellation and timeouts
select Multiplex channel operations
sync.Once One-time initialization

Error Handling

Errors Are Values

Check explicitly. Never discard with _:

// ❌ Never ignore errors
result, _ := doSomething()

// ✅ Always check
result, err := doSomething()
if err != nil {
    return fmt.Errorf("doing something: %w", err)
}

Error Wrapping

Use %w to wrap errors with context:

if err != nil {
    return fmt.Errorf("failed to parse config %s: %w", path, err)
}

Custom Errors

For libraries, define sentinel errors or custom types:

// Sentinel errors
var ErrNotFound = errors.New("not found")

// Custom error types
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

When to Panic

Context Panic? Alternative
Library code ❌ Never Return error
Impossible states ✅ Acceptable Assert invariants
Init failures ✅ Acceptable log.Fatal

Anti-Patterns

Anti-Pattern Description Remedy
Ignoring errors Using _ to discard errors Always check or explicitly document why ignored
Naked returns return without values in complex functions Use explicit return values
Interface pollution Defining interfaces before needed Define interfaces at point of use
Premature channels Using channels when mutex suffices Start simple; add concurrency primitives as needed
Giant interfaces Interfaces with many methods Keep interfaces small; compose when needed

Tooling

go fmt ./...           # Format code
go vet ./...           # Static analysis
golangci-lint run      # Comprehensive linting
go test ./...          # Run tests
go test -race ./...    # Detect race conditions

Quick Reference

  • Errors are values — check explicitly, wrap with context
  • Accept interfaces, return structs — flexible inputs, concrete outputs
  • Use gofmt — no formatting debates
  • Prefer composition — embed types, small interfaces
  • Channels for coordination — share by communicating
  • defer for cleanup — runs at function exit, LIFO
  • No naked returns — be explicit in non-trivial functions

These idioms refine but are subordinate to the Code-Edit Constraints.