# Golang

> Go coding standards and conventions for this project. Apply when writing, reviewing, or refactoring any Go source file.

- Skill: `jandedobbeleer/golang` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jandedobbeleer/golang`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jandedobbeleer/golang/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: jandedobbeleer (https://skillmd.com/u/jandedobbeleer)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jandedobbeleer/golang

---


# Go Development Instructions

Follow idiomatic Go practices and community standards when writing Go code.
These instructions are based on [Effective Go](https://go.dev/doc/effective_go),
[Go Code Review Comments](https://go.dev/wiki/CodeReviewComments),
and [Google's Go Style Guide](https://google.github.io/styleguide/go/).

## General Instructions

- Write simple, clear, and idiomatic Go code
- Favor clarity and simplicity over cleverness
- Follow the principle of least surprise
- Keep the happy path left-aligned (reduce indentation)
- Return early to reduce nesting
- Make the zero value useful
- Document exported types, functions, methods, and packages
- Use Go modules for dependency management
- **AVOID `else` statements - use early returns, continue, or break instead**
- Avoid wrapping primitives without a clear semantic benefit; define new types when they add meaning.
- Use typed slices/maps and document element semantics when not obvious.
- Start error strings with a lowercase letter.

## Naming Conventions

### Packages

- Use lowercase, single-word package names
- Avoid `_` characters, hyphens, or mixedCaps
- Choose names that describe what the package provides, not what it contains
- Avoid generic names like `util`, `common`, or `base`
- Package names should be singular, not plural

### Variables and Functions

- Use mixedCaps or MixedCaps (camelCase) rather than `_` characters
- Keep names short but descriptive
- Use single-letter variables for very short scopes (like loop indices)
- Exported names start with a capital letter
- Unexported names start with a lowercase letter
- Avoid stuttering (e.g., avoid `http.HTTPServer`, prefer `http.Server`)

### Interfaces

- Name interfaces with -er suffix when possible (e.g., `Reader`, `Writer`, `Formatter`)
- Single-method interfaces should be named after the method (e.g., `Read` → `Reader`)
- Keep interfaces small and focused

### Constants

- Use MixedCaps for exported constants
- Use mixedCaps for unexported constants
- Group related constants using `const` blocks
- Consider using typed constants for better type safety

## Code Style and Formatting

### Formatting

- Always use `gofmt` to format code
- Use `goimports` to manage imports automatically
- Keep line to 180 max at all times
- Add blank lines to separate logical groups of code

### Comments

- Write comments in complete sentences
- Start sentences with the name of the thing being described
- Package comments should start with "Package [name]"
- Use line comments (`//`) for most comments
- Use block comments (`/* */`) sparingly, mainly for package documentation
- Document why, not what, unless the what is complex

### Error Handling

- Check errors immediately after the function call
- Don't ignore errors using `_` unless you have a good reason (document why)
- Wrap errors with context using `fmt.Errorf` with `%w` verb
- Create custom error types when you need to check for specific errors
- Place error returns as the last return value
- Name error variables `err`
- Keep error messages lowercase and don't end with punctuation

### Logging

- Always use the codebase `log` package for logging
- Log errors at the point they occur using `log.Error(err)`
- Do not format the errors, let the `log` package handle it
- For complex function calls, use `defer log.Trace(time.Now(), args)`
    where args are the function arguments at the start of the function.

### Control Flow

- **NEVER use `else` statements** - they create unnecessary nesting and reduce readability
- Use early returns to handle error cases and edge conditions first
- Use `continue` in loops to skip to the next iteration instead of nesting
- Use `break` to exit loops early instead of complex conditional logic
- Keep the main logic (happy path) left-aligned with minimal indentation

**❌ BAD - Don't do this:**

```go
func processEntry(entry *Entry) string {
    if entry.Expired() {
        return "expired"
    } else {
        if entry.TTL < 0 {
            return "never expires"
        } else {
            return fmt.Sprintf("expires at %s", time.Unix(entry.Timestamp, 0))
        }
    }
}
```

