# Go Idiomatic Discipline

> Guides core Go authoring discipline along two axes — handle errors honestly and stop fighting the language on the floor (no discarded errors, no panic for ordinary failure, no Java/Python-in-Go), and don't over-abstract or out-clever it on the ceiling (no interface-per-struct, no premature generics, no framework scaffolding for a small tool). The judgment target is "clear AND correct." Auto-invokes when writing or editing .go files, and on "make it idiomatic", "is this idiomatic Go", or "clean this up" requests. The dual-axis policy root every other Go skill routes back to.

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

---


# Go Idiomatic Discipline

> "Clear is better than clever."
> — [Go Proverbs](https://go-proverbs.github.io/)

> "Errors are values." · "Don't just check errors, handle them gracefully." · "Don't panic."
> — [Go Proverbs](https://go-proverbs.github.io/)

> "A straightforward translation of a C++ or Java program into Go is unlikely to produce a satisfactory result—Java programs are written in Java, not Go."
> — [Effective Go](https://go.dev/doc/effective_go)

Idiomatic Go is not a matter of taste. The language has a small, opinionated grain, and code either runs with it or fights it. There are two opposite ways to fight it, and this skill bans both.

---

## 1. The Two Failure Modes

Writing Go well means landing between two opposite mistakes. Both compile. Both ship. Both are wrong.

### Axis 1 — too sloppy / fighting Go (the floor)

Under time pressure the model writes **Java- or Python-in-Go**: it silences the compiler and the runtime instead of working with them. It discards an error with `_`, reaches for `panic` on an ordinary failure, wraps everything in getter/setter classes, builds OOP inheritance fantasies, and stashes mutable state in package globals. Each is the Go-native equivalent of an escape hatch — the program looks finished, but a real failure is now invisible. Go's own guidance is blunt that this is the wrong move: "A straightforward translation of a C++ or Java program into Go is unlikely to produce a satisfactory result" ([Effective Go](https://go.dev/doc/effective_go)); "to write Go well, it's important to understand its properties and idioms" ([Effective Go](https://go.dev/doc/effective_go)).

### Axis 2 — too clever / over-abstracted (the ceiling)

Asked to "make it idiomatic" or "make it robust," the model **over-builds to look thorough**: an interface for every struct, generics with a single caller, a constructor that only zeroes fields, a 200-line clever generic, deep framework scaffolding around a 50-line tool. Every Go Proverb on the page pushes the other way: "Clear is better than clever"; "The bigger the interface, the weaker the abstraction"; "A little copying is better than a little dependency"; "interface{} says nothing" ([Go Proverbs](https://go-proverbs.github.io/)). Abstraction that no second implementation justifies is a cost with no buyer.

This skill **names** both axes and states the headline rules. Depth routes to the specific skills (Section 8).

---

## 2. The Meta-Rule: Clear AND Correct

The judgment target is code that is **both clear and correct.**

- Code that ignores an error or panics on ordinary failure is **incorrect** — that is axis 1.
- Code that is correct but needlessly abstract or clever is *also* wrong — that is axis 2. It compiles, it ships, and it is still a wrong answer because the next reader pays for it.

The Google Go Style Guide ranks the attributes of readable code "in order of importance": **clarity, simplicity, concision, maintainability, consistency** ([Google Go Style Guide — Guide](https://google.github.io/styleguide/go/guide)). Clarity is first, and its companion rule is least mechanism: "Where there are several ways to express the same idea, prefer the one that uses the most standard tools" ([Google Go Style Guide — Guide](https://google.github.io/styleguide/go/guide)).

When you author or review Go, both questions must pass:

1. **Is it correct?** Is every error handled, every goroutine stoppable, every failure surfaced — not swallowed?
2. **Is it clear?** Would the simplest reader on the team follow it? Is every abstraction earned by a real second caller, or is it scaffolding?

"Make it idiomatic" is satisfied only when both hold.

---

## 3. The Headline Disciplines

Each rule below is stated here as policy; the owning skill (Section 8) holds the depth.

| Discipline | The rule | Source |
|---|---|---|
| Errors are values | Never discard with `_`; check, handle, or return — wrap with `%w` | "Errors are values" ([Proverbs](https://go-proverbs.github.io/)); "Do not discard errors using `_` variables" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#handle-errors)) |
| Don't panic | `panic` is for programmer bugs, not ordinary failure | "Don't use panic for normal error handling. Use error and multiple return values" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#dont-panic)) |
| Line of sight | Happy path at minimal indent; handle the error first and return | "keep the normal code path at a minimal indentation, and indent the error handling, dealing with it first" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#indent-error-flow)) |
| No `Get` getters | A getter for `owner` is `Owner()`, not `GetOwner()` | "it's neither idiomatic nor necessary to put `Get` into the getter's name" ([Effective Go](https://go.dev/doc/effective_go)) |
| Small, consumer-side interfaces | Define interfaces where they're used, not per struct | "The bigger the interface, the weaker the abstraction" ([Proverbs](https://go-proverbs.github.io/)); "Do not define interfaces before they are used" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#interfaces)) |
| Don't reach for generics first | Use an interface for behavior; reach for type params only on real duplication | "You should avoid type parameters until you notice that you are about to write the exact same code multiple times" ([When To Use Generics](https://go.dev/blog/when-generics)) |
| Make the zero value useful | Avoid constructors that only zero fields | "Make the zero value useful" ([Proverbs](https://go-proverbs.github.io/)) |
| No mutable package globals | Configure with arguments and fields, not exported globals | "prefer explicit function arguments or struct field assignment or ... under the strictest of scrutiny exported global variables" ([Google Go Style Guide — Decisions](https://google.github.io/styleguide/go/decisions#global-variables)) |
| Gofmt decides | Don't hand-format; run `gofmt`/`gofumpt` | "Gofmt's style is no one's favorite, yet gofmt is everyone's favorite" ([Proverbs](https://go-proverbs.github.io/)) |

---

## 4. The Headline Rule: Errors Are Values, Never Silently Discarded

The single highest-frequency Go failure is the swallowed error. The Go Proverbs state it twice — "Errors are values" and "Don't just check errors, handle them gracefully" ([Go Proverbs](https://go-proverbs.github.io/)) — and Code Review Comments makes it a hard rule: "Do not discard errors using `_` variables. If a function returns an error, check it to make sure the function succeeded. Handle the error, return it, or, in truly exceptional situations, panic" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#handle-errors)).

```go
// WRONG — the error is discarded; a malformed payload becomes a silent empty struct
var cfg Config
_ = json.Unmarshal(data, &cfg)
return cfg

// RIGHT — check it, add context, return it; line of sight keeps the happy path flat
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
	return Config{}, fmt.Errorf("parsing config: %w", err)
}
return cfg, nil
```

Because errors are ordinary values, "the full power of the Go programming language is available for processing them" ([Errors are values](https://go.dev/blog/errors-are-values)) — but the floor is non-negotiable: "Whatever you do, always check your errors!" ([Errors are values](https://go.dev/blog/errors-are-values)). This skill states the floor; **`go-error-handling`** owns the depth: `%w` wrapping, `errors.Is`/`As`, sentinel vs typed errors, `errors.Join`, and message conventions.

---

## 5. Don't Fight Go (Axis 1 in Brief)

The recurring tells of Java/Python-in-Go, each routed to its owning skill:

- **Discarded errors / panic for ordinary failure** — see Section 4 and `go-error-handling`, `go-defer-panic-recover`. "Don't use panic for normal error handling" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#dont-panic)).
- **`Get`-prefixed getters and getter/setter classes** — the field `owner` is read with `Owner()`, not `GetOwner()` ([Effective Go](https://go.dev/doc/effective_go)). Use `Counts` over `GetCounts` ([Google Go Style Guide — Decisions](https://google.github.io/styleguide/go/decisions#getters)). Owned by `go-naming-and-style`.
- **OOP inheritance fantasies** — Go composes with embedding and satisfies interfaces structurally; there is no class hierarchy to port. Owned by `go-interfaces`, `go-zero-values-and-construction`.
- **Mutable package globals** — prefer arguments and struct fields ([Google Go Style Guide — Decisions](https://google.github.io/styleguide/go/decisions#global-variables)). Owned by `go-zero-values-and-construction`.
- **Leaked goroutines** — never start one you can't stop; tie it to a `context` or a `WaitGroup`. Owned by `go-concurrency-goroutines`.

---

## 6. Don't Out-Clever Go (Axis 2 in Brief)

The recurring tells of over-abstraction, each routed to its owning skill:

- **Interface pollution** — one interface mirroring a struct's whole method set, defined on the producer side "for mocking." "Do not define interfaces on the implementor side of an API"; "Do not define interfaces before they are used" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#interfaces)). "The bigger the interface, the weaker the abstraction" ([Proverbs](https://go-proverbs.github.io/)). Owned by `go-interfaces`.
- **Premature generics** — type parameters with one caller, where an interface or a concrete type reads better. "If all you need to do with a value of some type is call a method on that value, use an interface type, not a type parameter" ([When To Use Generics](https://go.dev/blog/when-generics)). Owned by `go-generics`.
- **Needless constructors** — a `New` that only returns `&T{}`; prefer a useful zero value. "Make the zero value useful" ([Proverbs](https://go-proverbs.github.io/)). Owned by `go-zero-values-and-construction`.
- **Framework scaffolding for a small tool** — a deep `pkg/`/`internal/`/`api/` tree and `util`/`common` grab-bags around a 50-line program. "A little copying is better than a little dependency" ([Proverbs](https://go-proverbs.github.io/)). Owned by `go-project-layout`.

---

## 7. Who Suffers When Go Is Done Badly

Both axes have a victim, and it is never the author at write time:

- The **on-call engineer** paged at 3am by a goroutine leak that exhausted memory — started by a library that spawned work the caller couldn't stop (axis 1).
- The **teammate** who loses an afternoon to a bug whose root cause was a `_ = err` three layers down that swallowed the only diagnostic (axis 1).
- The **reviewer** who has to reverse-engineer a 200-line clever generic, or an interface-per-struct mock maze, to make a one-line change (axis 2).

"Clear is better than clever" ([Proverbs](https://go-proverbs.github.io/)) is an empathy rule, not an aesthetic one: the clever version offloads cost onto whoever reads the code next. Idiomatic Go is what you write so that nobody downstream pays for your shortcut or your showmanship.

---

## 8. Routing to the Specific Skills

This skill is the **policy**. The specific applications live in the other Go skills:

**Axis 1 — correctness (don't fight Go; don't swallow failure):**
- `go-error-handling` — `%w` wrapping, `errors.Is`/`As`, sentinel vs typed, `errors.Join`, message strings. The depth behind Section 4.
- `go-defer-panic-recover` — `defer` mechanics, when `panic` is legitimate, `recover` only at a boundary.
- `go-concurrency-goroutines` / `go-context` — goroutine lifetime and cancellation; the leak in Section 7.
- `go-interfaces` — accept interfaces / return structs, and the typed-nil-error gotcha.
- `go-zero-values-and-construction` — useful zero values, constructors/options, no mutable globals.

**Axis 2 — restraint and design (don't over-build):**
- `go-interfaces` — interface pollution, consumer-side placement, small `-er` interfaces.
- `go-generics` — the "don't reach first" rule and when type params earn their place.
- `go-project-layout` — `internal/`, `cmd/`, no `util`/`common` grab-bags, keep `main` thin.
- `go-naming-and-style` — MixedCaps, initialisms, no `Get` getters, short receivers, line of sight, doc comments.

**Detection:**
- `go-tooling-and-static-analysis` — `gofmt`/`gofumpt`, `go vet`, staticcheck, golangci-lint, `govulncheck`: the CI gate that *detects* violations of the rules above (a discarded error or a copied lock is found by the toolchain, not by reading one file).
- `go-version-feature-map` — which idiom the module's `go` directive allows, so "idiomatic" means *current* idiomatic.

---

## 9. Reference Files

The high-frequency anti-patterns in LLM-generated Go, each with wrong/right code and citations, are in:

[references/common-mistakes.md](references/common-mistakes.md)

Source provenance for every claim in this skill:

[references/sources.yaml](references/sources.yaml)

