返回 Skills
cxuu/golang-skills· Apache-2.0 内容可用

go-naming

Use when naming any Go identifier — packages, types, functions, methods, variables, constants, or receivers — to ensure idiomatic, clear names. Also use when a user is creating new types, packages, or exported APIs, even if they don't explicitly ask about naming conventions. Does not cover package organization (see go-packages).

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: go-naming description: Use when naming any Go identifier — packages, types, functions, methods, variables, constants, or receivers — to ensure idiomatic, clear names. Also use when a user is creating new types, packages, or exported APIs, even if they don't explicitly ask about naming conventions. Does not cover package organization (see go-packages). allowed-tools: Bash(bash:*)

Go Naming Conventions

Resource Routing

  • scripts/check-naming.sh - Run when checking SCREAMING_SNAKE_CASE constants, Get-prefixed getters, generic package names, or receivers named this/self.
  • references/IDENTIFIERS.md - Read when choosing names for initialisms, exported identifiers, or package-level symbols.
  • references/REPETITION.md - Read when names repeat package, receiver, type, or local context.
  • references/VARIABLES.md - Read when choosing local variable names, receiver names, or loop identifiers.

Core Principle

Names should:

  • Not feel repetitive when used
  • Take context into consideration
  • Not repeat concepts that are already clear

Naming is more art than science—Go names tend to be shorter than in other languages.


Naming Decision Flow

What are you naming?
├─ Package       → Short, lowercase, singular noun (no underscores, no mixedCaps)
├─ Interface     → Method name + "-er" suffix when single-method (Reader, Writer)
├─ Receiver      → 1-2 letter abbreviation of type (c for Client); consistent across methods
├─ Constant      → MixedCaps; use iota for enums; no ALL_CAPS
├─ Exported func → Verb or verb-phrase in MixedCaps; no Get prefix for getters
├─ Variable      → Length proportional to scope distance
│                  ├─ Tiny scope (1-7 lines) → single letter (i, n, r)
│                  ├─ Medium scope           → short word (count, buf)
│                  └─ Package-level / wide   → descriptive (userAccountCount)
└─ Any name      → Check: does it repeat package name or context? If yes, shorten it

MixedCaps (Required)

Normative: All Go identifiers must use MixedCaps.

Underscores are allowed only in: test functions (TestFoo_InvalidInput), generated code, and OS/cgo interop.


Package Names

Normative: Packages must be lowercase with no underscores.

Short, lowercase, singular nouns. Avoid generic names like util, common, helper — prefer specific names: stringutil, httpauth, configloader.

// Good: user, oauth2, tabwriter
// Bad:  user_service, UserService, count (shadows var)

Interface Names

Advisory: One-method interfaces use "-er" suffix.

Name one-method interfaces by the method plus -er: Reader, Writer, Formatter. Honor canonical method names (Read, Write, Close, String) and their signatures.


Receiver Names

Normative: Receivers must be short abbreviations, used consistently.

One or two letters abbreviating the type, consistent across all methods: func (c *Client) Connect(), func (c *Client) Send(). Never use this or self.


Constant Names

Normative: Constants use MixedCaps, never ALL_CAPS or K prefix.

Name constants by role, not value: MaxRetries not Three, DefaultPort not Port8080.

const MaxPacketSize = 512
const defaultTimeout = 30 * time.Second

Initialisms and Acronyms

Normative: Initialisms maintain consistent case throughout.

Initialisms (URL, ID, HTTP, API) must be all uppercase or all lowercase: HTTPClient, userID, ParseURL() — not HttpClient, orderId, ParseUrl().


Function and Method Names

Advisory: No Get prefix for simple accessors; use verb-like names for actions.

Getter for field owner is Owner(), not GetOwner(). Setter is SetOwner(). Use Compute or Fetch for expensive operations.

When functions differ only by type, include type at the end: ParseInt(), ParseInt64().


Variable Names

Variable naming balances brevity with clarity. Key principles:

  • Scope-based length: Short names (i, v) for small scopes; longer, descriptive names for larger scopes
  • Single-letter conventions: Use familiar patterns (i for index, r/w for reader/writer)
  • Avoid type in name: Use users not userSlice, name not nameString
  • Prefix unexported globals: Use _ prefix for package-level unexported vars/consts to prevent shadowing