**✅ GOOD - Do this instead:**

```go
func processEntry(entry *Entry) string {
    if entry.Expired() {
        return "expired"
    }

    if entry.TTL < 0 {
        return "never expires"
    }

    return fmt.Sprintf("expires at %s", time.Unix(entry.Timestamp, 0))
}
```

**❌ BAD - Nested loop logic:**

```go
for _, item := range items {
    if item.IsValid() {
        if item.ShouldProcess() {
            // complex processing logic
        }
    }
}
```

**✅ GOOD - Early continue:**

```go
for _, item := range items {
    if !item.IsValid() {
        continue
    }
    if !item.ShouldProcess() {
        continue
    }

    // complex processing logic (happy path)
}
```

## Architecture and Project Structure

### Package Organization

- Follow standard Go project layout conventions
- Group related functionality into packages
- Avoid circular dependencies

### Dependency Management

- Use Go modules (`go.mod` and `go.sum`)
- Keep dependencies minimal
- Regularly update dependencies for security patches
- Use `go mod tidy` to clean up unused dependencies
- Vendor dependencies when necessary

## Type Safety and Language Features

### Type Definitions

- Define types to add meaning and type safety
- Use struct tags for JSON, YAML and TOML on exported fields
- Prefer explicit type conversions
- Use type assertions carefully and check the second return value

### Pointers vs Values

- Use pointers for large structs or when you need to modify the receiver
- Use values for small structs and when immutability is desired
- Be consistent within a type's method set
- Consider the zero value when choosing pointer vs value receivers

### Interfaces and Composition

- Accept interfaces, return concrete types
- Keep interfaces small (1-3 methods is ideal)
- Use embedding for composition
- Define interfaces close to where they're used, not where they're implemented
- Don't export interfaces unless necessary

## Concurrency

### Goroutines

- Don't create goroutines in libraries; let the caller control concurrency
- Always know how a goroutine will exit
- Use `sync.WaitGroup` or channels to wait for goroutines
- Avoid goroutine leaks by ensuring cleanup

### Channels

- Use channels to communicate between goroutines
- Don't communicate by sharing memory; share memory by communicating
- Close channels from the sender side, not the receiver
- Use buffered channels when you know the capacity
- Use `select` for non-blocking operations

### Synchronization

- Use `sync.Mutex` for protecting shared state
- Keep critical sections small
- Use `sync.RWMutex` when you have many readers
- Prefer channels over mutexes when possible
- Use `sync.Once` for one-time initialization

## Error Handling Patterns

### Creating Errors

- Use `errors.New` for simple static errors
- Use `fmt.Errorf` for errors with runtime values
- Create custom error types for domain-specific errors
- Export error variables for sentinel errors
- Use `errors.Is` and `errors.As` for error checking

### Error Propagation

- Add context when propagating errors up the stack
- Don't log and return errors (choose one)
- Handle errors at the appropriate level
- Consider using structured errors for better debugging

## Performance Optimization

### Memory Management

- Minimize allocations in hot paths
- Reuse objects when possible (consider `sync.Pool`)
- Use value receivers for small structs
- Preallocate slices when size is known
- Avoid unnecessary string conversions

### Profiling

- Use built-in profiling tools (`pprof`)
- Benchmark critical code paths
- Profile before making performance changes
- Focus on algorithmic improvements first
- Consider using `testing.B` for benchmarks

## Testing

### Test Organization

- Keep tests in the same package (white-box testing)
- Use `_test` package suffix for black-box testing
- Name test files with `_test.go` suffix
- Place test files next to the code they test

### Writing Tests

