Agent Skills: Go

Go programming patterns and idioms

UncategorizedID: miles990/claude-software-skills/go

Install this agent skill to your local

pnpm dlx add-skill https://github.com/miles990/claude-software-skills/tree/HEAD/programming-languages/go

Skill Files

Browse the full folder contents for go.

Download Skill

Loading file tree…

programming-languages/go/SKILL.md

Skill Metadata

Name
go
Description
Go programming patterns and idioms

Go

Overview

Go programming patterns including concurrency, error handling, and idiomatic Go code.


Basic Patterns

Structs and Methods

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

// Struct definition
type User struct {
	ID        string    `json:"id"`
	Email     string    `json:"email"`
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"created_at"`
	metadata  map[string]interface{} // unexported (private)
}

// Constructor function
func NewUser(email, name string) *User {
	return &User{
		ID:        generateID(),
		Email:     email,
		Name:      name,
		CreatedAt: time.Now(),
		metadata:  make(map[string]interface{}),
	}
}

// Value receiver (for read-only)
func (u User) FullName() string {
	return u.Name
}

// Pointer receiver (for mutations or large structs)
func (u *User) SetMetadata(key string, value interface{}) {
	u.metadata[key] = value
}

// Embedding (composition)
type Admin struct {
	User        // Embedded struct
	Permissions []string
}

func (a *Admin) HasPermission(perm string) bool {
	for _, p := range a.Permissions {
		if p == perm {
			return true
		}
	}
	return false
}

Interfaces

// Interface definition
type Repository interface {
	Find(id string) (*User, error)
	FindAll() ([]*User, error)
	Create(user *User) error
	Update(user *User) error
	Delete(id string) error
}

// Interface implementation (implicit)
type MemoryRepository struct {
	users map[string]*User
}

func NewMemoryRepository() *MemoryRepository {
	return &MemoryRepository{
		users: make(map[string]*User),
	}
}

func (r *MemoryRepository) Find(id string) (*User, error) {
	user, ok := r.users[id]
	if !ok {
		return nil, ErrNotFound
	}
	return user, nil
}

func (r *MemoryRepository) Create(user *User) error {
	r.users[user.ID] = user
	return nil
}

// Compile-time interface check
var _ Repository = (*MemoryRepository)(nil)

// Empty interface (any type)
func PrintAny(v interface{}) {
	fmt.Printf("%v\n", v)
}

// Type assertion
func ProcessValue(v interface{}) {
	switch val := v.(type) {
	case string:
		fmt.Println("String:", val)
	case int:
		fmt.Println("Int:", val)
	case *User:
		fmt.Println("User:", val.Name)
	default:
		fmt.Println("Unknown type")
	}
}

Error Handling

import (
	"errors"
	"fmt"
)

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

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

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

// Error wrapping
func GetUser(id string) (*User, error) {
	user, err := repository.Find(id)
	if err != nil {
		return nil, fmt.Errorf("getting user %s: %w", id, err)
	}
	return user, nil
}

// Error checking
func ProcessUser(id string) error {
	user, err := GetUser(id)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			return fmt.Errorf("user not found: %s", id)
		}
		var validationErr *ValidationError
		if errors.As(err, &validationErr) {
			return fmt.Errorf("validation failed: %s", validationErr.Field)
		}
		return err
	}
	// Process user...
	return nil
}

// Multi-error handling
type MultiError struct {
	Errors []error
}

func (m *MultiError) Error() string {
	var msgs []string
	for _, err := range m.Errors {
		msgs = append(msgs, err.Error())
	}
	return strings.Join(msgs, "; ")
}

func (m *MultiError) Add(err error) {
	if err != nil {
		m.Errors = append(m.Errors, err)
	}
}

func (m *MultiError) HasErrors() bool {
	return len(m.Errors) > 0
}

Concurrency

Goroutines and Channels

// Basic goroutine
func main() {
	go func() {
		fmt.Println("Hello from goroutine")
	}()

	time.Sleep(100 * time.Millisecond)
}

// Channel basics
func worker(jobs <-chan int, results chan<- int) {
	for job := range jobs {
		results <- job * 2
	}
}

func main() {
	jobs := make(chan int, 100)
	results := make(chan int, 100)

	// Start workers
	for w := 0; w < 3; w++ {
		go worker(jobs, results)
	}

	// Send jobs
	for j := 0; j < 9; j++ {
		jobs <- j
	}
	close(jobs)

	// Collect results
	for r := 0; r < 9; r++ {
		fmt.Println(<-results)
	}
}

// Select for multiple channels
func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	go func() {
		time.Sleep(100 * time.Millisecond)
		ch1 <- "one"
	}()

	go func() {
		time.Sleep(200 * time.Millisecond)
		ch2 <- "two"
	}()

	for i := 0; i < 2; i++ {
		select {
		case msg1 := <-ch1:
			fmt.Println("Received:", msg1)
		case msg2 := <-ch2:
			fmt.Println("Received:", msg2)
		case <-time.After(500 * time.Millisecond):
			fmt.Println("Timeout")
		}
	}
}

Concurrency Patterns

import (
	"context"
	"sync"
)

// Worker pool
type WorkerPool struct {
	numWorkers int
	jobs       chan func()
	wg         sync.WaitGroup
}

func NewWorkerPool(numWorkers int) *WorkerPool {
	pool := &WorkerPool{
		numWorkers: numWorkers,
		jobs:       make(chan func(), numWorkers*2),
	}
	pool.Start()
	return pool
}

func (p *WorkerPool) Start() {
	for i := 0; i < p.numWorkers; i++ {
		go func() {
			for job := range p.jobs {
				job()
				p.wg.Done()
			}
		}()
	}
}

func (p *WorkerPool) Submit(job func()) {
	p.wg.Add(1)
	p.jobs <- job
}

func (p *WorkerPool) Wait() {
	p.wg.Wait()
}

func (p *WorkerPool) Close() {
	close(p.jobs)
}

// Fan-out, fan-in
func FanOut(ctx context.Context, input <-chan int, workers int) []<-chan int {
	outputs := make([]<-chan int, workers)
	for i := 0; i < workers; i++ {
		outputs[i] = worker(ctx, input)
	}
	return outputs
}

func FanIn(ctx context.Context, channels ...<-chan int) <-chan int {
	var wg sync.WaitGroup
	merged := make(chan int)

	output := func(c <-chan int) {
		defer wg.Done()
		for v := range c {
			select {
			case merged <- v:
			case <-ctx.Done():
				return
			}
		}
	}

	wg.Add(len(channels))
	for _, c := range channels {
		go output(c)
	}

	go func() {
		wg.Wait()
		close(merged)
	}()

	return merged
}

// Rate limiter
type RateLimiter struct {
	ticker *time.Ticker
	tokens chan struct{}
}

func NewRateLimiter(rate int, burst int) *RateLimiter {
	rl := &RateLimiter{
		ticker: time.NewTicker(time.Second / time.Duration(rate)),
		tokens: make(chan struct{}, burst),
	}

	// Fill initial burst
	for i := 0; i < burst; i++ {
		rl.tokens <- struct{}{}
	}

	// Refill tokens
	go func() {
		for range rl.ticker.C {
			select {
			case rl.tokens <- struct{}{}:
			default:
			}
		}
	}()

	return rl
}

func (rl *RateLimiter) Wait(ctx context.Context) error {
	select {
	case <-rl.tokens:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

Context

import (
	"context"
	"time"
)

// Context with timeout
func FetchWithTimeout(url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return io.ReadAll(resp.Body)
}

// Context with values
type contextKey string

const userIDKey contextKey = "userID"

func WithUserID(ctx context.Context, userID string) context.Context {
	return context.WithValue(ctx, userIDKey, userID)
}

func GetUserID(ctx context.Context) (string, bool) {
	userID, ok := ctx.Value(userIDKey).(string)
	return userID, ok
}

// Passing context through layers
func Handler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	ctx = WithUserID(ctx, r.Header.Get("X-User-ID"))

	result, err := ProcessRequest(ctx)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(result)
}

func ProcessRequest(ctx context.Context) (*Result, error) {
	// Check for cancellation
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	userID, ok := GetUserID(ctx)
	if !ok {
		return nil, errors.New("user ID not found in context")
	}

	return fetchData(ctx, userID)
}

Generics (Go 1.18+)

// Generic function
func Map[T, U any](items []T, fn func(T) U) []U {
	result := make([]U, len(items))
	for i, item := range items {
		result[i] = fn(item)
	}
	return result
}

func Filter[T any](items []T, predicate func(T) bool) []T {
	var result []T
	for _, item := range items {
		if predicate(item) {
			result = append(result, item)
		}
	}
	return result
}

func Reduce[T, U any](items []T, initial U, fn func(U, T) U) U {
	result := initial
	for _, item := range items {
		result = fn(result, item)
	}
	return result
}

// Generic type constraint
type Number interface {
	~int | ~int32 | ~int64 | ~float32 | ~float64
}

func Sum[T Number](items []T) T {
	var sum T
	for _, item := range items {
		sum += item
	}
	return sum
}

// Generic struct
type Stack[T any] struct {
	items []T
}

func (s *Stack[T]) Push(item T) {
	s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
	if len(s.items) == 0 {
		var zero T
		return zero, false
	}
	item := s.items[len(s.items)-1]
	s.items = s.items[:len(s.items)-1]
	return item, true
}

// Usage
stack := &Stack[int]{}
stack.Push(1)
stack.Push(2)
val, ok := stack.Pop() // val = 2, ok = true

Testing

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

// Basic test
func TestSum(t *testing.T) {
	result := Sum([]int{1, 2, 3})
	if result != 6 {
		t.Errorf("expected 6, got %d", result)
	}
}

// Table-driven tests
func TestSumTableDriven(t *testing.T) {
	tests := []struct {
		name     string
		input    []int
		expected int
	}{
		{"empty", []int{}, 0},
		{"single", []int{5}, 5},
		{"multiple", []int{1, 2, 3}, 6},
		{"negative", []int{-1, 1}, 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := Sum(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test with testify
func TestUser(t *testing.T) {
	user := NewUser("test@example.com", "Test User")

	require.NotNil(t, user)
	assert.Equal(t, "test@example.com", user.Email)
	assert.Equal(t, "Test User", user.Name)
	assert.NotEmpty(t, user.ID)
}

// Benchmark
func BenchmarkSum(b *testing.B) {
	items := make([]int, 1000)
	for i := range items {
		items[i] = i
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		Sum(items)
	}
}

Related Skills

  • [[backend]] - Go web services
  • [[cloud-platforms]] - Cloud-native Go
  • [[system-design]] - System architecture