go-core
Core Go development patterns and idioms. Use for package design, code organization, configuration patterns, interface definitions, or when editing .go files. Triggers: package structure, dependency hierarchy, interface contracts, config transformation, encapsulation, layered organization, parameter encapsulation, modern Go idioms.
When & Why to Use This Skill
This Claude skill provides comprehensive guidance on idiomatic Go development, focusing on robust package design, clean code organization, and modern Golang patterns. It empowers developers to build scalable and maintainable software by enforcing best practices such as interface-based decoupling, configuration transformation, and strict encapsulation, ensuring codebases align with professional Go standards and the latest language features.
Use Cases
- Designing a new Go project structure from scratch to ensure a clear, unidirectional package dependency hierarchy and avoid circular imports.
- Refactoring legacy Go codebases to adopt modern idioms (Go 1.25+) like sync.WaitGroup.Go(), errors.Join(), and built-in min/max functions.
- Implementing robust configuration management by transforming raw config data into validated domain objects at package boundaries.
- Improving API maintainability by encapsulating multiple function parameters into structured request objects and providing semantic getters for complex data structures.
- Decoupling system layers using the Contract Interface Pattern to facilitate easier unit testing and modular component replacement.
| name | go-core |
|---|---|
| description | > |
Go Core Development
When This Skill Applies
- Designing package structure
- Organizing code within files
- Implementing configuration patterns
- Defining interfaces and contracts
- Working with dependencies between packages
- Any Go file editing
Principles
1. Configuration Transformation Pattern
Configuration packages serve as ephemeral data containers that transform into domain objects at package boundaries.
Key Pattern:
- Configuration types handle: structure, defaults, serialization, merging
- Domain creation happens via finalization/validation functions
- Runtime behavior depends on initialized state, not configuration values
- Configuration should not persist beyond initialization phase
// Configuration: structure and defaults only
type ServerConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
Timeout time.Duration `toml:"timeout"`
}
// Transformation: config → domain object
func NewServer(cfg ServerConfig) (*Server, error) {
if cfg.Port == 0 {
cfg.Port = 8080 // Default
}
return &Server{
addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
timeout: cfg.Timeout,
}, nil
}
Decision Framework:
- Type 1 (Initialization-Only): Config discarded after creating domain object
- Type 2 (Immutable Runtime Settings): Config stored throughout object lifetime
- Type 3 (Mutable Runtime Settings): Config with validated setters and mutex protection
2. Encapsulation & Data Access Pattern
Never expose direct field access to nested structures; always provide semantic getter methods.
Bad:
// Exposes internal structure, fragile to changes
chunk.Choices[0].Delta.Content
Good:
// Semantic getter encapsulates access logic
func (c *Chunk) ExtractContent() string {
if len(c.Choices) == 0 {
return ""
}
return c.Choices[0].Delta.Content
}
// Usage
content := chunk.ExtractContent()
Benefits:
- Hides internal structure complexity
- Bounds checking in one place
- Easier refactoring
3. Layered Code Organization
Structure code within files in dependency order: foundational types first.
Order:
- Package declaration
- Imports
- Constants
- Global variables
- Interfaces
- Pure types/enums (data structures without methods)
- Structures + methods (grouped together)
- Standalone functions
package example
import "context"
// Constants
const DefaultTimeout = 30 * time.Second
// Interfaces
type Repository interface {
Find(ctx context.Context, id string) (*Entity, error)
}
// Pure types
type EntityType string
const (
TypeA EntityType = "a"
TypeB EntityType = "b"
)
// Structures with methods
type Entity struct {
ID string
Type EntityType
}
func (e *Entity) Validate() error {
if e.ID == "" {
return errors.New("id required")
}
return nil
}
// Standalone functions
func NewEntity(id string, t EntityType) *Entity {
return &Entity{ID: id, Type: t}
}
4. Parameter Encapsulation Rule
If a function requires more than 2 parameters, encapsulate them into a structure.
Bad:
func Execute(ctx context.Context, capability string, input string,
timeout time.Duration, retries int, cache bool) (*Result, error)
Good:
type ExecuteRequest struct {
Capability string
Input string
Timeout time.Duration
Retries int
UseCache bool
}
func Execute(ctx context.Context, req ExecuteRequest) (*Result, error)
Benefits:
- Self-documenting named fields
- Optional parameters through zero values
- Easier extension without breaking existing calls
5. Interface-Based Layer Interconnection
Layers should interconnect exclusively through interfaces, not concrete types.
// Interface defines public API
type Renderer interface {
Render(input []byte) ([]byte, error)
}
// Constructor returns interface, not concrete type
func NewImageMagickRenderer(cfg ImageConfig) (Renderer, error) {
return &imageMagickRenderer{cfg: cfg}, nil
}
// Consumer stores interface dependency
type PDFDocument struct {
renderer Renderer // Interface, not *imageMagickRenderer
}
func NewPDFDocument(r Renderer) *PDFDocument {
return &PDFDocument{renderer: r}
}
6. Package Dependency Hierarchy
Maintain clear, unidirectional dependencies flowing from high-level to low-level packages.
Level 0: observability/ (no dependencies)
↓
Level 1: messaging/ (depends on observability)
↓
Level 2: hub/ (depends on messaging)
↓
Level 3: state/ (depends on observability)
↓
Level 4: workflows/ (depends on state + observability)
Rules:
- Lower layers cannot import higher layers
- Prevents circular dependencies
- Each layer validates independently
- Higher layers depend on lower-level interfaces
7. Package Organization Depth Limitation
Avoid package subdirectories deeper than one level.
Good:
pkg/
├── image/
├── cache/
└── document/
Bad:
pkg/
└── document/
└── formats/
└── processors/
└── types/
Deep nesting signals architectural problems. Ask: Should this be a separate package?
8. Contract Interface Pattern
Lower-level packages define minimal interfaces that higher-level packages implement.
// In pkg/cache (lower level)
type Logger interface {
Log(ctx context.Context, level string, msg string)
}
// Cache uses the interface, doesn't import logger package
type Cache struct {
logger Logger
}
func NewCache(logger Logger) *Cache {
return &Cache{logger: logger}
}
// In pkg/logger (higher level) - implements the contract
type SlogAdapter struct {
slog *slog.Logger
}
func (a *SlogAdapter) Log(ctx context.Context, level, msg string) {
a.slog.Log(ctx, parseLevel(level), msg)
}
// Usage: dependency injection
cache := NewCache(&SlogAdapter{slog: slog.Default()})
9. Modern Go Idioms (Go 1.25+)
Leverage latest language features and standard library methods.
// sync.WaitGroup.Go() - Combines Add(1) + goroutine launch + implicit Done()
var wg sync.WaitGroup
for _, task := range tasks {
wg.Go(func() {
process(task)
})
}
wg.Wait()
// for range n - Integer range without index variable
workers := min(runtime.NumCPU()*2, len(tasks))
for range workers {
wg.Go(func() { /* worker */ })
}
// min()/max() built-ins
limit := min(requested, maxAllowed)
// errors.Join() - Combine multiple errors
var errs []error
for _, item := range items {
if err := validate(item); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
// defer close(channel) - Always in sender goroutine
go func() {
defer close(results)
for item := range input {
results <- process(item)
}
}()
Anti-Patterns
Leaky Configuration
// Bad: Config persists and is accessed at runtime
type Service struct {
config Config // Stored and used later
}
func (s *Service) Process() {
timeout := s.config.Timeout // Accessing config at runtime
}
// Good: Config transformed at construction
type Service struct {
timeout time.Duration // Only the needed value stored
}
func NewService(cfg Config) *Service {
return &Service{timeout: cfg.Timeout}
}
God Packages
// Bad: Package does too many things
package utils
func ParseJSON() {}
func SendEmail() {}
func ResizeImage() {}
func ValidateInput() {}
// Good: Single responsibility packages
package json
package email
package image
package validation
Circular Dependencies
// Bad: A imports B, B imports A
package a
import "project/b"
package b
import "project/a" // Circular!
// Good: Extract shared interface to lower level
package contracts
type Processor interface { Process() }
package a
import "project/contracts"
package b
import "project/contracts"