Claude-skill-registry kennedy-mechanical-sympathy
Write Go code in the style of Bill Kennedy, author of Go in Action. Emphasizes mechanical sympathy, data-oriented design, and understanding how Go code executes. Use when writing performance-critical Go or when teaching Go fundamentals.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/kennedy" ~/.claude/skills/majiayu000-claude-skill-registry-kennedy-mechanical-sympathy && rm -rf "$T"
skills/data/kennedy/SKILL.mdBill Kennedy Style Guide
Overview
Bill Kennedy is the author of "Go in Action" and founder of Ardan Labs. His teaching emphasizes mechanical sympathy: understanding how software interacts with hardware. His "Ultimate Go" course is legendary for deep-dive explanations.
Core Philosophy
"Integrity, readability, and simplicity—in that order."
"If you don't understand the data, you don't understand the problem."
"Mechanical sympathy: understanding how the hardware and runtime work."
Kennedy believes that great Go code comes from understanding what happens beneath the surface: memory layout, garbage collection, scheduler behavior.
Design Principles
-
Data-Oriented Design: Design around data transformations, not object hierarchies.
-
Mechanical Sympathy: Write code that works with the hardware, not against it.
-
Value Semantics First: Prefer values over pointers unless you have a reason.
-
Integrity First: Correctness beats performance, readability beats cleverness.
When Writing Code
Always
- Understand the memory layout of your data structures
- Know when copies happen and when references are used
- Consider CPU cache behavior for hot paths
- Profile before optimizing
- Use value semantics by default
- Understand escape analysis
Never
- Optimize without profiling
- Use pointers just to "avoid copies" without measuring
- Create deep pointer chains (bad for cache)
- Ignore alignment and padding
- Assume you know what escapes to heap
Prefer
- Contiguous data (slices) over pointer-heavy structures
- Value receivers for small, immutable types
- Stack allocation over heap when possible
- Struct of arrays over array of structs for hot loops
- Understanding over blind rules
Code Patterns
Data-Oriented Design
// BAD: Object-oriented thinking, pointer-heavy type Node struct { Value int Children []*Node // Pointers scattered in memory } // GOOD: Data-oriented, cache-friendly type Tree struct { Values []int // Contiguous memory Children [][]int // Indices into Values } // For hot loops, struct of arrays beats array of structs // BAD: Array of structs (AoS) type Particle struct { X, Y, Z float64 VX, VY, VZ float64 Mass float64 } particles := make([]Particle, 1000) // GOOD: Struct of arrays (SoA) - better cache utilization type Particles struct { X, Y, Z []float64 VX, VY, VZ []float64 Mass []float64 } p := Particles{ X: make([]float64, 1000), Y: make([]float64, 1000), // ... } // When updating just positions: for i := range p.X { p.X[i] += p.VX[i] // Sequential memory access p.Y[i] += p.VY[i] p.Z[i] += p.VZ[i] }
Value vs Pointer Semantics
// Value semantics: type is small, immutable logically type Time struct { sec int64 nsec int32 } func (t Time) Add(d Duration) Time { return Time{sec: t.sec + int64(d), nsec: t.nsec} } // Pointer semantics: type represents a resource or is large type File struct { fd int name string // ... } func (f *File) Read(b []byte) (int, error) { // Modifies state, represents resource } // RULE: Pick one semantic and be consistent for a type // If any method needs pointer, use pointer for all methods
Understanding Escape Analysis
// Stack allocation: fast, automatic cleanup func sumLocal() int { numbers := [4]int{1, 2, 3, 4} // Array on stack sum := 0 for _, n := range numbers { sum += n } return sum // numbers never escapes } // Heap allocation: slower, needs GC func sumHeap() *int { sum := 0 for i := 0; i < 4; i++ { sum += i } return &sum // sum escapes to heap! } // Check with: go build -gcflags="-m" // ./main.go:10:2: moved to heap: sum // Slices and interfaces often cause escapes func process(data []byte) { // If data is used after function returns // or passed to interface{}, it may escape }
Memory Layout Awareness
// Struct padding wastes memory // BAD: Poor layout (24 bytes with padding) type BadLayout struct { a bool // 1 byte + 7 padding b int64 // 8 bytes c bool // 1 byte + 7 padding } // GOOD: Optimized layout (16 bytes) type GoodLayout struct { b int64 // 8 bytes a bool // 1 byte c bool // 1 byte + 6 padding } // Check with: unsafe.Sizeof() // Or use: go vet -fieldalignment
Slice Internals
// Slice header: (pointer, length, capacity) // Understanding this prevents bugs func modify(s []int) { s[0] = 999 // Modifies original! s = append(s, 4) // May or may not affect original } func main() { original := []int{1, 2, 3} modify(original) // original[0] is 999 // but append may have created new backing array } // Safe pattern: return the slice func appendSafe(s []int, v int) []int { return append(s, v) } original = appendSafe(original, 4)
Benchmarking Properly
func BenchmarkProcess(b *testing.B) { // Setup outside the loop data := generateTestData() b.ResetTimer() // Don't count setup time for i := 0; i < b.N; i++ { result := Process(data) // Prevent compiler from optimizing away _ = result } } // Compare implementations func BenchmarkProcessV1(b *testing.B) { ... } func BenchmarkProcessV2(b *testing.B) { ... } // Run with: go test -bench=. -benchmem // BenchmarkProcessV1-8 1000000 1234 ns/op 256 B/op 3 allocs/op // BenchmarkProcessV2-8 2000000 567 ns/op 0 B/op 0 allocs/op
Goroutine Pool Pattern
type Pool struct { work chan func() sem chan struct{} } func NewPool(size int) *Pool { p := &Pool{ work: make(chan func()), sem: make(chan struct{}, size), } return p } func (p *Pool) Submit(task func()) { select { case p.work <- task: // Worker picked it up case p.sem <- struct{}{}: // Start new worker go p.worker(task) } } func (p *Pool) worker(task func()) { defer func() { <-p.sem }() for { task() task = <-p.work } }
Mental Model
Kennedy teaches by asking:
- What's the data? Understand it before writing code.
- Where does it live? Stack? Heap? How is it laid out?
- How does it flow? What transformations happen?
- What's the cost? Allocations, copies, cache misses?
Kennedy's Priorities
- Integrity: Code must be correct
- Readability: Code must be maintainable
- Simplicity: Don't over-engineer
- Performance: After the above are satisfied
In that order.