⭐ featured file

Effective Go

by @pitchinnate · 🖥️ Coding · 3d ago · 37 views

Created a skill file from documentation at https://go.dev/doc/effective_go

ClaudeCodexOpencode codinggolangskill
coding · 288 lines
---
name: go
description: "Use this skill whenever you are writing, reviewing, or refactoring Go code. Triggers include: any request to write a Go function, package, or program; reviewing or improving existing Go code for idiomatic style; questions about Go naming conventions, error handling, interfaces, goroutines, or data structures. Ensures all output follows Effective Go conventions and community best practices. Do NOT use for other languages, general algorithms with no Go context, or infrastructure/tooling tasks unrelated to Go source code."
---

# Go Code Quality Skill

## When to Use This Skill
Use this skill whenever you are **writing, reviewing, or refactoring Go code**. This ensures all output follows idiomatic Go conventions from [Effective Go](https://go.dev/doc/effective_go) and community best practices.

---

## Step 1 — Formatting

- **Always run `gofmt`** (or `go fmt`) mentally when writing code. Tabs for indentation, columns aligned in structs.
- Opening braces go on the **same line** as the control structure — never on the next line.
- No parentheses around `if`, `for`, or `switch` conditions.

```go
// CORRECT
if x > 0 {
    return y
}

// WRONG
if (x > 0)
{
    return y
}
```

---

## Step 2 — Naming Conventions

### Packages
- Lowercase, single-word, no underscores, no mixedCaps: `bufio`, `bytes`, `encoding`.
- Package name is the last element of the import path. `src/encoding/base64` → package name is `base64`.
- Avoid redundancy: if the package is `bufio`, the type is `Reader` not `BufReader` (users call it `bufio.Reader`).

### Variables, Functions, Types
- Use `MixedCaps` or `mixedCaps`, never `snake_case`.
- Exported names start with uppercase; unexported with lowercase.
- Short names are idiomatic for local variables: `i`, `n`, `buf`, `err`.

### Getters and Setters
- Getter: use the field name capitalized — `Owner()`, not `GetOwner()`.
- Setter: `SetOwner()` is fine.

### Interfaces
- Single-method interfaces: name = method + `-er` suffix: `Reader`, `Writer`, `Stringer`, `Formatter`.
- Honor canonical names: if your method reads data, call it `Read`; if it writes, `Write`; if it converts to string, `String` — not `ToString`.

### Constructors
- Named `New` if it's the primary type in a package: `ring.New()`.
- Otherwise `NewFoo`: `NewFile`, `NewReader`.

---

## Step 3 — Error Handling

- **Always check errors**. Never discard with `_` unless there is an explicit, documented reason.
- Return errors as the last return value.
- Use the early-return / guard pattern — keep the happy path flowing down the page.

```go
// CORRECT — errors handled early, no else needed
f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

// WRONG — discarding error
fi, _ := os.Stat(path)
if fi.IsDir() { ... }
```

- Use `if err := ...; err != nil { }` to scope the error variable.

```go
if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}
```

---

## Step 4 — Control Structures

### If
- No unnecessary `else` after a `return`, `break`, `continue`, or `goto`.
- Prefer the initializer form to scope variables tightly.

### For
- Go has one loop keyword. Use `for { }` for infinite loops, `for condition { }` as a while loop.
- Use `range` for slices, arrays, strings, maps, channels — it is idiomatic.
- Use the blank identifier `_` to discard unwanted range values.

```go
for _, value := range slice { ... }
for key := range m { ... }
```

### Switch
- Prefer `switch` over long `if-else-if` chains.
- No automatic fallthrough; comma-separate cases that share a body: `case 'a', 'e', 'i':`.
- A `switch` with no expression switches on `true` — use this for range checks.
- Use labeled breaks (`break Loop`) when you need to exit an outer loop from inside a switch.

### Type Switch
- Use type switches to branch on interface dynamic types:

```go
switch v := i.(type) {
case int:
    // v is int
case string:
    // v is string
default:
    // unknown
}
```

---

## Step 5 — Functions and Methods

### Multiple Return Values
- Return `(value, error)` instead of sentinel values (like `-1` for EOF).
- Named return values are useful documentation but use bare `return` sparingly — only in short functions where it aids clarity.

### Defer
- Use `defer` for cleanup that must always run (closing files, unlocking mutexes).
- Place `defer` immediately after the resource is acquired — keeps open/close visually paired.
- Remember: deferred arguments are evaluated at the `defer` statement, not when the deferred call executes.
- LIFO execution order — the last deferred call runs first.

```go
f, err := os.Open(filename)
if err != nil {
    return err
}
defer f.Close()
```

### Receivers: Pointer vs Value
- Use a **pointer receiver** when the method modifies the receiver, or the struct is large.
- Use a **value receiver** when the method only reads the receiver and it's small/cheap to copy.
- Be consistent: if any method on a type uses a pointer receiver, prefer pointer receivers for all methods on that type.

---

## Step 6 — Data Structures

### new vs make
- `new(T)`: allocates zeroed memory, returns `*T`. Use for structs and scalar types.
- `make(T, ...)`: initializes slices, maps, and channels — returns an initialized (not zeroed) `T` (not `*T`). **Only use `make` for slices, maps, and channels.**

```go
// Slice — use make
s := make([]int, 10, 100)

// Struct — use new or composite literal
p := new(MyStruct)
p := &MyStruct{field: value}
```

### Composite Literals
- Prefer field-labeled composite literals over positional ones for readability and safety.

```go
// PREFERRED
f := &File{fd: fd, name: name}

// FRAGILE — breaks if struct fields are reordered
f := &File{fd, name, nil, 0}
```

### Slices
- Prefer slices over arrays for most sequence work.
- Slices are references — modifications inside a function are visible to the caller.
- Use the built-in `append`; always capture the return value: `s = append(s, elem)`.
- Use `copy` to avoid accidental aliasing when you need an independent copy.

### Maps
- Use the comma-ok idiom to distinguish a missing key from a zero value:

```go
value, ok := m[key]
if !ok { /* key not present */ }
```

- Deleting a key that does not exist is safe: `delete(m, key)`.

### Zero Values
- Design types so their zero value is useful and ready to use (like `sync.Mutex`, `bytes.Buffer`).
- Document when the zero value is valid vs when a constructor is required.

---

## Step 7 — Interfaces

- Keep interfaces small — one or two methods is idiomatic.
- Define interfaces where they are **consumed**, not where they are implemented.
- Accept interfaces, return concrete types (in most cases). Return an interface from a constructor only when you want to hide the implementation.
- A type implicitly satisfies an interface — no `implements` declaration needed.
- Use type assertions with the comma-ok form to avoid panics:

```go
str, ok := value.(string)
```

---

## Step 8 — Concurrency Patterns

- **Share memory by communicating** — prefer channels over shared mutable state.
- Protect shared state with `sync.Mutex` or `sync.RWMutex` when channels aren't the right tool.
- Use `defer mu.Unlock()` immediately after `mu.Lock()`.
- Goroutines are cheap but not free — ensure they can always exit (avoid goroutine leaks).

---

## Step 9 — Packages and Initialization

### init Functions
- Use `init()` to validate preconditions or set up state that cannot be expressed as a declaration.
- Keep `init` functions short and focused. Multiple `init` functions per file are allowed but prefer clarity.

### Import for Side Effects
- Use `import _ "pkg"` only for packages whose `init` side effects are needed (e.g., registering drivers):

```go
import _ "image/png" // register PNG decoder
```

### Blank Identifier Usage
- Use `_` to discard unused return values intentionally (document why).
- Use `var _ InterfaceName = (*MyType)(nil)` to assert interface compliance at compile time.

---

## Step 10 — Documentation

- Every exported name (type, function, method, constant, variable) must have a doc comment.
- Doc comments are full sentences starting with the name being documented.

```go
// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) { ... }
```

- Package-level comment goes in a `doc.go` file or at the top of the main file:

```go
// Package regexp implements regular expression search.
package regexp
```

---

## Quick Anti-Pattern Checklist

Before finalizing any Go code, verify none of these are present:

| Anti-Pattern | Correct Approach |
|---|---|
| `GetFoo()` getter name | `Foo()` |
| `snake_case` names | `mixedCaps` / `MixedCaps` |
| Opening brace on new line | Brace on same line as statement |
| `if (condition)` with parens | `if condition` |
| Discarding errors with `_` | Always check errors |
| `new([]int)` for slices | `make([]int, n)` |
| Positional composite literals | Field-labeled literals |
| Long `if-else-if` chain | `switch` statement |
| Unnecessary `else` after `return` | Omit `else`, keep flow linear |
| Forgetting to `defer` resource cleanup | `defer f.Close()` right after open |
| Interfaces defined in the implementor's package | Define interfaces where consumed |
| `BufReader` in `bufio` package | `Reader` (package name provides context) |
submitted March 31, 2026