for i, v := range items { ... }           // small scope
pendingOrders := filterPending(orders)    // larger scope
const _defaultPort = 8080                 // unexported global

Avoiding Repetition

Go names should not feel repetitive when used. Consider the full context:

  • Package + symbol: widget.New() not widget.NewWidget()
  • Receiver + method: p.Name() not p.ProjectName()
  • Context + type: In package sqldb, use Connection not DBConnection

Avoid Built-In Names

Never shadow Go's predeclared identifiers (error, string, len, cap, append, copy, new, make, etc.) as variable, parameter, or type names.

For detailed guidance: See go-declarations — "Avoid Using Built-In Names" section.


Quick Reference

ElementRuleExample
Packagelowercase, no underscorespackage httputil
ExportedMixedCaps, starts uppercasefunc ParseURL()
UnexportedmixedCaps, starts lowercasefunc parseURL()
Receiver1-2 letter abbreviationfunc (c *Client)
ConstantMixedCaps, never ALL_CAPSconst MaxSize = 100
Initialismconsistent caseuserID, XMLAPI
Variablelength ~ scope sizei (small), userCount (large)
Built-in namesNever shadow predeclared identifiersSee go-declarations

Validation: After renaming identifiers, run bash scripts/check-naming.sh to verify no naming anti-patterns remain. Then run go build ./... to confirm the rename didn't break anything.

Related Skills

  • Interface naming: See go-interfaces when naming interfaces with the -er suffix or choosing receiver types
  • Package naming: See go-packages when naming packages, avoiding util/common, or resolving import collisions
  • Error naming: See go-error-handling when naming sentinel errors (ErrFoo) or custom error types
  • Declaration scope: See go-declarations when variable name length depends on scope or when avoiding built-in shadowing
  • Style principles: See go-style-core when balancing clarity vs concision in identifier names

附带文件

references/IDENTIFIERS.md
# Identifier Naming Rules

Detailed rules and examples for naming Go packages, interfaces, receivers,
constants, initialisms, and functions.

## Package Names

> **Normative**: Packages must be lowercase with no underscores.

Package names must be:
- Concise and lowercase only
- No underscores (e.g., `tabwriter` not `tab_writer`)
- Not likely to shadow common variables

```go
// Good: user, oauth2, k8s, tabwriter
// Bad: user_service (underscores), UserService (uppercase), count (shadows var)
```

### Avoid Uninformative Names

> **Advisory**: Don't use generic package names.

Avoid names that tempt users to rename on import: `util`, `common`, `helper`,
`model`, `base`. Prefer specific names: `stringutil`, `httpauth`, `configloader`.

### Import Renaming

When renaming imports, the local name must follow package naming rules:
`import foopb "path/to/foo_go_proto"` (not `foo_pb` with underscore).

---

## Interface Names

> **Advisory**: One-method interfaces use "-er" suffix.

By convention, one-method interfaces are named by the method name plus an `-er`
suffix to construct an agent noun:

```go
// Standard library examples
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Formatter interface { Format(f State, verb rune) }
type CloseNotifier interface { CloseNotify() <-chan bool }
```

Honor canonical method names (`Read`, `Write`, `Close`, `String`) and their
signatures. If your type implements a method with the same meaning as a
well-known type, use the same name—call it `String` not `ToString`.

---

## Receiver Names

> **Normative**: Receivers must be short abbreviations, used consistently.

Receiver variable names must be:
- Short (one or two letters)
- Abbreviations for the type itself
- Consistent across all methods of that type

| Long Name (Bad)             | Better Name              |
|-----------------------------|--------------------------|
| `func (tray Tray)`          | `func (t Tray)`          |
| `func (info *ResearchInfo)` | `func (ri *ResearchInfo)`|
| `func (this *ReportWriter)` | `func (w *ReportWriter)` |
| `func (self *Scanner)`      | `func (s *Scanner)`      |

```go
// Good - consistent short receiver
func (c *Client) Connect() error
func (c *Client) Send(msg []byte) error
func (c *Client) Close() error

// Bad - inconsistent or long receivers
func (client *Client) Connect() error
func (cl *Client) Send(msg []byte) error
func (this *Client) Close() error
```

