Claude
Skills
Sign in
Back

golang

Included with Lifetime
$97 forever

Use when building Go backend services, implementing goroutines/channels, handling errors idiomatically, writing tests with testify, or following Go best practices for APIs/CLI tools.

Backend & APIs

What this skill does


# Go Development Best Practices

**Version**: 2.0.0
**Purpose**: Comprehensive Go development patterns covering idioms, error handling, concurrency, testing, and quality
**Scope**: Backend development with Go - API services, CLI tools, system software
**Prerequisites**: Basic Go syntax knowledge

## Overview

Go (Golang) is designed for simplicity, explicit error handling, and safe concurrent programming. This skill covers production-ready patterns validated by the Go community, official documentation, and industry standards (Uber Engineering, Google).

**Core Philosophy**:
- **Simplicity**: "Clear is better than clever" - favor readable code over abstractions
- **Explicit over implicit**: No exceptions, no hidden control flow, visible errors
- **Composition over inheritance**: Interfaces and embedding, not class hierarchies
- **Built-in concurrency**: Goroutines and channels as first-class primitives
- **Tooling-first**: Format, vet, test, and benchmark built into the language

**Key Design Principles**:
1. Small interfaces (1-3 methods ideal)
2. Consumer-side interface placement
3. Error values, not exceptions
4. Happy path at left margin
5. Goroutines must have explicit termination

---

## 1. Idiomatic Go Patterns

### 1.1 Naming Conventions

**Package Names**:
```go
// ✅ GOOD: Package names are single lowercase identifiers
// Import path: "net/url" → package name: url
// Import path: "encoding/json" → package name: json
package url      // from "net/url"
package json     // from "encoding/json"
package strings

// ❌ BAD
package urls            // No plural
package encodingjson    // Don't smash words together
package stringutils     // Too verbose
```

**Getters and Setters**:
```go
type Account struct {
    balance int
}

// ✅ GOOD: No "Get" prefix
func (a *Account) Balance() int {
    return a.balance
}

func (a *Account) SetBalance(amount int) {
    a.balance = amount
}

// ❌ BAD: Java-style getters
func (a *Account) GetBalance() int {
    return a.balance
}
```

**Error Variables**:
```go
// Exported sentinel errors (capitalized)
var ErrNotFound = errors.New("not found")
var ErrTimeout = errors.New("timeout")

// Unexported internal errors (lowercase)
var errInternal = errors.New("internal error")
```

**Interface Naming**:
```go
// ✅ GOOD: Short, descriptive
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ❌ BAD: Verbose or unclear
type DataReader interface { ... }
type IReader interface { ... }  // No "I" prefix
```

---

### 1.2 Interface Design - "The Bigger the Interface, the Weaker the Abstraction"

**Core Principle**: Small, consumer-side interfaces provide maximum flexibility.

**Single-Method Interfaces** (Ideal):
```go
// Standard library examples
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
type ReadCloser interface {
    Reader
    Closer
}
```

**Consumer-Side Interface Placement**:
```go
// ❌ WRONG: Producer defines interface
package store

type CustomerStorage interface {
    StoreCustomer(Customer) error
    GetCustomer(string) (Customer, error)
    UpdateCustomer(Customer) error
    // 10+ methods...
}

type PostgresStore struct {}
func (s *PostgresStore) StoreCustomer(...) { ... }

// ✅ CORRECT: Consumer defines what it needs
package client

type customerGetter interface {
    GetCustomer(string) (store.Customer, error)
}

func ProcessCustomer(cg customerGetter) {
    customer, _ := cg.GetCustomer("123")
    // Only depends on GetCustomer method
}
```