- Name tests descriptively using `TestFunctionNameScenario`
- Use subtests with `t.Run` for better organization
- Test both success and error cases
- Use `testify/assert` and `testify/require` for assertions
- Include both positive and negative test cases
- Test edge cases and error conditions
- When including a standard library that conflicts with an existing import,
  use the lib(library name) pattern to avoid conflicts.
  Such as: `libtime` for the `time` package.

#### Test behavior, not the patch

Before adding a test, name the observable behavior or invariant it proves. A regression test must fail on
the code before the fix for the same reason as the reported bug, pass after the fix, and permit correct internal
refactors.

Do not add tests that inspect source code or embedded text for a symbol, statement, condition, string, or the relative
order of statements in the code. Such tests prove that a patch has a particular shape, not that the behavior works.
Text assertions are appropriate when the emitted text is itself the contract, such as generated commands,
escaping, serialization, or required output encoding.

Test behavior in the runtime that owns it. A Go test must not inspect an embedded shell script to claim that shell host
behavior works; use a shell integration test instead. If the available infrastructure cannot exercise the regression,
state the missing coverage and required manual verification rather than adding a proxy test that cannot catch the bug.

Use these checks for every new test:

1. Could the original bug still occur while this test passes?
2. Could a correct refactor make this test fail?

If either answer is yes, redesign or remove the test.

#### Table-driven tests are the default

One behavior under test = one test function with a table of cases. Never write several
near-identical test functions that differ in input data, fixtures, or expected outcome, because
those differences are table fields. A per-case fixture (a different map, config, or mock return)
is not a reason to split; put the fixture in the table. Shared setup (mocks, caches, `Init`
calls) runs once before the loop.

When adding cases to an existing test file, extend the existing table instead of adding a new
test function.

Split into separate test functions when the flow genuinely differs: a different API under
test, or a setup/assertion sequence that cannot be expressed as table fields.

```go
// ✅ CORRECT: fixture and error expectation are table fields
cases := []struct {
    Fixture       Palette
    Case          string
    Input         Ansi
    Expected      Ansi
    ExpectedError bool
}{
    {Case: "literal", Fixture: Palette{"a": "#123456"}, Input: "p:a", Expected: "#123456"},
    {Case: "invalid", Fixture: Palette{"a": "{{ broken"}, Input: "p:a", ExpectedError: true},
}

// ❌ WRONG: TestResolveLiteral, TestResolveReference, TestResolveInvalid —
// three functions repeating the same setup with different data
```

### Test Helpers

- Mark helper functions with `t.Helper()`
- Create test fixtures for complex setup
- Use `testing.TB` interface for functions used in tests and benchmarks
- Clean up resources using `t.Cleanup()`

#### Global state: always save the original value and restore it

When a test mutates package-level variables (resolvers, loggers, clocks, `time.Local`, etc.),
save the original value into a local variable and restore it via `t.Cleanup`. Never restore to a
hardcoded value; you would overwrite whatever state preceded your test.

```go
// ✅ CORRECT: save original, restore original
origResolver := myPackageResolver
t.Cleanup(func() { myPackageResolver = origResolver })
myPackageResolver = fakeResolver

origLocal := time.Local
t.Cleanup(func() { time.Local = origLocal })
time.Local = time.UTC

// ❌ WRONG: restores to a hardcoded value instead of the pre-test value
defer func() { time.Local = time.FixedZone("UTC", 0) }()
```

## Security Best Practices

### Input Validation

- Validate all external input
- Use strong typing to prevent invalid states
- Sanitize data before using in SQL queries
- Be careful with file paths from user input
- Validate and escape data for different contexts (HTML, SQL, shell)

### Cryptography

- Use standard library crypto packages
- Never write your own cryptography
- Use crypto/rand for random number generation
- Store passwords using bcrypt or similar
- Use TLS for network communication

## Documentation

### Code Documentation

- Document all exported symbols
- Start documentation with the symbol name
- Use examples in documentation when helpful
- Keep documentation close to code
- Update documentation when code changes

### README and Documentation Files