---

## Constant Names

> **Normative**: Constants use MixedCaps, never ALL_CAPS or K prefix.

```go
// Good
const MaxPacketSize = 512
const defaultTimeout = 30 * time.Second

// Bad
const MAX_PACKET_SIZE = 512    // no snake_case
const kMaxBufferSize = 1024    // no K prefix
```

### Name by Role, Not Value

> **Advisory**: Constants should explain what the value denotes.

```go
// Good - names explain the role
const MaxRetries = 3
const DefaultPort = 8080

// Bad - names just describe the value
const Three = 3
const Port8080 = 8080
```

---

## Initialisms and Acronyms

> **Normative**: Initialisms maintain consistent case throughout.

Initialisms (URL, ID, HTTP, API) should be all uppercase or all lowercase:

| English   | Exported  | Unexported |
|-----------|-----------|------------|
| URL       | `URL`     | `url`      |
| ID        | `ID`      | `id`       |
| HTTP/API  | `HTTP`    | `http`     |
| gRPC/iOS  | `GRPC`/`IOS` | `gRPC`/`iOS` |

```go
// Good: HTTPClient, userID, ParseURL()
// Bad: HttpClient, orderId, ParseUrl()
```

---

## Function and Method Names

### Getters and Setters

> **Advisory**: Don't use `Get` prefix for simple accessors.

If you have a field called `owner` (unexported), the getter should be `Owner()`
(exported), not `GetOwner()`. The setter, if needed, is `SetOwner()`:

```go
// Good
owner := obj.Owner()
if owner != user {
    obj.SetOwner(user)
}

// Bad: c.GetName(), u.GetEmail(), p.GetID()
```

Use `Compute` or `Fetch` for expensive operations:
`db.FetchUser(id)`, `stats.ComputeAverage()`.

### Naming Conventions

> **Advisory**: Use noun-like names for getters, verb-like names for actions.

```go
// Noun-like for returning values
func (c *Config) JobName(key string) string
func (u *User) Permissions() []Permission

// Verb-like for actions
func (c *Config) WriteDetail(w io.Writer) error
```

### Type Suffixes

When functions differ only by type, include type at the end:
`ParseInt()`, `ParseInt64()`, `AppendInt()`, `AppendInt64()`.

For a clear "primary" version, omit the type:
`Marshal()` (primary), `MarshalText()` (variant).
references/REPETITION.md
# Avoiding Repetition

This reference covers how to avoid redundant naming in Go by considering the context
where names appear—package, receiver type, and surrounding code.

## Package vs. Exported Symbol

> **Advisory**: Don't repeat package name in exported symbols.

```go
// Bad - repetitive at call site
package widget
func NewWidget() *Widget           // widget.NewWidget()
func NewWidgetWithName(n string)   // widget.NewWidgetWithName()

// Good - concise at call site
package widget
func New() *Widget                 // widget.New()
func NewWithName(n string) *Widget // widget.NewWithName()
```

```go
// Bad
package db
func LoadFromDatabase() error      // db.LoadFromDatabase()

// Good
package db
func Load() error                  // db.Load()
```

## Method vs. Receiver Type

> **Advisory**: Don't repeat receiver type in method name.

```go
// Bad
func (c *Config) WriteConfigTo(w io.Writer) error
func (p *Project) ProjectName() string

// Good
func (c *Config) WriteTo(w io.Writer) error
func (p *Project) Name() string
```

## Context vs. Local Names

> **Advisory**: Omit information already clear from context.

```go
// Bad - in package "ads/targeting/revenue/reporting"
type AdsTargetingRevenueReport struct{}

// Good
type Report struct{}
```

```go
// Bad - in package "sqldb"
type DBConnection struct{}

// Good
type Connection struct{}
```

## Complete Example

```go
// Bad - excessive repetition
func (db *DB) UserCount() (userCount int, err error) {
    var userCountInt64 int64
    if dbLoadError := db.LoadFromDatabase("count(distinct users)", &userCountInt64); dbLoadError != nil {
        return 0, fmt.Errorf("failed to load user count: %s", dbLoadError)
    }
    userCount = int(userCountInt64)
    return userCount, nil
}

// Good - clear and concise
func (db *DB) UserCount() (int, error) {
    var count int64
    if err := db.Load("count(distinct users)", &count); err != nil {
        return 0, fmt.Errorf("failed to load user count: %s", err)
    }
    return int(count), nil
}
```
references/VARIABLES.md
# Variable Names

