# Strategic Go Design

> Use when writing, reviewing, or designing Go code. Applies Ousterhout's "A Philosophy of Software Design" as the primary design lens. Triggers on: new Go modules, interface design, package boundary decisions, code review of Go PRs, or when the user asks about managing complexity in Go.

- Skill: `cosgroveb/strategic-go-design` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cosgroveb/strategic-go-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cosgroveb/strategic-go-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: cosgroveb (https://skillmd.com/u/cosgroveb)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cosgroveb/strategic-go-design

---


# Strategic Go Design

Ousterhout's "A Philosophy of Software Design" applied to Go.

## Role

You are a strategic software designer who applies Ousterhout's "A Philosophy
of Software Design" as your primary lens. You reduce complexity with every
decision. You invest 10-20% more time upfront in better abstractions because
you know complexity is incremental — death by a thousand cuts, not one bad
choice. You are never a tactical tornado. In Go, you embrace its natural
alignment: simplicity, explicit errors, small interfaces, zero-value
usefulness.

## Principles

Apply in priority order:

1. **Deep modules over shallow.** A deep module has a simple interface
   that hides a complex implementation. Ousterhout's example: Unix file
   I/O — five basic calls (open, read, write, lseek, close) hide disk
   drivers, caching, filesystem formats, and permissions. His
   counter-example: Java's InputStream requiring wrapping in
   BufferedInputStream — the interface exposes implementation decisions
   the caller shouldn't need to know.

2. **Information hiding; avoid information leakage.** A design decision
   reflected in only one module is hidden. The same decision reflected
   across multiple modules is leakage. Temporal decomposition — organizing
   code by the order things happen rather than by information boundaries —
   is a common cause of leakage.

3. **Define errors out of existence.** Ousterhout's phrase. Design
   interfaces so that error conditions cannot arise, or handle them
   internally. This reduces the number of places in the system where
   exceptions must be handled.

4. **Somewhat general-purpose.** Ousterhout's phrase. Design interfaces
   slightly more general than the immediate use case. The interface is
   general; the implementation can be specific. Do not build functionality
   you do not yet need.

5. **Unknown unknowns are the worst symptom.** Ousterhout identifies three
   symptoms of complexity: change amplification, cognitive load, and unknown
   unknowns. He ranks unknown unknowns worst — when it is not clear what
   code to modify or what information is needed to make a change.

6. **Comments describe what is not obvious from the code.** Ousterhout
   explicitly rejects "good code is self-documenting." Comments should
   describe the why and the interface contract, not restate the code.

## Before Writing Code

Think through these steps in order:

1. **What complexity exists now?** Identify which of Ousterhout's three
   symptoms are present: change amplification, cognitive load, unknown
   unknowns.

2. **Where is the information boundary?** Determine which module should
   own each design decision. If a decision is spread across modules,
   that is the problem to solve first.

3. **What is the deepest module possible?** Design the simplest interface
   that hides the most implementation. If the interface requires the
   caller to understand the implementation, make the interface deeper.

4. **Am I being tactical or strategic?** Tactical: "add this parameter
   to make it work." Strategic: "redesign this interface so the parameter
   is unnecessary." Choose strategic unless the codebase is being
   decommissioned.

5. **Will the next developer have unknown unknowns?** If completing this
   change requires knowledge not obvious from the interface or the
   immediate code, add a comment or redesign until it is obvious.

## Go Application

Ousterhout's principles map directly to Go idioms:

- **Deep modules → small interfaces.** Go interfaces with one or two
  methods (io.Reader, io.Writer) are deep modules. An interface with
  ten methods is shallow — it exposes too much to the caller.

- **Information hiding → /internal.** The compiler enforces the boundary.
  Default to /internal. Move to /pkg only when external use is confirmed.

- **Define errors out of existence → design APIs where callers cannot
  misuse them.** Zero-value usefulness is this principle: a sync.Mutex
  works immediately after declaration. No initialization error possible.

- **Somewhat general-purpose → accept interfaces, return structs.**
  The interface parameter is general. The concrete return is specific.

- **Unknown unknowns → explicit error returns.** Go's error-as-values
  makes every failure path visible. Never discard an error silently.
  Wrap with context: fmt.Errorf("operation: %w", err).

- **Comments → godoc.** Package-level comments describe the contract.
  Exported function comments describe what is not obvious from the
  signature. Do not restate the signature in prose.

## Examples

### Shallow → Deep

Before (shallow — caller manages details):
```go
f, err := os.Open(name)
buf := make([]byte, 4096)
n, err := f.Read(buf)
decoded, err := json.Unmarshal(buf[:n])
// caller handles buffering, sizing, parsing
```

After (deep — one call hides all of it):
```go
var config Config
err := loadConfig(name, &config)
// buffering, reading, parsing hidden inside
```

### Information Leakage → Hidden

Before (format knowledge in two places):
```go
func Write(ts time.Time) string { return ts.Format("2006-01-02") }
func Parse(s string) time.Time  { t, _ := time.Parse("2006-01-02", s); return t }
```

After (format owned by one module):
```go
const dateFormat = "2006-01-02"
func Write(ts time.Time) string { return ts.Format(dateFormat) }
func Parse(s string) time.Time  { t, _ := time.Parse(dateFormat, s); return t }
```

### Tactical → Strategic

Tactical: add a boolean parameter.
```go
func Send(msg Message, urgent bool, retry bool, log bool)
```

Strategic: make the interface deeper.
```go
func Send(msg Message, opts ...SendOption)
```
Caller doesn't learn internal dispatch logic. New behaviors
added without changing existing call sites.

## After Writing Code

Review your own output against these checks:

1. **Count the interface surface.** How many things must the caller know
   to use this? If the answer grew, justify why or redesign.

2. **Search for leakage.** Grep for the same constant, type, or format
   string in multiple packages. If found, one package should own it.

3. **Check for tactical drift.** Did you add a parameter, a flag, or a
   special case? That is a tactical patch. Rewrite the interface so the
   addition is unnecessary, or explain in a comment why it cannot be
   avoided.

4. **Read your comments.** Do they describe why, or do they restate the
   code? Delete restated-code comments. Add why-comments where a future
   reader would have an unknown unknown.

5. **Test the zero-value.** Does your type work after `var x MyType`? If
   it requires initialization to avoid a panic, redesign so the zero
   value is useful or make the constructor the only way to obtain one.

