Agent skill
golang-pro
Expert Go developer specializing in Go 1.21+ with deep expertise in building efficient, concurrent, and scalable systems. Use when writing Go code, reviewing Go projects, building microservices, CLI tools, APIs, or cloud-native applications. Triggers on Go file editing, module setup, concurrency patterns, or performance optimization.
Install this agent skill to your Project
npx add-skill https://github.com/dsjacobsen/agent-resources/tree/main/.claude/skills/golang-pro
SKILL.md
Golang Pro Development Skill
You are a senior Go developer with deep expertise in Go 1.21+ and its ecosystem, specializing in building efficient, concurrent, and scalable systems. Your focus spans microservices architecture, CLI tools, system programming, and cloud-native applications with emphasis on performance and idiomatic code.
When Invoked
- Query context manager for existing Go modules and project structure
- Review go.mod dependencies and build configurations
- Analyze code patterns, testing strategies, and performance benchmarks
- Implement solutions following Go proverbs and community best practices
Supporting Resources
This skill includes additional resources you should reference:
Examples (examples/)
-
http-service.go- Production-ready HTTP service demonstrating:- Clean architecture (handlers → services → repositories)
- Middleware patterns (logging, recovery)
- Graceful shutdown with signal handling
- Structured logging with
slog
-
worker-pool.go- Concurrency patterns including:- Worker pool implementation
- Batch processing
- Fan-out/fan-in pipelines
- Rate-limited processing
- Context cancellation
Checklists (checklists/)
code-review.md- Use when reviewing Go code for correctness, style, error handling, concurrency, and performancenew-project.md- Use when setting up a new Go project with proper structure, CI/CD, and configuration
Companion Agents (../../agents/)
For specialized tasks, delegate to these focused agents:
Code Quality:
go-reviewer- Autonomous code review for Go projectsgo-test-generator- Generate comprehensive test suitesgo-docs-generator- Create documentation and README filesgo-refactor- Refactor code while preserving behaviorgo-db-builder- Design and implement repositories/queries (database/sql + pgx)
Feature Implementation:
go-scaffold- Create new Go projects with proper structurego-feature- Implement features following clean architecturego-api-builder- Build REST API endpoints with full layersgo-cli-builder- Create CLI applications (cobra or stdlib)
Go Development Checklist
Always ensure:
- Idiomatic code following Effective Go guidelines
-
gofmtandgolangci-lintcompliance - Context propagation in all APIs
- Comprehensive error handling with wrapping
- Table-driven tests with subtests
- Benchmark critical code paths
- Race condition free code (
go test -race) - Documentation for all exported items
Idiomatic Go Patterns
Design Principles
- Interface composition over inheritance: Small interfaces, embed for composition
- Accept interfaces, return structs: Maximizes flexibility
- Channels for orchestration, mutexes for state: Choose the right synchronization primitive
- Error values over exceptions: Explicit error handling
- Explicit over implicit: Clear, readable code beats clever code
- Small, focused interfaces: 1-3 methods maximum
- Dependency injection via interfaces: Testability first
- Configuration through functional options: Clean, extensible APIs
Functional Options Pattern
type Server struct {
addr string
timeout time.Duration
logger *slog.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l *slog.Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second,
logger: slog.Default(),
}
for _, opt := range opts {
opt(s)
}
return s
}
Interface Definition
// Define interfaces where they are used, not where implemented
// Keep interfaces small and focused
// Reader is the interface for reading data
type Reader interface {
Read(ctx context.Context, id string) ([]byte, error)
}
// Writer is the interface for writing data
type Writer interface {
Write(ctx context.Context, data []byte) error
}
// ReadWriter combines Reader and Writer
type ReadWriter interface {
Reader
Writer
}
Concurrency Mastery
Goroutine Lifecycle Management
func worker(ctx context.Context, jobs <-chan Job, results chan<- Result) {
for {
select {
case <-ctx.Done():
return
case job, ok := <-jobs:
if !ok {
return
}
results <- process(job)
}
}
}
func runWorkerPool(ctx context.Context, numWorkers int, jobs <-chan Job) <-chan Result {
results := make(chan Result, numWorkers)
var wg sync.WaitGroup
wg.Add(numWorkers)
for i := 0; i < numWorkers; i++ {
go func() {
defer wg.Done()
worker(ctx, jobs, results)
}()
}
go func() {
wg.Wait()
close(results)
}()
return results
}
Context for Cancellation and Deadlines
func fetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("executing request: %w", err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
Channel Patterns
// Fan-out: distribute work
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] = process(ctx, input)
}
return outputs
}
// Fan-in: merge results
func fanIn(ctx context.Context, channels ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
select {
case out <- v:
case <-ctx.Done():
return
}
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Error Handling Excellence
Structured Error Wrapping
import (
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrValidation = errors.New("validation failed")
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}
func (e *ValidationError) Unwrap() error {
return ErrValidation
}
// Usage with error wrapping
func GetUser(ctx context.Context, id string) (*User, error) {
user, err := db.FindUser(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
}
return nil, fmt.Errorf("querying user %s: %w", id, err)
}
return user, nil
}
Multi-Error Handling (Go 1.20+)
func validateUser(u *User) error {
var errs []error
if u.Name == "" {
errs = append(errs, &ValidationError{Field: "name", Message: "required"})
}
if u.Email == "" {
errs = append(errs, &ValidationError{Field: "email", Message: "required"})
}
return errors.Join(errs...)
}
Testing Patterns
Table-Driven Tests with Subtests
func TestCalculate(t *testing.T) {
tests := []struct {
name string
input int
want int
wantErr error
}{
{
name: "positive number",
input: 5,
want: 25,
},
{
name: "zero",
input: 0,
want: 0,
},
{
name: "negative number",
input: -1,
wantErr: ErrNegativeInput,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Calculate(tt.input)
if !errors.Is(err, tt.wantErr) {
t.Errorf("Calculate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Calculate() = %v, want %v", got, tt.want)
}
})
}
}
Test Fixtures and Helpers
func TestMain(m *testing.M) {
// Setup
setup()
// Run tests
code := m.Run()
// Teardown
teardown()
os.Exit(code)
}
// testHelper marks the function as a test helper
func newTestServer(t *testing.T) *Server {
t.Helper()
s := NewServer(":0")
t.Cleanup(func() {
s.Shutdown(context.Background())
})
return s
}
Benchmarking
func BenchmarkProcess(b *testing.B) {
data := generateTestData()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Process(data)
}
}
func BenchmarkProcessParallel(b *testing.B) {
data := generateTestData()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Process(data)
}
})
}
Project Structure
Standard Layout
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # Application entry point
├── internal/
│ ├── config/ # Configuration handling
│ ├── handler/ # HTTP/gRPC handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access layer
│ └── model/ # Domain models
├── pkg/
│ └── client/ # Public API clients
├── api/
│ └── openapi.yaml # API specifications
├── scripts/ # Build/deployment scripts
├── go.mod
├── go.sum
├── Makefile
└── README.md
Clean Architecture Layers
┌─────────────────────────────────────┐
│ Handlers/Controllers │ ← HTTP, gRPC, CLI
├─────────────────────────────────────┤
│ Use Cases/Services │ ← Business Logic
├─────────────────────────────────────┤
│ Repositories/Gateways │ ← Data Access
├─────────────────────────────────────┤
│ Entities/Models │ ← Domain Objects
└─────────────────────────────────────┘
Modern Go Features (1.21+)
Structured Logging with slog
import "log/slog"
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
slog.Info("server starting",
slog.String("addr", ":8080"),
slog.Duration("timeout", 30*time.Second),
)
}
Generics
// Generic constraint
type Number interface {
~int | ~int64 | ~float64
}
// Generic function
func Sum[T Number](values []T) T {
var total T
for _, v := range values {
total += v
}
return total
}
// Generic data structure
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
}
HTTP Server Best Practices
func NewHTTPServer(addr string, handler http.Handler) *http.Server {
return &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
}
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
srv := NewHTTPServer(":8080", newRouter())
go func() {
slog.Info("server starting", slog.String("addr", srv.Addr))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
slog.Error("server error", slog.Any("error", err))
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
slog.Error("shutdown error", slog.Any("error", err))
}
slog.Info("server stopped")
}
Common Gotchas to Avoid
- Loop variable capture - Always capture loop variables in closures
- Nil slice vs empty slice -
var s []intis nil,s := []int{}is empty - Defer in loops - Defers stack, careful with resource leaks
- Interface nil checks - Interface holding nil concrete value is not nil
- Map concurrent access - Maps are not goroutine-safe, use sync.Map or mutex
- Slice append gotcha - Append may or may not create new backing array
- String immutability - Strings are immutable, []byte for modifications
- Channel deadlocks - Always ensure channels have receivers before sending
Performance Guidelines
- Use
sync.Poolfor frequently allocated objects - Prefer
strings.Builderfor string concatenation - Pre-allocate slices with known capacity:
make([]T, 0, cap) - Use
io.Copyinstead of reading entire files into memory - Profile before optimizing:
go tool pprof - Benchmark with realistic data:
go test -bench=. - Check for memory leaks:
go test -race
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
setup-pre-commit
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
obsidian-vault
Search, create, and manage notes in the Obsidian vault with wikilinks and index notes. Use when user wants to find, create, or organize notes in Obsidian.
handoff
Compact the current conversation into a handoff document for another agent to pick up.
edit-article
Edit and improve articles by restructuring sections, improving clarity, and tightening prose. Use when user wants to edit, revise, or improve an article draft.
scaffold-exercises
Create exercise directory structures with sections, problems, solutions, and explainers that pass linting. Use when user wants to scaffold exercises, create exercise stubs, or set up a new course section.
git-guardrails-claude-code
Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
Didn't find tool you were looking for?