This reference provides detailed guidance on naming variables in Go, covering scope-based
naming, single-letter conventions, and avoiding type redundancy.

## Length Proportional to Scope

> **Advisory**: Short names for small scopes, longer names for large scopes.

| Scope        | Lines  | Name Length |
|--------------|--------|-------------|
| Small        | 1-7    | 1-2 chars   |
| Medium       | 8-15   | short word  |
| Large        | 15-25  | descriptive |
| Very large   | 25+    | full words  |

```go
// Good - short scope, short name
for i := 0; i < len(items); i++ {
    process(items[i])
}

// Good - larger scope, clearer name
func processOrders(orders []*Order) error {
    pendingOrders := filterPending(orders)
    // ... 20+ lines of processing ...
    return nil
}
```

## Single-Letter Variables

> **Advisory**: Use single letters only when meaning is obvious.

Appropriate uses:
- Loop indices: `i`, `j`, `k`
- Coordinates: `x`, `y`, `z`
- Receivers: one or two letters
- Common types: `r` for `io.Reader`, `w` for `io.Writer`
- Short loops: `for _, n := range nodes`

```go
// Good - familiar conventions
func Copy(w io.Writer, r io.Reader) (int64, error)

for i, v := range values {
    process(v)
}

// Bad - unclear single letters
func Process(a, b, c string) error  // what are a, b, c?
```

## Avoid Type in Variable Name

> **Advisory**: Don't include the type in the variable name.

| Repetitive (Bad)             | Better               |
|------------------------------|----------------------|
| `var numUsers int`           | `var users int`      |
| `var nameString string`      | `var name string`    |
| `var primaryProject *Project`| `var primary *Project`|
| `var userSlice []User`       | `var users []User`   |

When disambiguating multiple forms, use meaningful qualifiers:

```go
// Good - meaningful distinction
limitRaw := r.FormValue("limit")
limit, err := strconv.Atoi(limitRaw)

// Also good
limitStr := r.FormValue("limit")
limit, err := strconv.Atoi(limitStr)
```

## Prefix Unexported Globals with _

> **Source**: Uber Go Style Guide

Prefix unexported top-level `var`s and `const`s with `_` to clarify when they
are used that they are global symbols.

**Rationale**: Top-level variables and constants have package scope. Using a
generic name makes it easy to accidentally shadow the value in a different file.

```go
// Bad - hard to distinguish from local variables
const (
    defaultPort = 8080
    defaultUser = "user"
)

func Bar() {
    defaultPort := 9090  // shadows global, no compile error
    fmt.Println("Default port", defaultPort)
}
```

```go
// Good - clearly global
const (
    _defaultPort = 8080
    _defaultUser = "user"
)
```

**Exception**: Unexported error values use the `err` prefix without underscore:

```go
var errUserNotFound = errors.New("user not found")
var errInvalidInput = errors.New("invalid input")
```
scripts/check-naming.sh
#!/usr/bin/env bash
set -euo pipefail

VERSION="1.0.0"
SCRIPT_NAME="$(basename "$0")"

usage() {
    cat <<EOF
$SCRIPT_NAME v$VERSION — Check Go code for common naming anti-patterns

USAGE
    bash $SCRIPT_NAME [options] [path]

DESCRIPTION
    Scans Go source files for naming violations based on Go style guidelines:
      - SCREAMING_SNAKE_CASE constants (should be MixedCaps)
      - Get-prefixed getter methods (should omit Get)
      - Packages named util/helper/common/misc
      - Receivers named "this" or "self"

    Exits 0 if no violations found, 1 if violations found, 2 on error.

OPTIONS
    -h, --help       Show this help message
    -v, --version    Show version
    --json           Output results as JSON
    --limit N        Show at most N results (default: all)

ARGUMENTS
    path             Directory or Go file to check (default: current directory)

EXAMPLES
    bash $SCRIPT_NAME
    bash $SCRIPT_NAME ./cmd/server
    bash $SCRIPT_NAME --json ./pkg/...
    bash $SCRIPT_NAME myfile.go
EOF
}

