
Golang Naming
- 34.3k installs
- 2.8k repo stars
- Updated July 27, 2026
- samber/cc-skills-golang
golang-naming is an agent skill for Go package, type, error, boolean, constructor, and test naming conventions using MixedCaps.
About
The golang-naming skill v1.1.1 from samber cc-skills-golang codifies Go identifier conventions where capitalization controls export and MixedCaps replaces underscores. It triggers when writing or reviewing Go code, choosing between New versus NewTypeName constructors, ErrNotFound versus NotFoundError, boolean isConnected versus connected fields, or debating utils package anti-patterns. Quick reference tables cover packages, files, interfaces with er suffixes, Err-prefixed error variables, Error-suffixed types, With-prefixed option funcs, and iota enums with unknown zero values. MixedCaps rules forbid MAX_PACKET_SIZE, snake_case, and Hungarian notation because Go tooling assumes capitalization-based exports. Avoid stuttering guidance explains why http.HTTPClient wastes reader time. Sections dive into package naming, constructors, receivers, getters without Get prefixes on exported methods, acronym casing like URL and HTTPServer, and test table got versus expected fields. Community company skills that explicitly supersede this entry take precedence.
- Quick reference tables for packages, exports, interfaces, errors, booleans, constructors, and functional options.
- Enforces MixedCaps identifiers and forbids underscores except test subcases and interop exceptions.
- Covers Err prefix variables, Error suffix types, and lowercase unpunctuated error strings.
- Documents avoid-stuttering rules so identifiers do not repeat package or type names.
- Triggers on naming debates: New vs NewTypeName, isConnected vs connected, and utils anti-patterns.
Golang Naming by the numbers
- 34,298 all-time installs (skills.sh)
- +586 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #3 of 99 Go skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
golang-naming capabilities & compatibility
- Capabilities
- package and file naming guidance · error and boolean naming patterns · constructor and functional option naming · acronym and receiver conventions · test and subtest naming rules
- Use cases
- code review · refactoring
What golang-naming says it does
All identifiers MUST use MixedCaps, NEVER underscores.
Clear is better than clever.
A name MUST NOT repeat information already present in the package name, type name, or surrounding context.
npx skills add https://github.com/samber/cc-skills-golang --skill golang-namingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 34.3k |
|---|---|
| repo stars | ★ 2.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | samber/cc-skills-golang ↗ |
What is the idiomatic Go name for this package, type, error, boolean field, or constructor?
Apply Go naming conventions for packages, types, errors, booleans, constructors, acronyms, tests, and functional options.
Who is it for?
Go developers writing or reviewing code where naming choices affect API clarity and tooling compatibility.
Skip if: Skip for general Go implementation questions that do not involve naming decisions.
When should I use this skill?
Choosing package names, error types, boolean prefixes, acronym casing, or test function names in Go.
What you get
Consistent Go identifiers that follow export rules, avoid stuttering, and match community conventions.
- Idiomatic Go identifier choices
- Naming review feedback
By the numbers
- Metadata version 1.1.1 from samber cc-skills-golang.
- Quick reference table spans fifteen identifier element types.
Files
Community default. A company skill that explicitly supersedes samber/cc-skills-golang@golang-naming skill takes precedence.Go Naming Conventions
Go favors short, readable names. Capitalization controls visibility — uppercase is exported, lowercase is unexported. All identifiers MUST use MixedCaps, NEVER underscores.
"Clear is better than clever." — Go Proverbs
>
"Design the architecture, name the components, document the details." — Go Proverbs
To ignore a rule, just add a comment to the code.
Quick Reference
| Element | Convention | Example |
|---|---|---|
| Package | lowercase, single word, \_test suffix OK for test files | json, http, tabwriter, http_test |
| File | lowercase, underscores OK | user_handler.go |
| Exported name | UpperCamelCase | ReadAll, HTTPClient |
| Unexported | lowerCamelCase | parseToken, userCount |
| Interface | method name + -er | Reader, Closer, Stringer |
| Struct | MixedCaps noun | Request, FileHeader |
| Constant | MixedCaps (not ALL_CAPS) | MaxRetries, defaultTimeout |
| Receiver | 1-2 letter abbreviation | func (s *Server), func (b *Buffer) |
| Error variable | Err prefix | ErrNotFound, ErrTimeout |
| Error type | Error suffix | PathError, SyntaxError |
| Constructor | New (single type) or NewTypeName (multi-type) | ring.New, http.NewRequest |
| Boolean field | is, has, can prefix on fields and methods | isReady, IsConnected() |
| Test function | Test + function name | TestParseToken |
| Acronym | all caps or all lower | URL, HTTPServer, xmlParser |
| Variant: context | WithContext suffix | FetchWithContext, QueryContext |
| Variant: in-place | In suffix | SortIn(), ReverseIn() |
| Variant: error | Must prefix | MustParse(), MustLoadConfig() |
| Option func | With + field name | WithPort(), WithLogger() |
| Enum (iota) | type name prefix, zero-value = unknown | StatusUnknown at 0, StatusReady |
| Named return | descriptive, for docs only | (n int, err error) |
| Error string | lowercase (incl. acronyms), no punctuation | "image: unknown format", "invalid id" |
| Import alias | short, only on collision | mrand "math/rand", pb "app/proto" |
| Format func | f suffix | Errorf, Wrapf, Logf |
| Test table fields | got/expected prefixes | input string, expected int |
MixedCaps
All Go identifiers MUST use MixedCaps (or mixedCaps). NEVER use underscores in identifiers — the only exceptions are test function subcases (TestFoo_InvalidInput), generated code, and OS/cgo interop. This is load-bearing, not cosmetic — Go's export mechanism relies on capitalization, and tooling assumes MixedCaps throughout.
// ✓ Good
MaxPacketSize
userCount
parseHTTPResponse
// ✗ Bad — these conventions conflict with Go's export mechanism and tooling expectations
MAX_PACKET_SIZE // C/Python style
max_packet_size // snake_case
kMaxBufferSize // Hungarian notationAvoid Stuttering
Go call sites always include the package name, so repeating it in the identifier wastes the reader's time — http.HTTPClient forces parsing "HTTP" twice. A name MUST NOT repeat information already present in the package name, type name, or surrounding context.
// Good — clean at the call site
http.Client // not http.HTTPClient
json.Decoder // not json.JSONDecoder
user.New() // not user.NewUser()
config.Parse() // not config.ParseConfig()
// In package sqldb:
type Connection struct{} // not DBConnection — "db" is already in the package name
// Anti-stutter applies to ALL exported types, not just the primary struct:
// In package dbpool:
type Pool struct{} // not DBPool
type Status struct{} // not PoolStatus — callers write dbpool.Status
type Option func(*Pool) // not PoolOptionFrequently Missed Conventions
These conventions are correct but non-obvious — they are the most common source of naming mistakes:
Constructor naming: When a package exports a single primary type, the constructor is New(), not NewTypeName(). This avoids stuttering — callers write apiclient.New() not apiclient.NewClient(). Use NewTypeName() only when a package has multiple constructible types (like http.NewRequest, http.NewServeMux).
Boolean struct fields: Unexported boolean fields MUST use is/has/can prefix — isConnected, hasPermission, not bare connected or permission. The exported getter keeps the prefix: IsConnected() bool. This reads naturally as a question and distinguishes booleans from other types.
Error strings are fully lowercase — including acronyms. Write "invalid message id" not "invalid message ID", because error strings are often concatenated with other context (fmt.Errorf("parsing token: %w", err)) and mixed case looks wrong mid-sentence. Sentinel errors should include the package name as prefix: errors.New("apiclient: not found").
Enum zero values: Always place an explicit Unknown/Invalid sentinel at iota position 0. A var s Status silently becomes 0 — if that maps to a real state like StatusReady, code can behave as if a status was deliberately chosen when it wasn't.
Subtest names: Table-driven test case names in t.Run() should be fully lowercase descriptive phrases: "valid id", "empty input" — not "valid ID" or "Valid Input".
Detailed Categories
For complete rules, examples, and rationale, see:
- [Packages, Files & Import Aliasing](./references/packages-files.md) — Package naming (single word, lowercase, no plurals), file naming conventions, import alias patterns (only use on collision to avoid cognitive load), and directory structure.
- [Variables, Booleans, Receivers & Acronyms](./references/identifiers.md) — Scope-based naming (length matches scope:
ifor 3-line loops, longer names for package-level), single-letter receiver conventions (sfor Server), acronym casing (URL not Url, HTTPServer not HttpServer), and boolean naming patterns (isReady, hasPrefix).
- [Functions, Methods & Options](./references/functions-methods.md) — Getter/setter patterns (Go omits
Getsouser.Name()reads naturally), constructor conventions (NeworNewTypeName), named returns (for documentation only), format function suffixes (Errorf,Wrapf), and functional options (WithPort,WithLogger).
- [Types, Constants & Errors](./references/types-errors.md) — Interface naming (
Reader,Closersuffix with-er), struct naming (nouns, MixedCaps), constants (MixedCaps, not ALL_CAPS), enums (type name prefix likeStatusReady), sentinel errors (ErrNotFoundvariables), error types (PathErrorsuffix), and error message conventions (lowercase, no punctuation).
- [Test Naming](./references/testing.md) — Test function naming (
TestFunctionName), table-driven test field conventions (input,expected), test helper naming, and subcase naming patterns.
Common Mistakes
| Mistake | Fix |
|---|---|
ALL_CAPS constants | Go reserves casing for visibility, not emphasis — use MixedCaps (MaxRetries) |
GetName() getter | Go omits Get because user.Name() reads naturally at call sites. But Is/Has/Can prefixes are kept for boolean predicates: IsHealthy() bool not Healthy() bool |
Url, Http, Json acronyms | Mixed-case acronyms create ambiguity (HttpsUrl — is it Https+Url?). Use all caps or all lower |
this or self receiver | Go methods are called frequently — use 1-2 letter abbreviation (s for Server) to reduce visual noise |
util, helper packages | These names say nothing about content — use specific names that describe the abstraction |
http.HTTPClient stuttering | Package name is always present at call site — http.Client avoids reading "HTTP" twice |
user.NewUser() constructor | Single primary type uses New() — user.New() avoids repeating the type name |
connected bool field | Bare adjective is ambiguous — use isConnected so the field reads as a true/false question |
"invalid message ID" error | Error strings must be fully lowercase including acronyms — "invalid message id" |
StatusReady at iota 0 | Zero value should be a sentinel — StatusUnknown at 0 catches uninitialized values |
"not found" error string | Sentinel errors should include the package name — "mypackage: not found" identifies the origin |
userSlice type-in-name | Types encode implementation detail — users describes what it holds, not how |
| Inconsistent receiver names | Switching names across methods of the same type confuses readers — use one name consistently |
snake_case identifiers | Underscores conflict with Go's MixedCaps convention and tooling expectations — use mixedCaps |
| Long names for short scopes | Name length should match scope — i is fine for a 3-line loop, userIndex is noise |
| Naming constants by value | Values change, roles don't — DefaultPort survives a port change, Port8080 doesn't |
FetchCtx() context variant | WithContext is the standard Go suffix — FetchWithContext() is instantly recognizable |
sort() in-place but no In | Readers assume functions return new values. SortIn() signals mutation |
parse() panicking on error | MustParse() warns callers that failure panics — surprises belong in the name |
Mixing With*, Set*, Use* | Consistency across the codebase — With* is the Go convention for functional options |
| Plural package names | Go convention is singular (net/url not net/urls) — keeps import paths consistent |
Wrapf without f suffix | The f suffix signals format-string semantics — Wrapf, Errorf tell callers to pass format args |
| Unnecessary import aliases | Aliases add cognitive load. Only alias on collision — mrand "math/rand" |
| Inconsistent concept names | Using user/account/person for the same concept forces readers to track synonyms — pick one name |
Enforce with Linters
Many naming convention issues are caught automatically by linters: revive, predeclared, misspell, errname. See samber/cc-skills-golang@golang-lint skill for configuration and usage.
Cross-References
- → See
samber/cc-skills-golang@golang-code-styleskill for broader formatting and style decisions - → See
samber/cc-skills-golang@golang-structs-interfacesskill for interface naming depth and receiver design - → See
samber/cc-skills-golang@golang-lintskill for automated enforcement (revive, predeclared, misspell, errname)
[
{
"id": 1,
"name": "new-constructor-naming",
"description": "New() constructor for single primary type; error strings with package prefix; bool field is-prefix",
"prompt": "Create a Go package called `apiclient` that provides an HTTP client for a REST API. The package exports a single primary type — the client struct — along with functional options, sentinel errors, and a custom error type. Include:\n\n1. The client struct with base URL, timeout, http.Client, and a boolean tracking connection state\n2. A constructor accepting a DSN-like connection string and functional options\n3. Sentinel errors for 'not found' and 'unauthorized'\n4. A custom error type wrapping the HTTP status code\n5. A method to fetch a resource by ID\n\nWrite `apiclient.go` with proper Go naming throughout.",
"trap": "Model names the constructor NewClient() because the package name is apiclient and 'client' is prominent — anti-stutter means the primary type should use New(). Also likely to write 'not found' error strings without 'apiclient:' prefix, and use bare `connected bool` instead of `isConnected bool`.",
"assertions": [
{
"id": "1.1",
"text": "Constructor is named New() not NewClient() or NewAPIClient() — the package is already called apiclient, so New() is unambiguous and avoids reading 'client' twice at the call site (apiclient.New())"
},
{
"id": "1.2",
"text": "Sentinel error strings include the package name as prefix (e.g., 'apiclient: not found', 'apiclient: unauthorized') — bare strings like 'not found' lose origin when wrapped with fmt.Errorf"
},
{
"id": "1.3",
"text": "The unexported boolean field uses an is/has prefix (isConnected, hasConnection) not a bare adjective (connected) — the is prefix makes it readable as a question"
},
{
"id": "1.4",
"text": "Acronyms in exported identifiers are all-caps (URL not Url, HTTP not Http, ID not Id) — e.g., FetchByID() not FetchById(), BaseURL not BaseUrl"
},
{
"id": "1.5",
"text": "The custom error type uses the Error suffix (e.g., APIError, ResponseError) and NOT a prefix like ErrAPIResponse"
}
]
},
{
"id": 2,
"name": "must-prefix-enum-iota",
"description": "New() constructor, Status not PoolStatus (anti-stutter), IsHealthy() bool, type-prefixed enums",
"prompt": "I'm building a Go package called `dbpool` for managing database connection pools. Please write the code for `pool.go` with:\n\n1. A ConnectionState enum with values: idle, active, closed, errored\n2. An interface that any database driver must implement, with methods for Connect, Ping, Close, and a method that returns whether the connection is healthy\n3. A Pool struct with fields for maximum connections, current count, and the database DSN (data source name)\n4. A constructor that accepts a DSN string and functional options\n5. Methods to get a connection from the pool and return it\n6. A method that returns the pool's current status as a JSON-serializable struct\n7. Error variables for pool exhausted, connection failed, and invalid DSN\n8. A helper that must successfully parse the DSN or panic\n9. Constants for the default pool size (10) and the max idle timeout (5 minutes)\n\nMake sure to follow Go naming best practices.",
"trap": "Model uses NewPool() instead of New(), PoolStatus (stutter), bare Healthy() bool without Is prefix, Idle/Active enum values without type prefix, or bare error strings",
"assertions": [
{
"id": "2.1",
"text": "Constructor is named New() not NewPool(), because Pool is the single primary type in the dbpool package — callers write dbpool.New()"
},
{
"id": "2.2",
"text": "The JSON-serializable status struct is named Status (not PoolStatus) since the package is already dbpool — avoids stuttering at call site (dbpool.Status not dbpool.PoolStatus)"
},
{
"id": "2.3",
"text": "The health-check method on the interface uses IsHealthy() bool with Is prefix — not bare Healthy() bool. The Is prefix signals a boolean predicate (same pattern as reflect.Type.IsVariadic, net.IP.IsLoopback)"
},
{
"id": "2.4",
"text": "The panic-on-error DSN parser uses the Must prefix (MustParseDSN) to signal that failure panics, not just ParseDSN or ParseDSNOrDie"
},
{
"id": "2.5",
"text": "Enum values are prefixed with the type name (e.g., ConnectionStateIdle, ConnectionStateActive) rather than bare Idle/Active, and include a zero-value sentinel (Unknown or similar at iota position 0)"
},
{
"id": "2.6",
"text": "Sentinel error strings include the package name as prefix (e.g., 'dbpool: pool exhausted'), not bare strings like 'pool exhausted'"
}
]
},
{
"id": 3,
"name": "error-string-lowercase-acronyms",
"description": "Error strings and test subtest names must be fully lowercase including acronyms like ID, URL, HTTP",
"prompt": "Write a Go file `handler.go` in package `webhook` and its test file `handler_test.go`. The webhook handler should:\n\n1. A Handler struct with a secret key (for HMAC validation), a base URL for callbacks, and a boolean tracking if it's processing\n2. A method to validate an incoming webhook payload — it must verify the HMAC signature and the source URL\n3. Sentinel errors for: invalid HMAC signature, invalid source URL, missing request ID\n4. A Processor interface with a single method Process(ctx context.Context, payload []byte) error\n\nFor the test file, write table-driven tests for the validate method covering: valid request, invalid HMAC, invalid URL, missing request ID, empty payload. Use proper Go test naming conventions.",
"trap": "Model capitalizes acronyms in error strings ('invalid source URL', 'missing request ID') and in subtest names ('invalid URL', 'missing request ID') — but Go's convention requires fully lowercase error strings and subtest names, including acronyms. Also likely to write bare boolean field (processing) without is prefix.",
"assertions": [
{
"id": "3.1",
"text": "Error strings are fully lowercase including acronyms — 'invalid source url' not 'invalid source URL', 'missing request id' not 'missing request ID'. Acronyms that are capitalized in identifiers are lowercase in error strings"
},
{
"id": "3.2",
"text": "Subtest names passed to t.Run() are fully lowercase — 'invalid url' not 'invalid URL', 'missing request id' not 'missing request ID'. The same lowercase-for-acronyms rule applies to t.Run() subtest names"
},
{
"id": "3.3",
"text": "The unexported boolean field uses an is prefix (isProcessing) rather than a bare adjective (processing), and the exported method uses IsProcessing()"
},
{
"id": "3.4",
"text": "All Handler methods use the same 1-2 letter receiver name consistently (e.g., 'h') rather than mixing names or using 'self'/'this'"
},
{
"id": "3.5",
"text": "Sentinel errors use Err prefix (ErrInvalidSignature, ErrInvalidSourceURL, ErrMissingRequestID) matching the Go convention — note the capitalized acronyms in error variable names contrast with lowercase in error string values"
}
]
},
{
"id": 4,
"name": "package-naming-anti-patterns",
"description": "Tests that generic package names (util, helper, common, base) are avoided and packages use singular, lowercase, single-word names",
"prompt": "I'm refactoring a Go project and need to organize some shared code. I have:\n\n1. A function that validates email addresses\n2. A function that hashes passwords with bcrypt\n3. A function that generates random UUIDs\n4. A function that formats timestamps for display\n5. A function that converts between different currency amounts\n6. Helper functions for reading/writing JSON files\n\nCurrently all of these live in a single package called `utils`. Suggest how to reorganize them into proper Go packages with appropriate names. Write the package declarations and function signatures (no implementations needed).",
"trap": "Model keeps the utils package or creates similarly generic names like helpers, common, shared, or base. May also use plural package names or MixedCaps/underscores in package names.",
"assertions": [
{
"id": "4.1",
"text": "Does NOT use generic package names like util, utils, helper, helpers, common, shared, base, or model — each package has a specific, purposeful name"
},
{
"id": "4.2",
"text": "Package names are singular, not plural (e.g., 'currency' not 'currencies', 'email' not 'emails')"
},
{
"id": "4.3",
"text": "Package names are lowercase single words with no underscores or MixedCaps (e.g., 'email' not 'email_validator' or 'emailValidator')"
},
{
"id": "4.4",
"text": "Functions do NOT stutter with their package name (e.g., email.Validate not email.ValidateEmail, currency.Convert not currency.ConvertCurrency)"
},
{
"id": "4.5",
"text": "The JSON file I/O functions are placed in a specific package (e.g., jsonfile, storage) not left in a generic helpers package"
}
]
},
{
"id": 5,
"name": "scope-based-variable-naming",
"description": "Tests that variable name length is proportional to scope — short names for tiny scopes, longer names for package-level",
"prompt": "Review and improve the naming in this Go function. The code works correctly but the naming may need adjustment:\n\n```go\npackage analytics\n\nvar t = &http.Transport{MaxIdleConns: 100}\n\nfunc ProcessUserEvents(eventList []Event) map[string]int {\n resultMap := make(map[string]int)\n for userIndex, currentEvent := range eventList {\n if currentEvent.IsValid() {\n temporaryKey := currentEvent.Type\n resultMap[temporaryKey]++\n _ = userIndex\n }\n }\n return resultMap\n}\n```\n\nFix the variable naming to follow Go conventions. Explain each change.",
"trap": "Model may not recognize the inverted naming problem: the package-level variable `t` is too short (should be descriptive) while the loop variables `userIndex`, `currentEvent`, `temporaryKey`, `resultMap`, `eventList` are too long for their scope.",
"assertions": [
{
"id": "5.1",
"text": "Renames the package-level variable `t` to something more descriptive like `defaultTransport` or `defaultHTTPTransport` — single-letter names are too cryptic at package scope"
},
{
"id": "5.2",
"text": "Shortens the loop variable `currentEvent` to something like `e` or `ev` — the 3-line loop scope makes a long name unnecessary noise"
},
{
"id": "5.3",
"text": "Shortens the loop index `userIndex` to `i` or similar — loop indices in small scopes should be single letters"
},
{
"id": "5.4",
"text": "Renames `resultMap` and/or `eventList` to shorter names that don't encode the type (e.g., `counts` instead of `resultMap`, `events` instead of `eventList`)"
},
{
"id": "5.5",
"text": "Explains the principle: name length should be proportional to scope size — short names for small scopes, descriptive names for package-level variables"
}
]
},
{
"id": 6,
"name": "avoid-type-in-variable-name",
"description": "Tests that variable names describe what a value represents, not its Go type",
"prompt": "I have these variable declarations in a Go function. Are the names idiomatic?\n\n```go\nuserSlice := fetchUsers()\ncountInt := len(userSlice)\nnameString := user.FullName()\nerrorValue := validate(input)\nchannelChan := make(chan Event, 10)\ncontextCtx := context.Background()\nresultBool := isValid(token)\ntimeoutDuration := 30 * time.Second\n```\n\nSuggest better names for each.",
"trap": "Model may fix only the most obvious ones (like countInt) but miss that ALL type-encoded names should be changed, including channelChan, contextCtx, resultBool, timeoutDuration.",
"assertions": [
{
"id": "6.1",
"text": "Renames `userSlice` to `users` — the name should describe what the value holds, not the Go type"
},
{
"id": "6.2",
"text": "Renames `countInt` to `count` or `n` — dropping the type suffix"
},
{
"id": "6.3",
"text": "Renames `channelChan` to `events` or similar — not encoding the channel type in the name"
},
{
"id": "6.4",
"text": "Renames `contextCtx` to `ctx` — the standard Go convention for context.Context"
},
{
"id": "6.5",
"text": "Renames `timeoutDuration` to `timeout` — Duration is the type, not the purpose"
},
{
"id": "6.6",
"text": "Renames `resultBool` to something like `valid` or `ok` — removing the type suffix"
}
]
},
{
"id": 7,
"name": "interface-naming-multi-method-noun",
"description": "Multi-method interfaces use a descriptive noun, not a compound -er; canonical method names must not be invented",
"prompt": "I'm designing a Go storage abstraction for a key-value store. A teammate has drafted these interface definitions:\n\n```go\ntype KeyGetter interface {\n GetKey(key string) ([]byte, error)\n}\n\ntype KeySetter interface {\n SetKey(key string, value []byte) error\n}\n\ntype KeyDeleter interface {\n DeleteKey(key string) error\n}\n\ntype GetterSetter interface {\n KeyGetter\n KeySetter\n}\n\ntype FullStorer interface {\n KeyGetter\n KeySetter\n KeyDeleter\n Stringify() string\n Release() error\n}\n```\n\nReview the interface and method naming. What problems do you see, and how would you fix them?",
"trap": "Model may accept GetterSetter as a reasonable compound name, keep Stringify() and Release() without flagging them as non-canonical, or not recognize that FullStorer should be a noun. The skill teaches: multi-method interfaces use descriptive nouns (Store, not FullStorer or GetterSetter), canonical method names (String() not Stringify(), Close() not Release()), and single-method -er interfaces should embed rather than compound.",
"assertions": [
{
"id": "7.1",
"text": "Flags GetterSetter as a poor compound -er name for a multi-method interface and suggests a descriptive noun (e.g., ReadWriter, or more specifically, a noun like Store)"
},
{
"id": "7.2",
"text": "Flags Stringify() as non-canonical — the correct method name is String() returning string to satisfy fmt.Stringer"
},
{
"id": "7.3",
"text": "Flags Release() as non-canonical — the correct method name is Close() returning error to satisfy io.Closer"
},
{
"id": "7.4",
"text": "Suggests renaming FullStorer to a descriptive noun (e.g., Store) rather than a compound -er — multi-method interfaces use nouns, not method-name-plus-er patterns"
},
{
"id": "7.5",
"text": "Flags GetKey/SetKey/DeleteKey method names as stuttering (key is already in the interface context) and suggests Get/Set/Delete to avoid repeating the concept"
}
]
},
{
"id": 8,
"name": "enum-zero-value-protection",
"description": "Enum zero value must be an Unknown/Invalid sentinel to catch uninitialized variables; type-prefixed enum values",
"prompt": "I'm writing a task scheduler in Go. Define the enum types for task state and priority. Here's my first attempt:\n\n```go\ntype TaskState int\n\nconst (\n TaskStatePending TaskState = iota\n TaskStateRunning\n TaskStateDone\n TaskStateFailed\n)\n\ntype Priority int\n\nconst (\n PriorityLow Priority = iota + 1\n PriorityMedium\n PriorityHigh\n PriorityCritical\n)\n```\n\nIs this correct? Are there any naming or design issues?",
"trap": "TaskStatePending at iota 0 looks reasonable — Pending seems like a natural initial state. But the skill teaches that zero value must be an Unknown/Invalid sentinel to catch uninitialized variables. A var t Task will silently have TaskStatePending, making it look like a task was deliberately queued when it wasn't. Priority uses iota+1 to skip zero, which is one valid approach — the model should recognize this as intentional zero-value protection.",
"assertions": [
{
"id": "8.1",
"text": "Flags TaskStatePending at iota 0 as a bug: a zero-value TaskState will silently appear as Pending, making uninitialized task structs look like they have been queued"
},
{
"id": "8.2",
"text": "Suggests placing an explicit Unknown/Invalid sentinel at iota 0 (e.g., TaskStateUnknown or TaskStateInvalid) so that uninitialized values are detectable"
},
{
"id": "8.3",
"text": "Recognizes that Priority using iota+1 (skipping zero) is a valid approach to zero-value protection — does NOT flag it as wrong"
},
{
"id": "8.4",
"text": "Confirms that enum values are correctly prefixed with the type name (TaskStatePending, PriorityLow) — this is correct and should be kept"
}
]
},
{
"id": 9,
"name": "named-returns-and-bare-returns",
"description": "Tests that named returns are used only for documentation purposes and bare returns are avoided",
"prompt": "Review these Go function signatures and tell me which use named returns correctly and which don't. Fix any problems:\n\n```go\nfunc ParseConfig(data []byte) (config Config, err error) {\n // ... 40 lines of parsing logic ...\n return\n}\n\nfunc Divide(a, b float64) (result float64, err error) {\n if b == 0 {\n return 0, errors.New(\"division by zero\")\n }\n return a / b, nil\n}\n\nfunc ScanRecord(data []byte, atEOF bool) (advance int, token []byte, err error) {\n // ... complex scanning logic ...\n return advance, token, nil\n}\n\nfunc Write(p []byte) (n int, e error) {\n // ... write logic ...\n return\n}\n```",
"trap": "Model approves bare returns in the 40-line ParseConfig or doesn't flag 'e' as a non-standard error name in Write. May also miss that Divide's named returns add no documentary value since the types already clarify.",
"assertions": [
{
"id": "9.1",
"text": "Flags the bare return in ParseConfig as problematic — bare returns hurt readability in long functions where the reader must scroll to find what's being returned"
},
{
"id": "9.2",
"text": "Identifies ScanRecord as a correct use of named returns — the names clarify which int is which and serve as documentation for the caller"
},
{
"id": "9.3",
"text": "Flags 'e' as a non-standard error variable name in Write — the convention is 'err', and 'e' adds no clarity"
},
{
"id": "9.4",
"text": "Notes that Divide's named returns add little value since there's only one float64 and one error — unnamed returns would be equally clear"
},
{
"id": "9.5",
"text": "Flags the bare return in Write as problematic — should explicitly return the values"
}
]
},
{
"id": 10,
"name": "format-function-f-suffix",
"description": "Tests that functions accepting format strings use the f suffix convention",
"prompt": "I'm building a custom logging and error-wrapping library in Go. Design the public API with these functions:\n\n1. A function that creates a new error from a format string and arguments\n2. A function that wraps an existing error with additional context using a format string\n3. A function that logs at info level with a format string\n4. A function that logs at info level with a plain message (no formatting)\n5. A function that creates a new error from a plain string\n6. A function that panics with a formatted message\n\nWrite the function signatures (no implementations). Make sure the naming clearly signals which functions accept format strings.",
"trap": "Model creates WrapError or LogInfo for format-string variants instead of using the f suffix (Wrapf, Infof). May also not distinguish between plain-string and format-string versions.",
"assertions": [
{
"id": "10.1",
"text": "Format-string error creation uses the f suffix (Errorf) — not Error or NewError with format args"
},
{
"id": "10.2",
"text": "Format-string error wrapping uses the f suffix (Wrapf) — not WrapError or Wrap with format args"
},
{
"id": "10.3",
"text": "Format-string logging uses the f suffix (Infof or Logf) — not Info or LogInfo with format args"
},
{
"id": "10.4",
"text": "Plain-string variants do NOT have the f suffix (e.g., Info vs Infof, New vs Errorf) — clear distinction between format and plain versions"
},
{
"id": "10.5",
"text": "The panic function with format string uses the f suffix (Panicf or Fatalf) — not Panic with format args"
}
]
},
{
"id": 11,
"name": "context-variant-and-in-place-suffixes",
"description": "Tests WithContext suffix for context variants and In suffix for in-place mutations",
"prompt": "I have a Go data processing library. I need to add variants to existing functions:\n\n1. `Fetch(key string) ([]byte, error)` needs a variant that accepts a context.Context\n2. `Sort(items []Item) []Item` (returns a new sorted slice) needs an in-place variant that modifies the slice directly\n3. `ParseConfig(path string) (*Config, error)` needs a variant that panics instead of returning an error\n4. `Reverse(s string) string` (returns a new string) needs an in-place variant for byte slices\n5. `Query(sql string) (*Rows, error)` needs a context-aware variant\n\nWhat should the variant function names be? Write the signatures.",
"trap": "Model uses FetchCtx/FetchWithCtx instead of FetchWithContext, SortMut/SortSlice instead of SortIn, or ParseConfigOrPanic instead of MustParseConfig.",
"assertions": [
{
"id": "11.1",
"text": "Context-accepting variants use WithContext suffix (FetchWithContext, QueryWithContext or QueryContext) — NOT FetchCtx, FetchWithCtx, or CtxFetch"
},
{
"id": "11.2",
"text": "In-place mutation variants use the In suffix (SortIn, ReverseIn) — NOT SortMut, SortInPlace, SortSlice, or MutateSort"
},
{
"id": "11.3",
"text": "Panic-on-error variant uses Must prefix (MustParseConfig) — NOT ParseConfigOrPanic, ParseConfigOrDie, or ParseConfigMust"
},
{
"id": "11.4",
"text": "All variants follow their respective conventions consistently (With* for context, In for mutation, Must for panic)"
}
]
},
{
"id": 12,
"name": "import-aliasing-collision-only",
"description": "Import aliases are only justified when two packages have the same final path segment; aliases for readability are wrong",
"prompt": "A teammate wrote this Go file. Review the import aliases:\n\n```go\npackage main\n\nimport (\n \"context\"\n \"encoding/json\"\n httputil \"net/http\"\n stdfmt \"fmt\"\n \"crypto/rand\"\n mrand \"math/rand/v2\"\n apiv1 \"myapp/api/v1\"\n apiv2 \"myapp/api/v2\"\n \"strings\"\n strutil \"golang.org/x/text/unicode/norm\"\n \"github.com/rs/zerolog\"\n zlog \"github.com/rs/zerolog/log\"\n)\n```\n\nWhich aliases are justified? Which should be removed? For each removal, explain why. For each kept alias, explain why it's necessary.",
"trap": "Model may approve httputil (net/http is verbose), stdfmt (explicit about stdlib origin), strutil (long package name), apiv1/apiv2 (disambiguation) and zlog (explicit about subpackage). The skill teaches: alias ONLY on actual name collision — the default package name is already the last path segment. apiv1/apiv2 are justified because both would collide as 'v2'/'v1'. mrand is justified. httputil, stdfmt, strutil, zlog are NOT justified — they add cognitive load without resolving a collision.",
"assertions": [
{
"id": "12.1",
"text": "Flags httputil as unjustified — net/http's default package name is 'http', there is no collision. 'httputil' adds a mapping readers must remember"
},
{
"id": "12.2",
"text": "Flags stdfmt as unjustified — 'fmt' is already the package name. 'stdfmt' just adds a prefix with no collision to resolve"
},
{
"id": "12.3",
"text": "Keeps mrand as justified — it collides with crypto/rand (both would be 'rand' without aliases)"
},
{
"id": "12.4",
"text": "Keeps apiv1 and apiv2 as justified — both myapp/api/v1 and myapp/api/v2 would default to 'v2'/'v1' but the numeric suffixes are ambiguous; aliasing to apiv1/apiv2 disambiguates"
},
{
"id": "12.5",
"text": "Flags strutil or zlog as unjustified — the package already has a clear default name (norm or log); renaming it to strutil or zlog adds cognitive load without resolving a collision"
}
]
},
{
"id": 13,
"name": "consistent-concept-naming",
"description": "Tests that the same domain concept uses the same name throughout the codebase",
"prompt": "I have a Go service with these function signatures across different files. Are there any naming consistency issues?\n\n```go\n// user_service.go\nfunc CreateUser(user *User) error\nfunc UpdateAccount(acct *User) error\nfunc RemovePerson(id string) error\nfunc GetUserByID(userID string) (*User, error)\n\n// order_service.go \nfunc PlaceOrder(order *Order) error\nfunc ModifyPurchase(purchase *Order) error\nfunc CancelOrder(orderId string) error\nfunc FetchOrder(oid string) (*Order, error)\n```\n\nReview and fix the naming.",
"trap": "Model may fix only the obvious GetUserByID→UserByID but miss the inconsistent synonyms (user/account/person, order/purchase) and the inconsistent parameter naming (userID/id/orderId/oid).",
"assertions": [
{
"id": "13.1",
"text": "Identifies that user/account/person are inconsistent synonyms for the same concept and standardizes on one name (user)"
},
{
"id": "13.2",
"text": "Identifies that order/purchase are inconsistent synonyms and standardizes on one name (order)"
},
{
"id": "13.3",
"text": "Standardizes the ID parameter naming — uses one consistent name (e.g., userID, orderID) instead of mixing id/userID/orderId/oid"
},
{
"id": "13.4",
"text": "Fixes the acronym casing: orderId should be orderID (ID is an acronym, must be all caps)"
},
{
"id": "13.5",
"text": "Standardizes the verb pattern across CRUD operations (e.g., Create/Update/Delete or Create/Modify/Cancel — not Create/Update/Remove mixed with Place/Modify/Cancel)"
}
]
},
{
"id": 14,
"name": "boolean-getter-vs-richer-return",
"description": "Tests the distinction between Is*/Has* bool predicates and value getters that happen to return status-like values",
"prompt": "A Go struct Server has these fields:\n\n```go\ntype Server struct {\n port int\n healthy bool\n ready bool\n status HealthStatus\n connected bool\n}\n```\n\nWrite getter methods for all five fields following Go conventions. HealthStatus is a custom enum type.",
"trap": "Model uses GetPort() or drops the Is prefix on boolean getters (Healthy() bool). May also incorrectly add Is prefix to the HealthStatus getter (IsStatus() instead of Status()).",
"assertions": [
{
"id": "14.1",
"text": "The port getter is named Port() not GetPort() — Go omits the Get prefix on value getters"
},
{
"id": "14.2",
"text": "Boolean getters use Is prefix: IsHealthy() bool, IsReady() bool, IsConnected() bool — NOT bare Healthy(), Ready(), Connected()"
},
{
"id": "14.3",
"text": "The HealthStatus getter is named Status() not GetStatus() and does NOT use Is prefix (IsStatus) — Is/Has is only for bool return types"
},
{
"id": "14.4",
"text": "All methods use the same 1-2 letter receiver name consistently (e.g., 's' for Server)"
}
]
}
]
Functions, Methods & Options
Functions and Methods
Functions returning a value are named like nouns (what they return). Functions performing actions are named like verbs (what they do).
// Noun-like: returns something
func UserName() string { ... }
func DefaultConfig() Config { ... }
// Verb-like: performs an action
func WriteFile(name string, data []byte) error { ... }
func SendNotification(user *User) error { ... }NEVER repeat the package name in function names:
// Good: users call http.Get(), not http.HTTPGet()
package http
func Get(url string) (*Response, error)
// Bad: stutters at the call site
package http
func HTTPGet(url string) (*Response, error)Functions that accept a format string and variadic args (like fmt.Sprintf) MUST end with `f`:
// Good
func Errorf(format string, args ...any) error
func Wrapf(err error, format string, args ...any) error
func Logf(format string, args ...any)
// Bad
func Error(format string, args ...any) error // looks like it takes a plain string
func WrapError(err error, format string, args ...any) errorGetters and Setters
Getters MUST NOT use the Get prefix. The getter is simply the field name, capitalized.
// Good
func (u *User) Name() string { return u.name }
func (u *User) SetName(name string) { u.name = name }
// Bad
func (u *User) GetName() string { return u.name }Only use Get when the underlying concept inherently uses "get" (e.g., HTTP GET). For expensive or blocking operations, use Fetch or Compute to signal that the call is not trivial.
Exception — boolean predicates keep the `Is`/`Has`/`Can` prefix. The no-Get rule applies to value getters, not boolean predicates. A method returning bool SHOULD use Is/Has/Can to read naturally as a question — this follows the standard library pattern (reflect.Type.IsVariadic(), net.IP.IsLoopback(), big.Int.IsInt64()).
// Good — boolean predicate keeps Is prefix
func (s *Server) IsHealthy() bool { return s.healthy }
// Good — value getter omits Get prefix
func (s *Server) Port() int { return s.port }
// Good — richer return type, bare name is fine (different semantics)
func (s *Server) Healthy() (HealthStatus, error)
// Bad — bare adjective for bool is ambiguous
func (s *Server) Healthy() bool { return s.healthy }
// Bad — Get prefix on value getter is redundant
func (s *Server) GetPort() int { return s.port }Constructors
Name constructors New when the package exports a single primary type, or NewTypeName when there are multiple types.
// Single primary type — New is unambiguous
package ring
func New(size int) *Ring
// Multiple types — qualify with the type name
package http
func NewRequest(method, url string, body io.Reader) (*Request, error)
func NewServeMux() *ServeMuxNamed Return Values
Named return values SHOULD only be used when it improves readability — typically when multiple return values have the same type, or when the names serve as documentation.
// Good — names clarify which int64 is which
func Copy(dst Writer, src Reader) (written int64, err error)
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
// Good — single error return, no name needed
func Write(p []byte) (int, error)
// Bad — names add no clarity
func Read(p []byte) (bytes int, e error) // "bytes" shadows the package, "e" is non-standardNEVER use named returns just to enable bare return — bare returns hurt readability in anything but the shortest functions.
Functional Options Pattern
When a constructor has 3+ optional parameters that may grow, use the functional options pattern for clean, extensible APIs.
- Struct:
ServerOptions,ClientOptions(notOpts,Params,Settings,Config) - Function type:
ServerOption(singular, not plural) - *With\ functions**:
WithPort(),WithTimeout(),WithLogger() - Factory:
DefaultServerOptions()
Variables, Booleans, Receivers & Acronyms
Variables
Name length SHOULD be proportional to scope size. Short names for small scopes, descriptive names for large scopes.
// Small scope (1-7 lines): short names are fine
for i, v := range items {
result = append(result, v.Name)
}
// Medium scope: moderately descriptive
userCount := len(users)
// Large scope / package-level: explicit and clear
var defaultHTTPTransport = &http.Transport{
MaxIdleConns: 100,
}Common single-letter conventions:
| Letter | Meaning |
|---|---|
i, j, k | Loop indices |
n | Count or length |
v | Value (in range loops) |
k | Key (in map ranges) |
r | io.Reader |
w | io.Writer |
b | []byte or buffer |
s | String |
t | *testing.T |
ctx | context.Context |
err | Error |
Avoid Type in the Name
The name should describe what the value represents, not its type.
// Good
users := getUsers()
count := len(items)
// Bad
userSlice := getUsers()
countInt := len(items)
nameString := "hello"Avoid Repetition with Context
Omit words already clear from the enclosing function, method, or type.
// Good — "user" is clear from the method receiver
func (u *UserService) Create(name string) error { ... }
// Bad — "user" is redundant
func (u *UserService) CreateUser(userName string) error { ... }Use Predictable Names
The same concept MUST always use the same name across the codebase. If a user is called user in one function, it should not become account, person, or u in another. Consistency makes code searchable and reduces cognitive load.
// Good — same concept, same name everywhere
func CreateUser(user *User) error { ... }
func UpdateUser(user *User) error { ... }
func DeleteUser(userID string) error { ... }
// Bad — same concept, different names
func CreateUser(user *User) error { ... }
func UpdateAccount(acct *User) error { ... } // why "acct"? it's a User
func RemovePerson(id string) error { ... } // why "person"? why "remove"?This applies to variables, parameters, functions, and fields. Pick one name per domain concept and stick with it: order not sometimes order / sometimes purchase; userID not sometimes userID / sometimes uid / sometimes userId.
Parameters
Parameters double as documentation at the call site. When the type is descriptive, keep the name short. When the type is ambiguous, use a longer name to clarify intent.
// Good — type is descriptive, short name is fine
func AfterFunc(d Duration, f func()) *Timer
func Escape(w io.Writer, s []byte)
// Good — type is ambiguous (int64, string), longer name documents meaning
func Unix(sec, nsec int64) Time
func HasPrefix(s, prefix string) bool
// Bad — ambiguous type with cryptic name
func Unix(a, b int64) Time // what are a and b?
func HasPrefix(a, b string) bool // which is the prefix?Booleans
Boolean variables and fields MUST read naturally as true/false questions. Use prefixes like is, has, can, allow, should. This applies to both variables and struct fields.
// Good — struct fields use is/has prefix
type Client struct {
isConnected bool // reads as "client is connected"
hasPermission bool // reads as "client has permission"
}
// Good — variables
isReady := true
hasPermission := user.CanEdit(doc)
// Bad — bare adjective is ambiguous
type Client struct {
connected bool // could be confused with a connection object
permission bool // noun, not a question
}For exported boolean methods, the prefix becomes part of the method name: IsValid(), HasPrefix(), CanRetry(). The unexported field keeps the prefix too: isConnected field → IsConnected() method.
Receivers
Receivers MUST be 1-2 letter abbreviations of the type name. Use the same name across all methods of a type.
// Good — short, consistent
func (s *Server) Start() error { ... }
func (s *Server) Stop() error { ... }
func (s *Server) Handle(r *Request) { ... }
// Bad — too long
func (server *Server) Start() error { ... }
// Bad — inconsistent names across methods
func (s *Server) Start() error { ... }
func (srv *Server) Stop() error { ... }
// Bad — NEVER use "this" or "self"
func (this *Server) Handle(r *Request) { ... }Acronyms and Initialisms
Acronyms MUST be all caps or all lower, NEVER mixed. This preserves readability in MixedCaps names.
// Good
URL // all caps
url // all lower
HTTPServer // HTTP is all caps
xmlParser // xml is all lower
userID // ID is all caps
newHTTPSURL // both HTTPS and URL all caps
// Bad
Url // mixed case acronym
HttpServer // mixed case
userId // mixed case for IDWhen exporting a lowercase acronym, capitalize the whole thing: url → URL, grpc → GRPC, ios → IOS.
Packages, Files & Import Aliasing
Packages
Package names MUST be lowercase, single-word, with no underscores or MixedCaps. They should be short, concise, and evocative of their purpose. Numbers are allowed (oauth2, k8s).
// Good
package json
package http
package tabwriter
package oauth2
// Bad
package httpServer // no MixedCaps
package http_server // no underscores
package util // too generic
package common // meaningless
package helpers // what does it help with?
package base // says nothing
package model // too vagueNEVER use generic package names like util, helper, common, base, model. They fail to communicate purpose and cause import collisions. If you reach for util, the function probably belongs in a more specific package.
Package names SHOULD be singular, not plural — net/url not net/urls, go/token not go/tokens.
Directory vs Package Name
Directory names SHOULD match the package name when possible. Multi-word directories use hyphens, but since package names cannot contain hyphens, the package drops them.
// Good — directory matches package
httputil/ → package httputil
middleware/ → package middleware
auth/ → package auth
// Good — hyphenated directory, package drops hyphens
user-service/ → package userservice
rate-limit/ → package ratelimit
go-chi/ → package chi
// Good — special directories
cmd/api/ → package main (cmd/ subdirectories are always main)
internal/auth/ → package auth (internal/ restricts visibility)
// Bad
user_service/ → package user_service (underscores in both)
UserService/ → package UserService (no MixedCaps in directories)
myPackage/ → package mypackage (directory has caps, package doesn't)Special directories have Go toolchain meaning and don't follow normal naming:
cmd/— entry points, each subdirectory ispackage maininternal/— restricts import visibility to parent moduletestdata/— ignored by the Go toolvendor/— vendored dependencies
Package names SHOULD NOT duplicate exported names — users see bufio.Reader, not bufio.BufReader. Think about the call site.
Files
File names MUST be lowercase with words separated by underscores.
user_handler.go
string_converter.go
http_client_test.goSpecial suffixes:
_test.go— test files (excluded from production builds)_linux.go,_amd64.go— OS/architecture-specific (build constraints)
Import Aliasing
Import aliases SHOULD only be used on name collision. When an alias is necessary, use a descriptive short name.
// Good — no alias needed
import "github.com/go-chi/chi/v5"
// Good — alias resolves collision
import (
"crypto/rand"
mrand "math/rand"
)
// Good — conventional alias for generated code
import pb "myapp/proto/userpb"
// Bad — unnecessary alias
import f "fmt"Test Naming
Test Functions
Test functions follow Test + the name of what is being tested. Use underscores for subcases.
func TestParseToken(t *testing.T) { ... }
func TestServer_Handle(t *testing.T) { ... } // method test
func TestParseToken_InvalidInput(t *testing.T) { ... } // subcaseTable-Driven Tests
Table-driven test case names SHOULD be fully lowercase, descriptive phrases — including acronyms. Use input for inputs and expected for expected outputs to make the data flow clear:
tests := []struct {
name string
input string
expectedCode int
expectedErr bool
}{
{name: "empty input", input: "", expectedCode: 400, expectedErr: true},
{name: "valid token", input: "abc123", expectedCode: 200},
{name: "expired token", input: "exp", expectedCode: 401, expectedErr: true},
{name: "invalid id", input: "???", expectedCode: 400, expectedErr: true}, // "id" not "ID"
}
// Bad — mixed case in test names
{name: "valid ID", ...} // should be "valid id"
{name: "Empty Input", ...} // should be "empty input"Test Helpers
Test helper functions that panic on failure conventionally use the must prefix: mustLoadFixture(), mustParseURL().
Types, Constants & Errors
Interfaces
Single-Method Interfaces
Name them with the method name + `-er` suffix:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Stringer interface {
String() string
}
type Closer interface {
Close() error
}Multi-Method Interfaces
Use a descriptive noun or compose from single-method interfaces:
type ReadWriteCloser interface {
Reader
Writer
Closer
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}Canonical Method Names
Honor established Go method names and their signatures. If your type implements Read, it MUST match io.Reader's signature. NEVER invent variations like ReadData or ToString — use String.
| Method name | Expected interface |
|---|---|
Read | io.Reader |
Write | io.Writer |
Close | io.Closer |
String | fmt.Stringer |
Error | error |
Len | sort.Interface |
ServeHTTP | http.Handler |
Structs
Name structs with MixedCaps nouns describing the entity. Fields follow exported/unexported rules.
type Server struct {
Addr string // exported
Handler http.Handler // exported
timeout time.Duration // unexported
}NEVER suffix struct names with Struct, Object, or Data — they add no information.
Constants
Constants MUST use MixedCaps, NEVER ALL_CAPS. The name should explain the role, not the value.
// Good — MixedCaps, name explains purpose
const MaxRetries = 3
const defaultTimeout = 30 * time.Second
const DefaultPort = 8080
// Bad — ALL_CAPS is not idiomatic Go
const MAX_RETRIES = 3
const DEFAULT_TIMEOUT = 30
// Bad — name is the value, not the purpose
const Three = 3
const Port8080 = 8080Enums (iota)
Prefix enum values with the type name to avoid collisions and improve readability at the call site.
type Status int
const (
StatusUnknown Status = iota // zero value = unknown/invalid
StatusReady
StatusRunning
StatusDone
)
type Color int
const (
ColorRed Color = iota + 1 // skip zero to catch uninitialized values
ColorGreen
ColorBlue
)Always protect the zero value. A var s Status will silently be 0 — if that maps to a real state like StatusReady, code can behave as if a status was deliberately chosen when it wasn't. Either place an explicit Unknown sentinel at iota 0, or start at iota + 1. This is not optional — uninitialized enums are a common source of silent bugs.
Errors
Sentinel Errors
Sentinel error variables use the Err prefix. Error strings SHOULD include the package name as prefix to identify the origin when errors are wrapped:
// Good — package prefix identifies origin
var ErrNotFound = errors.New("mypackage: not found")
var ErrPermissionDenied = errors.New("mypackage: permission denied")
var ErrTimeout = errors.New("mypackage: operation timed out")
// Bad — bare strings lose origin when wrapped
var ErrNotFound = errors.New("not found")Error Types
Custom error types use the Error suffix:
type PathError struct {
Op string
Path string
Err error
}
type SyntaxError struct {
Offset int64
msg string
}Error Strings
Error strings MUST be fully lowercase — including acronyms — and MUST NOT end with punctuation, because they are often printed following other context (fmt.Errorf("parsing config: %w", err)). Acronyms that would normally be capitalized in identifiers (ID, URL, HTTP) become lowercase in error strings.
// Good — lowercase including acronyms, no punctuation
errors.New("image: unknown format")
errors.New("mypackage: invalid message id") // "id" not "ID"
errors.New("mypackage: invalid url") // "url" not "URL"
fmt.Errorf("decoding config: %w", err)
// Bad — capitalized, acronyms, punctuation
errors.New("Image: Unknown format.")
errors.New("mypackage: invalid message ID") // ID should be lowercase in error strings
fmt.Errorf("Failed to decode config: %w.", err)Related skills
How it compares
Go-specific naming rulebook, not a general style guide for all languages.
FAQ
Who is golang-naming for?
Go developers deciding package, type, error, boolean, constructor, or test naming conventions.
When should I use golang-naming?
When reviewing naming in PRs, refactoring identifiers, or debating MixedCaps versus snake_case in Go.
Is golang-naming safe to install?
Review the Security Audits panel on this page before installing in production.