- Include clear setup instructions
- Document dependencies and requirements
- Provide usage examples
- Document configuration options
- Include troubleshooting section

## Tools and Development Workflow

### Essential Tools

- `go fmt`: Format code
- `go vet`: Find suspicious constructs
- `golint` or `golangci-lint`: Additional linting
- `go test`: Run tests
- `go mod`: Manage dependencies
- `go generate`: Code generation

### Development Practices

- Run tests before committing
- Use pre-commit hooks for formatting and linting
- Keep commits focused and atomic
- Write clear, descriptive commit messages
- Review diffs before committing

### Pre-Commit Quality Gate

**REQUIRED BEFORE EVERY COMMIT.** Run the following commands in sequence after any Go code
change. Commit after all pass with zero errors. Never skip this step; these
linters catch real bugs and style violations that will be flagged in CI or code review.

1. **Code Modernization**: Apply modern Go best practices; this rewrites files in place

   ```bash
   modernize --fix "./..."
   ```

   > `modernize` modifies source files (e.g. replacing `strings.Split`
   > with `strings.SplitSeq` for Go 1.24+ range loops). Always stage its changes and
   > include them in the same commit as your feature code.

2. **Field Alignment**: Optimize struct field ordering for memory efficiency; this rewrites files in place

   ```bash
   fieldalignment --fix "./..."
   ```

   > **Warning:** `fieldalignment` rewrites struct field order. Any inline struct
   > literals that use **positional** (unnamed) field initialization (common in
   > table-driven test files) will break after the reorder.
   > **Always use named fields** in struct literals (e.g. `{Case: "foo", Now: t}`)
   > so that the order of fields in the struct definition does not matter.

3. **Dependency Management**: Clean up and organize module dependencies

   ```bash
   go mod tidy
   ```

4. **Formatting and Linting**: Ensure code follows standards (**must report zero errors**)

   ```bash
   gofmt -w .
   golangci-lint run
   ```

After steps 1 through 3, always run `git diff` to review auto-applied changes before staging them.
All four steps must complete with zero errors before the commit is created.

#### Platform-specific files (`_unix.go`, `_windows.go`, `_darwin.go`, `_js.go`)

These suffixes, and an explicit `//go:build` constraint, are how a file is bound to one target.
The toolchain type-checks and lints the files selected for the host and nothing else, so a
violation in another platform's file ships silently.

If you add or modify such a file, also cross-compile to catch issues the local OS linter skips.
On Windows, run:

```powershell
$env:GOOS = "linux"; go build ./...; $env:GOOS = ""
```

On Linux/macOS, run:

```bash
GOOS=windows go build ./...
```

This catches import mismatches, missing symbols, and linter rules (like `modernize`
`strings.SplitSeq`) that apply on the non-host platform. Run `golangci-lint run` under the same
`GOOS` so the lint rules are covered too, not the build alone.

#### Common golangci-lint violations to fix before committing

These rules frequently fire on new code and are quick to resolve before linting:

| Linter | Trigger | Fix |
| ------ | ------- | --- |
| `goconst` | Same string literal occurs 3+ times | Extract to a named `const` |
| `gofmt` | Incorrect indentation or comment spacing | Run `gofmt -w .`; it fixes automatically |
| `dupl` | Two functions/test cases with near-identical structure | Add `//nolint:dupl` with a brief reason comment |
| `modernize` | `strings.Split` used in a `for range` (Go 1.24+) | Run `modernize --fix "./..."` (auto-fixes) |

## Common Pitfalls to Avoid

- Not checking errors
- Ignoring race conditions
- Creating goroutine leaks
- Not using defer for cleanup
- Modifying maps concurrently
- Confusing nil interfaces with nil pointers
- Forgetting to close resources (files, connections)
- Using global variables unnecessarily
- Over-using empty interfaces (`interface{}` or `any`)
- Not considering the zero value of types