JSON_OUTPUT=false
LIMIT=0
TARGET=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help)    usage; exit 0 ;;
        -v|--version) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;;
        --json)       JSON_OUTPUT=true; shift ;;
        --limit)
            if [[ $# -lt 2 ]]; then
                echo "error: --limit requires a number" >&2
                exit 2
            fi
            LIMIT="$2"
            shift 2
            ;;
        -*)           echo "error: unknown option: $1" >&2; usage >&2; exit 2 ;;
        *)            TARGET="$1"; shift ;;
    esac
done

TARGET="${TARGET:-.}"

if ! [[ "$LIMIT" =~ ^[0-9]+$ ]]; then
    echo "error: --limit must be a non-negative integer, got: $LIMIT" >&2
    exit 2
fi

json_escape() {
    local s="$1"
    s="${s//\\/\\\\}"
    s="${s//\"/\\\"}"
    s="${s//$'\t'/\\t}"
    s="${s//$'\r'/}"
    s="${s//$'\n'/\\n}"
    printf '%s' "$s"
}

# Resolve target to a list of .go files (exclude _test.go and vendor)
find_go_files() {
    local t="$1"
    if [[ -f "$t" ]]; then
        echo "$t"
    elif [[ -d "$t" ]]; then
        find "$t" -name '*.go' ! -name '*_test.go' ! -path '*/vendor/*' ! -path '*/.git/*' 2>/dev/null
    else
        # Handle ./... style patterns
        local dir="${t%%/...}"
        dir="${dir:-.}"
        if [[ -d "$dir" ]]; then
            find "$dir" -name '*.go' ! -name '*_test.go' ! -path '*/vendor/*' ! -path '*/.git/*' 2>/dev/null
        else
            echo "error: path not found: $t" >&2
            exit 2
        fi
    fi
}

VIOLATIONS=()

add_violation() {
    local file="$1" line="$2" rule="$3" message="$4"
    VIOLATIONS+=("${file}:${line}|${rule}|${message}")
}

# Rule 1: SCREAMING_SNAKE_CASE constants
check_screaming_constants() {
    local file="$1"
    local line_num=0
    local in_const_block=false
    while IFS= read -r line; do
        line_num=$((line_num + 1))
        if [[ "$line" =~ ^[[:space:]]*const[[:space:]]*\([[:space:]]*$ ]]; then
            in_const_block=true
            continue
        fi
        if $in_const_block && [[ "$line" =~ ^[[:space:]]*\)[[:space:]]*$ ]]; then
            in_const_block=false
            continue
        fi
        # Match const declarations with ALL_CAPS_SNAKE names (2+ uppercase segments with underscore)
        if $in_const_block; then
            pat='^[[:space:]]*[A-Z][A-Z0-9]*_[A-Z0-9_]+[[:space:]]'
        else
            pat='^[[:space:]]*(const[[:space:]]+)[A-Z][A-Z0-9]*_[A-Z0-9_]+[[:space:]]'
        fi
        if [[ "$line" =~ $pat ]]; then
            local name
            if $in_const_block; then
                name=$(echo "$line" | sed -E -n 's/^[[:space:]]*([A-Z][A-Z0-9]*_[A-Z0-9_]*).*/\1/p')
            else
                name=$(echo "$line" | sed -E -n 's/^[[:space:]]*const[[:space:]]+([A-Z][A-Z0-9]*_[A-Z0-9_]*).*/\1/p')
            fi
            if [[ -n "$name" ]]; then
                add_violation "$file" "$line_num" "screaming-const" "constant '$name' uses SCREAMING_SNAKE_CASE; use MixedCaps instead"
            fi
        fi
    done < "$file"
}

