| name | go-naming-and-style |
| description | 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. |
| allowed-tools | Read, Glob, Grep |
| compatibility | Claude Code, Codex CLI, Gemini CLI |
Go Naming and Style
"Clear is better than clever."
— Go Proverbs
"The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names."
— Effective Go
"Every exported (capitalized) name should have a doc comment."
— Go Doc Comments
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) |
| Export by case | Capital = exported, lower = package-private | "The use of upper-case names for export provides the hook" (Effective Go) |
| 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) |
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) |
-er interface names | One-method interface → method + -er | "one-method interfaces are named by the method name plus an -er suffix" (Effective Go) |
| Scope-proportional vars | Short name for short life: i, r, c | "Prefer c to lineCount. Prefer i to sliceIndex" (CodeReviewComments) |
| 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) |
| Package = short lowercase noun | No plurals, no util/common; no stutter | "lower case, single-word names ... no need for underscores or mixedCaps" (Effective Go) |
| Doc comment per exported name | Full sentence beginning with the name | "Every exported (capitalized) name should have a doc comment" (Go Doc Comments) |
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) |
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). 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).
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). The same applies to ID: "write 'appID' instead of 'appId'" (CodeReviewComments).
type HttpClient struct{}
func parseUrl(s string) {}
var userId string
func (s *Server) ServeHttp(w http.ResponseWriter, r *http.Request) {}
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).
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). The governing principle: "the further from its declaration that a name is used, the more descriptive the name must be" (CodeReviewComments). 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). 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).
func (this *Client) Get(id string) {}
func (self *Client) Put(id string) {}
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). The Google guide is identical: "use Counts over GetCounts" (Google Decisions). 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). And reuse the canonical method names with their canonical meaning: "call your string-converter method String not ToString" (Effective Go). Interface design (size, consumer-side placement) is owned by go-interfaces; the -er name lives here.
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). 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). 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). The Google guide lists the same ban: "Avoid uninformative package names like util, utility, common, helper, model, testhelper" (Google Decisions). 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.
package utils package tokenize
package HTTPServer package httpserver
type HTTPServer struct{} type Server struct{}
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). 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).
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
}
}
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). Two rules govern it. First, coverage: "Every exported (capitalized) name should have a doc comment" (Go Doc Comments); "All top-level, exported names should have doc comments, as should non-trivial unexported type or function declarations" (CodeReviewComments). 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). 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.
func Add(a, b int) int { return a + b }
func Add(a, b int) int { return a + b }
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). 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).
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). Tooling (gopls, staticcheck SA1019) then warns at call sites.
package proxy
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). 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). 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).
- 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) 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:
${CLAUDE_SKILL_DIR}/references/common-mistakes.md
Source provenance for every claim in this skill:
${CLAUDE_SKILL_DIR}/references/sources.yaml