**Return Concrete Types, Accept Interfaces** (Postel's Law):
```go
// ✅ GOOD
func NewStore() *PostgresStore {
    return &PostgresStore{}
}

func Process(storage CustomerStorage) error {
    // Accepts interface
}

// ❌ BAD: Returning interface
func NewStore() CustomerStorage {
    return &PostgresStore{}
}
```

**When to Create Interfaces**:
- Multiple implementations exist or are planned
- Need for testing (mocking dependencies)
- Decoupling packages
- **NOT for**: Single implementation with no testing need

---

### 1.3 Happy Path Left, Early Returns

**Core Principle**: Align success path to left margin, handle errors first.

```go
// ❌ BAD: Deep nesting
func join(s1, s2 string, max int) (string, error) {
    if s1 == "" {
        return "", errors.New("s1 is empty")
    } else {
        if s2 == "" {
            return "", errors.New("s2 is empty")
        } else {
            concat, err := concatenate(s1, s2)
            if err != nil {
                return "", err
            } else {
                if len(concat) > max {
                    return concat[:max], nil
                } else {
                    return concat, nil
                }
            }
        }
    }
}

// ✅ GOOD: Happy path aligned left
func join(s1, s2 string, max int) (string, error) {
    if s1 == "" {
        return "", errors.New("s1 is empty")
    }
    if s2 == "" {
        return "", errors.New("s2 is empty")
    }

    concat, err := concatenate(s1, s2)
    if err != nil {
        return "", err
    }

    if len(concat) > max {
        return concat[:max], nil
    }
    return concat, nil
}
```

**Guidelines**:
- Maximum 3-4 levels of nesting
- Omit `else` blocks when `if` returns
- Handle errors immediately
- Keep normal flow at lowest indentation

---

### 1.4 Composition Over Inheritance

**Type Embedding** (Struct Composition):
```go
// Embedding for method promotion
type Logger struct {
    *log.Logger
    prefix string
}

func NewLogger(prefix string) *Logger {
    return &Logger{
        Logger: log.New(os.Stdout, "", 0),
        prefix: prefix,
    }
}

// Logger methods automatically available
logger := NewLogger("APP")
logger.Println("message") // Calls embedded log.Logger.Println
```

**Interface Composition**:
```go
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// Compose interfaces
type ReadCloser interface {
    Reader
    Closer
}
```

**Warning**: Avoid embedding in public APIs:
```go
// ❌ BAD: Exposes implementation details
type MyHandler struct {
    http.Handler // Leaks all Handler methods
}

// ✅ GOOD: Explicit delegation
type MyHandler struct {
    handler http.Handler
}

func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Custom logic
    h.handler.ServeHTTP(w, r)
}
```

---

### 1.5 Key Go Idioms

**Defer for Cleanup**:
```go
func processFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // Guaranteed cleanup

    // Multiple returns, all close file
    if condition {
        return nil // File closed
    }

    return process(f) // File closed
}

// Mutex pattern
func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()

    c.value++ // All paths unlock
}
```

**Critical Rule**: Call `defer` AFTER checking error:
```go
// ❌ WRONG
defer f.Close() // f is nil if Open failed
f, err := os.Open(path)

// ✅ CORRECT
f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close()
```

**Multiple Return Values**:
```go
// (value, error) - Standard error handling
func GetUser(id string) (*User, error) {
    // ...
}

// (value, bool) - "comma ok" idiom
value, ok := myMap[key]
if !ok {
    // key not found
}

result, ok := someValue.(TargetType)
if !ok {
    // type assertion failed
}

data, ok := <-channel
if !ok {
    // channel closed
}
```

**Blank Identifier `_`**:
```go
// Ignore unwanted values
_, err := os.Open(filename)

// Compile-time interface check
var _ http.Handler = (*MyHandler)(nil)

// Import for side effects
import _ "net/http/pprof"
```

**Useful Zero Values**:
```go
// sync.Mutex - ready to use
var mu sync.Mutex
mu.Lock() // Works immediately

// bytes.Buffer - valid empty buffer
var buf bytes.Buffer
buf.WriteString("hello") // 

Related in Backend & APIs