# Go Naming And Style

> Guides how Go reads to a human across two themes — identifier and structural naming (MixedCaps/mixedCaps never under_scores, initialisms kept as one case unit like URL/ID/HTTP, no Get prefix on getters, single-method interfaces get -er names, short scope-proportional variable and 1–2-letter receiver names that are never this/self, package names that are short lowercase nouns with no util/common grab-bags and no stutter, and the early-return line-of-sight shape) and exported doc comments (a full sentence beginning with the name being declared, one package comment, the Deprecated: convention, gofmt owns mechanical formatting). Auto-invokes when writing or editing identifier names, package names, receiver names, getters, initialisms like URL/ID, or exported doc comments — and on "is this named idiomatically", "rename this", or "clean up the naming". The name and its doc comment are the API a reader meets first.

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

---


# Go Naming and Style

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

> "The convention in Go is to use `MixedCaps` or `mixedCaps` rather than underscores to write multiword names."
> — [Effective Go](https://go.dev/doc/effective_go#mixed-caps)

> "Every exported (capitalized) name should have a doc comment."
> — [Go Doc Comments](https://go.dev/doc/comment)

A name is the smallest piece of API. Before anyone reads a function body they read its name, its receiver, its package qualifier, and its doc comment — and those four are what they will type at every call site. Go's naming rules are narrow and mechanical on purpose: they remove the choices so the reader never has to wonder whether it is `userId` or `userID`, `GetName` or `Name`, `util` or a real package. This skill owns those rules and the doc comments that ride alongside them. The deeper *correctness* policy (no `Get` getters as a Java tell, no `util` grab-bag) routes back to **`go-idiomatic-discipline`**.

---

## 1. The Rules at a Glance

| Rule | The discipline | Source |
|---|---|---|
| MixedCaps, never underscores | `userID`, `maxLength` — not `user_id`, `MAX_LENGTH` | "use `MixedCaps` or `mixedCaps` ... rather than underscores" ([Effective Go](https://go.dev/doc/effective_go#mixed-caps)) |
| Export by case | Capital = exported, lower = package-private | "The use of upper-case names for export provides the hook" ([Effective Go](https://go.dev/doc/effective_go#Getters)) |
| Initialisms keep one case | `URL`/`url`, `appID`, `ServeHTTP` — never `Url`, `appId` | "URL should appear as URL or url ... never as Url ... ServeHTTP not ServeHttp" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#initialisms)) |
| No `Get` on getters | Read `owner` with `Owner()`; set with `SetOwner` | "it's neither idiomatic nor necessary to put `Get` into the getter's name" ([Effective Go](https://go.dev/doc/effective_go#Getters)) |
| `-er` interface names | One-method interface → method + `-er` | "one-method interfaces are named by the method name plus an -er suffix" ([Effective Go](https://go.dev/doc/effective_go#interface-names)) |
| Scope-proportional vars | Short name for short life: `i`, `r`, `c` | "Prefer `c` to `lineCount`. Prefer `i` to `sliceIndex`" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#variable-names)) |
| Short, consistent receivers | 1–2 letters, same across all methods; never `this`/`self` | "one or two letters ... Don't use generic names such as 'me', 'this' or 'self'" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#receiver-names)) |
| Package = short lowercase noun | No plurals, no `util`/`common`; no stutter | "lower case, single-word names ... no need for underscores or mixedCaps" ([Effective Go](https://go.dev/doc/effective_go#package-names)) |
| Doc comment per exported name | Full sentence beginning with the name | "Every exported (capitalized) name should have a doc comment" ([Go Doc Comments](https://go.dev/doc/comment)) |
| `gofmt` owns formatting | Don't hand-align; run `gofmt`/`gofumpt` | "Gofmt's style is no one's favorite, yet gofmt is everyone's favorite" ([Proverbs](https://go-proverbs.github.io/)) |

---

## THEME A — NAMING

## 2. MixedCaps, and Initialisms as One Case Unit

Go writes multiword names with case, not underscores: "the convention in Go is to use `MixedCaps` or `mixedCaps` rather than underscores to write multiword names" ([Effective Go](https://go.dev/doc/effective_go#mixed-caps)). Capitalization is also the export switch — `MixedCaps` is exported, `mixedCaps` is package-private — so even a constant stays cased: "an unexported constant is `maxLength` not `MaxLength` or `MAX_LENGTH`" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#mixed-caps)).

The rule LLMs break most is **initialism casing**. An initialism is a single unit and keeps one case throughout: "Words in names that are initialisms or acronyms (e.g. 'URL' or 'NATO') have a consistent case. For example, 'URL' should appear as 'URL' or 'url' ... never as 'Url'. As an example: `ServeHTTP` not `ServeHttp`" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#initialisms)). The same applies to `ID`: "write 'appID' instead of 'appId'" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#initialisms)).

```go
// WRONG — initialism half-cased; reads like two words it isn't
type HttpClient struct{}
func parseUrl(s string) {}
var userId string
func (s *Server) ServeHttp(w http.ResponseWriter, r *http.Request) {}

// RIGHT — each initialism is one case unit
type HTTPClient struct{}
func parseURL(s string) {}
var userID string
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {}
```

When two initialisms collide, the whole run keeps one case: `xmlHTTPRequest` or `XMLHTTPRequest`, never `xmlHttpRequest` ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#initialisms)).

## 3. Variable and Receiver Names Are Proportional to Scope

A name's length should track how far it travels. "Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer `c` to `lineCount`. Prefer `i` to `sliceIndex`" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#variable-names)). The governing principle: "the further from its declaration that a name is used, the more descriptive the name must be" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#variable-names)). A two-line loop index is `i`; an exported package-level variable earns a full descriptive name.

Receivers are the extreme case — they live one method long. "The name of a method's receiver should be a reflection of its identity; often a one or two letter abbreviation of its type suffices (such as 'c' or 'cl' for 'Client'). Don't use generic names such as 'me', 'this' or 'self' ... In Go, the receiver of a method is just another parameter" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#receiver-names)). And it must be the *same* abbreviation on every method of the type: "Be consistent, too: if you call the receiver 'c' in one method, don't call it 'cl' in another" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#receiver-names)).

```go
// WRONG — OO-language receiver, and inconsistent across methods
func (this *Client) Get(id string) {}
func (self *Client) Put(id string) {}

// RIGHT — short, type-derived, identical on every method
func (c *Client) Get(id string) {}
func (c *Client) Put(id string) {}
```

## 4. Getters Have No `Get`; Setters Are `Set`; Interfaces End in `-er`

Go has no getter/setter convention to port from Java. "There's nothing wrong with providing getters and setters yourself ... but it's neither idiomatic nor necessary to put `Get` into the getter's name. If you have a field called `owner` (lower case, unexported), the getter method should be called `Owner` (upper case, exported), not `GetOwner`. ... A setter function, if needed, will likely be called `SetOwner`" ([Effective Go](https://go.dev/doc/effective_go#Getters)). The Google guide is identical: "use `Counts` over `GetCounts`" ([Google Decisions](https://google.github.io/styleguide/go/decisions#getters)). A `Get` prefix is allowed only when the domain itself says "get" (an HTTP GET, a cache `Get(key)` lookup that may miss).

Single-method interfaces are named for the method plus `-er`: "one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: `Reader`, `Writer`, `Formatter`, `CloseNotifier`" ([Effective Go](https://go.dev/doc/effective_go#interface-names)). And reuse the canonical method names with their canonical meaning: "call your string-converter method `String` not `ToString`" ([Effective Go](https://go.dev/doc/effective_go#interface-names)). Interface *design* (size, consumer-side placement) is owned by **`go-interfaces`**; the `-er` *name* lives here.

```go
// WRONG                                  // RIGHT
func (u *User) GetName() string           func (u *User) Name() string
type DataReadable interface{ Read() }     type Reader interface{ Read(p []byte) (int, error) }
func (p Point) ToString() string          func (p Point) String() string
```

## 5. Package Names: Short Lowercase Nouns, No Grab-Bags, No Stutter

The package name is part of every identifier it exports, so it is part of the API. "By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps" ([Effective Go](https://go.dev/doc/effective_go#package-names)). Because the caller always writes `pkg.Name`, the name should not repeat the package: "the buffered reader type in the `bufio` package is called `Reader`, not `BufReader`, because users see it as `bufio.Reader`" ([Effective Go](https://go.dev/doc/effective_go#package-names)). That repetition is **stutter** — `http.HTTPServer` should be `http.Server`, `strings.StringReader` should be `strings.Reader`.

The other failure is the meaningless catch-all. "Avoid meaningless package names. Packages named `util`, `common`, or `misc` provide clients with no sense of what the package contains ... Over time, they accumulate dependencies ... and ... are more likely to collide with other packages" ([Go Blog — Package Names](https://go.dev/blog/package-names)). The Google guide lists the same ban: "Avoid uninformative package names like `util`, `utility`, `common`, `helper`, `model`, `testhelper`" ([Google Decisions](https://google.github.io/styleguide/go/decisions#package-names)). Name a package for what it *does* (`tokenize`, `ratelimit`), not for the fact that it holds code. This skill owns the package *name*; **`go-project-layout`** owns the package *boundary* and the structural cure for a `util` grab-bag.

```go
// WRONG                              // RIGHT
package utils                         package tokenize
package HTTPServer                    package httpserver  // or just `server`
type HTTPServer struct{}              type Server struct{} // caller writes httpserver.Server
```

## 6. Line of Sight: Handle the Error, Return, Keep the Happy Path Flat

A readable function reads top-to-bottom down the left edge. "Try to keep the normal code path at a minimal indentation, and indent the error handling, dealing with it first. This improves the readability of the code by permitting visually scanning the normal path quickly" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#indent-error-flow)). The Uber guide states the same shape: "Code should reduce nesting where possible by handling error cases/special conditions first and returning early or continuing the loop" ([Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md#reduce-nesting)).

```go
// WRONG — the success path is buried two levels deep in else-branches
func load(p string) (*Cfg, error) {
	if data, err := os.ReadFile(p); err == nil {
		if cfg, err := parse(data); err == nil {
			return cfg, nil
		} else {
			return nil, err
		}
	} else {
		return nil, err
	}
}

// RIGHT — guard, return early, leave the happy path at the left margin
func load(p string) (*Cfg, error) {
	data, err := os.ReadFile(p)
	if err != nil {
		return nil, err
	}
	cfg, err := parse(data)
	if err != nil {
		return nil, err
	}
	return cfg, nil
}
```

This is the readability rule; the *error-flow* depth (wrapping with `%w`, sentinel vs typed) is owned by **`go-error-handling`**.

---

## THEME B — DOC COMMENTS

## 7. Every Exported Name Gets a Doc Comment That Starts With Its Name

A doc comment is the comment "immediately before top-level package, const, func, type, and var declarations with no intervening newlines" ([Go Doc Comments](https://go.dev/doc/comment)). Two rules govern it. First, coverage: "Every exported (capitalized) name should have a doc comment" ([Go Doc Comments](https://go.dev/doc/comment)); "All top-level, exported names should have doc comments, as should non-trivial unexported type or function declarations" ([CodeReviewComments](https://go.dev/wiki/CodeReviewComments#doc-comments)). Second, form: it is a full sentence that **begins with the name being declared**. "These comments should be full sentences that begin with the name of the object being described" ([Google Decisions](https://google.github.io/styleguide/go/decisions#doc-comments)). Starting with the name is what makes `go doc` and pkg.go.dev read correctly, because the rendered entry is the comment.

A doc comment explains *what* the thing is and *why* it exists — it does not echo the code.

```go
// WRONG — doesn't start with the name; restates the signature
// this function takes two ints and returns the sum
func Add(a, b int) int { return a + b }

// RIGHT — full sentence, begins with the name, says something the signature doesn't
// Add returns the sum of a and b. It does not check for integer overflow.
func Add(a, b int) int { return a + b }

// RIGHT — type doc comment, begins with the type name
// Endpoint is the target of a proxied request. Its zero value is not usable;
// construct one with NewEndpoint.
type Endpoint struct{ /* ... */ }
```

## 8. The Package Comment, and the `Deprecated:` Convention

Every package gets one introductory comment. "Every package should have a package comment introducing the package" and "the first sentence begins with 'Package '" ([Go Doc Comments](https://go.dev/doc/comment)). In a multi-file package it goes in exactly one file: "For multi-file packages, the package comment should only be in one source file. If multiple files have package comments, they are concatenated" ([Go Doc Comments](https://go.dev/doc/comment)).

To deprecate an API without breaking callers, keep it and add a paragraph: "Paragraphs starting with `Deprecated:` are treated as deprecation notices" ([Go Doc Comments](https://go.dev/doc/comment)). Tooling (`gopls`, `staticcheck` SA1019) then warns at call sites.

```go
// Package proxy forwards HTTP requests to a configured upstream URL.
package proxy

// OldParse parses s into a Config.
//
// Deprecated: OldParse does not report syntax errors. Use Parse instead.
func OldParse(s string) Config { /* ... */ }
```

## 9. `gofmt` Owns Mechanical Formatting — Don't Hand-Align

Formatting is not a judgment call. `gofmt` (and the stricter `gofumpt`) reformats code *and* doc comments to a canonical form: "The standard formatter gofmt reformats doc comments to use a canonical formatting" — bullet lists to dashes, code blocks to a tab indent, and "Gofmt preserves line breaks in paragraph text: it does not rewrap the text" ([Go Doc Comments](https://go.dev/doc/comment)). So do not hand-align struct fields, columns, or comment markup; write the content and let the tool format it. "Gofmt's style is no one's favorite, yet gofmt is everyone's favorite" ([Go Proverbs](https://go-proverbs.github.io/)). Wiring `gofmt` into CI is owned by **`go-tooling-and-static-analysis`**.

---

## 10. Who Suffers When Naming Is Done Badly

The cost of a bad name is paid at every call site, by everyone but the author:

- The **reviewer** who can't grep `userID` because half the codebase wrote `userId` and the other half `UserID` — the inconsistency hides the field instead of naming it.
- The **caller** who imports a `util` package and gets no idea what is inside, then watches their build slow as the grab-bag drags in transitive dependencies it never needed ([Go Blog — Package Names](https://go.dev/blog/package-names)).
- The **new teammate** reading `obj.GetData().GetUser().GetName()` who has to confirm these are plain field reads, not expensive calls — the `Get` noise that idiomatic Go drops.
- The **doc reader** on pkg.go.dev who sees a blank entry next to an exported function, or a comment that just restates the signature, and has to open the source to learn what it does.

"Clear is better than clever" ([Go Proverbs](https://go-proverbs.github.io/)) is an empathy rule: the name and its doc comment are what the next person meets first, before any logic.

---

## 11. Routing to the Specific Skills

- **`go-idiomatic-discipline`** — the policy root. `Get`-getters and `util`-packages are its *axis 1* (fighting Go); this skill holds the naming depth.
- **`go-project-layout`** — owns package *boundaries* and the structural fix for a `util`/`common` grab-bag. This skill owns the package *name* rule; layout owns where the code lives.
- **`go-interfaces`** — owns interface *design* (small, consumer-side). This skill owns the `-er` *naming* convention.
- **`go-error-handling`** — owns the error-flow depth behind line-of-sight (Section 6), and error-string conventions.
- **`go-zero-values-and-construction`** — owns constructor/option naming (`New`, `SetX` options) and constant/`iota` value modeling.
- **`go-tooling-and-static-analysis`** — owns `gofmt`/`gofumpt` and the naming linters (`revive`, `staticcheck`, `go vet`) that detect violations of the rules above in CI.

---

## 12. Reference Files

High-frequency naming and doc-comment anti-patterns in LLM-generated Go, each with wrong/right code and citations:

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

Source provenance for every claim in this skill:

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