# Rule 2: Get-prefixed getter methods
check_get_prefix() {
    local file="$1"
    local line_num=0
    while IFS= read -r line; do
        line_num=$((line_num + 1))
        # Match: func (r Type) GetFoo(...) — exported getter with Get prefix
        local re_get='^[[:space:]]*func[[:space:]]+\([^)]+\)[[:space:]]+Get([A-Z][a-zA-Z0-9]*)\('
        if [[ "$line" =~ $re_get ]]; then
            local method_name="Get${BASH_REMATCH[1]}"
            # Skip GetX where X could be legitimate (e.g., GetByID is not a simple getter)
            # Only flag simple GetField patterns (no preposition after Get)
            case "${BASH_REMATCH[1]}" in
                By*|From*|Or*|With*|All*) continue ;;
            esac
            add_violation "$file" "$line_num" "get-prefix" "method '$method_name' has Get prefix; Go getters should omit Get (use '${BASH_REMATCH[1]}')"
        fi
    done < "$file"
}

# Rule 3: Packages named util/helper/common/misc
check_bad_package_names() {
    local file="$1"
    local line_num=0
    while IFS= read -r line; do
        line_num=$((line_num + 1))
        pat='^package[[:space:]]+(util|utils|helper|helpers|common|misc|shared|base|lib)$'
        if [[ "$line" =~ $pat ]]; then
            local pkg_name="${BASH_REMATCH[1]}"
            add_violation "$file" "$line_num" "bad-package-name" "package '$pkg_name' is too generic; use a specific, descriptive name"
        fi
        # Only check the first package line
        if [[ "$line" =~ ^package[[:space:]] ]]; then
            break
        fi
    done < "$file"
}

# Rule 4: Receivers named "this" or "self"
check_bad_receivers() {
    local file="$1"
    local line_num=0
    while IFS= read -r line; do
        line_num=$((line_num + 1))
        # Match: func (this *Type) or func (self Type)
        pat='^[[:space:]]*func[[:space:]]+\([[:space:]]*(this|self)[[:space:]]'
        if [[ "$line" =~ $pat ]]; then
            local recv="${BASH_REMATCH[1]}"
            add_violation "$file" "$line_num" "bad-receiver" "receiver named '$recv'; use a short 1-2 letter abbreviation of the type instead"
        fi
    done < "$file"
}

FILES=()
while IFS= read -r f; do
    [[ -n "$f" ]] && FILES+=("$f")
done < <(find_go_files "$TARGET")

if [[ ${#FILES[@]} -eq 0 ]]; then
    if $JSON_OUTPUT; then
        echo '{"violations":[],"total":0,"truncated":false,"status":"no_go_files"}'
    else
        echo "No Go files found in: $TARGET"
    fi
    exit 0
fi

for file in "${FILES[@]}"; do
    check_screaming_constants "$file"
    check_get_prefix "$file"
    check_bad_package_names "$file"
    check_bad_receivers "$file"
done

# Truncation
TOTAL=${#VIOLATIONS[@]}
TRUNCATED=false
if [[ $LIMIT -gt 0 && $TOTAL -gt $LIMIT ]]; then
    VIOLATIONS=("${VIOLATIONS[@]:0:$LIMIT}")
    TRUNCATED=true
fi

# Output results
if $JSON_OUTPUT; then
    echo "{"
    echo '  "violations": ['
    first=true
    for v in "${VIOLATIONS[@]+"${VIOLATIONS[@]}"}"; do
        IFS='|' read -r location rule message <<< "$v"
        file="${location%%:*}"
        line="${location#*:}"
        $first || echo ","
        first=false
        printf '    {"file":"%s","line":%s,"rule":"%s","message":"%s"}' \
            "$(json_escape "$file")" "$line" "$(json_escape "$rule")" "$(json_escape "$message")"
    done
    echo ""
    echo "  ],"
    printf '  "total": %d,\n' "$TOTAL"
    printf '  "truncated": %s\n' "$TRUNCATED"
    echo "}"
else
    if [[ $TOTAL -eq 0 ]]; then
        echo "No naming violations found."
        exit 0
    fi

    echo "Naming violations found:"
    echo ""
    for v in "${VIOLATIONS[@]}"; do
        IFS='|' read -r location rule message <<< "$v"
        printf "  %s  [%s] %s\n" "$location" "$rule" "$message"
    done
    if $TRUNCATED; then
        echo "  ... and $((TOTAL - LIMIT)) more (use --limit to adjust)"
    fi
    echo ""
    echo "Total: $TOTAL violation(s)"
fi

if [[ $TOTAL -gt 0 ]]; then
    exit 1
fi
exit 0
    go-naming | Prompt Minder