golang

Installation
SKILL.md

When to Use

  • Writing new Go code or packages
  • Refactoring existing Go code
  • Implementing TDD workflow in Go
  • Reviewing Go code for best practices
  • Designing package structure and APIs

Project Structure

project/
├── cmd/                    # Application entrypoints
│   └── myapp/
│       └── main.go
├── internal/               # Private application code
│   ├── domain/
│   ├── service/
│   └── repository/
├── pkg/                    # Public reusable packages
├── api/                    # API definitions (OpenAPI, proto)
├── configs/                # Configuration files
├── scripts/                # Build/deploy scripts
├── go.mod
├── go.sum
└── Makefile

Critical Patterns

1. Error Handling

// GOOD: Wrap errors with context
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    user, err := s.repo.FindByID(ctx, id)
    if err != nil {
        return nil, fmt.Errorf("get user %s: %w", id, err)
    }
    return user, nil
}

// GOOD: Define sentinel errors
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
)

// GOOD: Check for specific errors
if errors.Is(err, ErrNotFound) {
    // handle not found
}

2. Interface Design

// GOOD: Small, focused interfaces (accept interfaces, return structs)
type Reader interface {
    Read(ctx context.Context, id string) (*Entity, error)
}

type Writer interface {
    Write(ctx context.Context, entity *Entity) error
}

// Compose when needed
type ReadWriter interface {
    Reader
    Writer
}

// GOOD: Define interfaces where they're used, not where implemented
// In service package:
type userRepository interface {
    FindByID(ctx context.Context, id string) (*User, error)
}

type UserService struct {
    repo userRepository // lowercase = private, defined here
}

3. Constructor Pattern

// GOOD: Functional options for complex constructors
type Server struct {
    addr    string
    timeout time.Duration
    logger  Logger
}

type Option func(*Server)

func WithTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.timeout = d
    }
}

func WithLogger(l 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:  noopLogger{},     // default
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
server := NewServer(":8080", WithTimeout(time.Minute), WithLogger(myLogger))

4. Context Usage

// GOOD: Context as first parameter
func (s *Service) DoSomething(ctx context.Context, input Input) error {
    // Check for cancellation
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
    }
    
    // Pass context to downstream calls
    return s.repo.Save(ctx, input)
}

// BAD: Don't store context in structs
type Service struct {
    ctx context.Context // NEVER do this
}

TDD Workflow

Red-Green-Refactor Cycle

1. RED:    Write a failing test first
2. GREEN:  Write minimal code to pass
3. REFACTOR: Improve code, keep tests green

Test Structure (AAA Pattern)

func TestUserService_GetUser(t *testing.T) {
    // Arrange
    repo := &mockUserRepository{
        user: &User{ID: "123", Name: "John"},
    }
    svc := NewUserService(repo)
    
    // Act
    user, err := svc.GetUser(context.Background(), "123")
    
    // Assert
    require.NoError(t, err)
    assert.Equal(t, "John", user.Name)
}

Table-Driven Tests

func TestCalculate(t *testing.T) {
    tests := []struct {
        name     string
        input    int
        expected int
        wantErr  bool
    }{
        {
            name:     "positive number",
            input:    5,
            expected: 25,
            wantErr:  false,
        },
        {
            name:     "zero",
            input:    0,
            expected: 0,
            wantErr:  false,
        },
        {
            name:    "negative number",
            input:   -1,
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result, err := Calculate(tt.input)
            
            if tt.wantErr {
                require.Error(t, err)
                return
            }
            
            require.NoError(t, err)
            assert.Equal(t, tt.expected, result)
        })
    }
}

Test Doubles

// Mock for testing
type mockRepository struct {
    findFn func(ctx context.Context, id string) (*Entity, error)
    saveFn func(ctx context.Context, e *Entity) error
}

func (m *mockRepository) Find(ctx context.Context, id string) (*Entity, error) {
    if m.findFn != nil {
        return m.findFn(ctx, id)
    }
    return nil, nil
}

// Usage in test
repo := &mockRepository{
    findFn: func(ctx context.Context, id string) (*Entity, error) {
        return &Entity{ID: id}, nil
    },
}

Testing HTTP Handlers

func TestHandler_GetUser(t *testing.T) {
    // Arrange
    svc := &mockUserService{
        user: &User{ID: "123", Name: "John"},
    }
    handler := NewHandler(svc)
    
    req := httptest.NewRequest(http.MethodGet, "/users/123", nil)
    rec := httptest.NewRecorder()
    
    // Act
    handler.GetUser(rec, req)
    
    // Assert
    assert.Equal(t, http.StatusOK, rec.Code)
    
    var response User
    err := json.NewDecoder(rec.Body).Decode(&response)
    require.NoError(t, err)
    assert.Equal(t, "John", response.Name)
}

Code Style

Naming Conventions

Type Convention Example
Package lowercase, single word user, order
Interface -er suffix for single method Reader, Writer
Exported PascalCase GetUser, UserService
Unexported camelCase getUserByID, userRepo
Acronyms Consistent case HTTPServer, userID
Test files _test.go suffix user_test.go

Documentation

// Package user provides user management functionality.
package user

// User represents a system user with authentication credentials.
type User struct {
    ID    string
    Email string
}

// GetByID retrieves a user by their unique identifier.
// It returns ErrNotFound if no user exists with the given ID.
func (s *Service) GetByID(ctx context.Context, id string) (*User, error) {
    // ...
}

Anti-Patterns to Avoid

Anti-Pattern Problem Solution
interface{} / any overuse Loses type safety Use generics or specific types
Naked returns Hard to read Use explicit returns
init() functions Hidden side effects Use explicit initialization
Global state Hard to test Use dependency injection
Panic for errors Crashes program Return errors instead
Ignoring errors Silent failures Always handle or wrap errors

Commands

# Run tests
go test ./...

# Run tests with coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# Run tests verbose
go test -v ./...

# Run specific test
go test -run TestUserService_GetUser ./...

# Run benchmarks
go test -bench=. ./...

# Lint code
golangci-lint run

# Format code
gofmt -w .
goimports -w .

# Vet code
go vet ./...

# Generate mocks (with mockgen)
mockgen -source=repository.go -destination=mock_repository.go -package=user

# Build
go build -o bin/myapp ./cmd/myapp

# Tidy dependencies
go mod tidy

Useful Test Assertions (testify)

import (
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

// assert - continues on failure
assert.Equal(t, expected, actual)
assert.NotNil(t, value)
assert.True(t, condition)
assert.Contains(t, slice, element)
assert.Empty(t, collection)

// require - stops on failure
require.NoError(t, err)
require.NotNil(t, value)

Resources

Related skills
Installs
1
First Seen
Apr 9, 2026