go-packages

$npx mdskill add cxuu/golang-skills/go-packages

Organize Go packages and imports for clarity and avoid generic names

  • Group imports correctly to maintain code readability and avoid conflicts.
  • Depends on understanding package structure and import rules in Go.
  • Decides based on naming conventions, avoiding vague terms like 'util'.
  • Delivers organized packages and improved import statements for better maintenance.

SKILL.md

.github/skills/go-packagesView on GitHub ↗
---
name: go-packages
description: Use when creating Go packages, organizing imports, managing dependencies, or deciding how to structure Go code into packages. Also use when starting a new Go project or splitting a growing codebase into packages, even if the user doesn't explicitly ask about package organization. Does not cover naming individual identifiers (see go-naming).
---

# Go Packages and Imports

## Resource Routing

- `references/IMPORTS.md` - Read when grouping imports, using blank imports, dot imports, or import aliases.
- `references/PACKAGE-SIZE.md` - Read when splitting packages, avoiding `init`, structuring `main`, or designing CLI flags/subcommands.

> **When this skill does NOT apply**: For naming individual identifiers within a package, see [go-naming](../go-naming/SKILL.md). For organizing functions within a single file, see [go-functions](../go-functions/SKILL.md). For configuring linters that enforce import rules, see [go-linting](../go-linting/SKILL.md).

## Package Organization

### Avoid Util Packages

Package names should describe what the package provides. Avoid generic names
like `util`, `helper`, `common` — they obscure meaning and cause import
conflicts.

```go
// Good: Meaningful package names
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)

// Bad: Vague names obscure meaning
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)
```

Generic names can be used as *part* of a name (e.g., `stringutil`) but should
not be the entire package name.

### Package Size

| Question | Action |
|----------|--------|
| Can you describe its purpose in one sentence? | No → split by responsibility |
| Do files never share unexported symbols? | Those files could be separate packages |
| Distinct user groups use different parts? | Split along user boundaries |
| Godoc page overwhelming? | Split to improve discoverability |

**Do NOT split** just because a file is long, to create single-type packages, or
if it would create circular dependencies.

---

## Imports

Imports are organized in groups separated by blank lines. Standard library
packages always come first. Use
[goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) to manage this
automatically.

```go
import (
    "fmt"
    "os"

    "github.com/foo/bar"
    "rsc.io/goversion/version"
)
```

**Quick rules:**

| Rule | Guidance |
|------|----------|
| Grouping | stdlib first, then external. Extended: stdlib → other → protos → side-effects |
| Renaming | Avoid unless collision. Rename the most local import. Proto packages get `pb` suffix |
| Blank imports (`import _`) | Only in `main` packages or tests |
| Dot imports (`import .`) | Never use, except for circular-dependency test files |

---

## Avoid init()

Avoid `init()` where possible. When unavoidable, it must be:

1. Completely deterministic
2. Independent of other `init()` ordering
3. Free of environment state (env vars, working dir, args)
4. Free of I/O (filesystem, network, system calls)

**Acceptable uses**: complex expressions that can't be single assignments,
pluggable hooks (e.g., `database/sql` dialects), deterministic precomputation.

---

## Exit in Main

Call `os.Exit` or `log.Fatal*` **only in `main()`**. All other functions should
return errors.

**Why**: Non-obvious control flow, untestable, `defer` statements skipped.

**Best practice**: Use the `run()` pattern — extract logic into
`func run() error`, call from `main()` with a single exit point:

```go
func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}
```

---

## Command-Line Flags

> **Advisory**: Define flags only in `package main`.

- Flag names use `snake_case`: `--output_dir` not `--outputDir`
- Libraries should accept configuration as parameters, not read flags directly —
  this keeps them testable and reusable
- Prefer the standard `flag` package; use `pflag` only when POSIX conventions
  (double-dash, single-char shortcuts) are required

```go
// Good: Flag in main, passed as parameter to library
func main() {
    outputDir := flag.String("output_dir", ".", "directory for output files")
    flag.Parse()
    if err := mylib.Generate(*outputDir); err != nil {
        log.Fatal(err)
    }
}
```

---

## Related Skills

- **Package naming**: See [go-naming](../go-naming/SKILL.md) when choosing package names, avoiding stuttering, or naming exported symbols
- **Error handling across packages**: See [go-error-handling](../go-error-handling/SKILL.md) when wrapping errors at package boundaries with `%w` vs `%v`
- **Import linting**: See [go-linting](../go-linting/SKILL.md) when configuring goimports local-prefixes or enforcing import grouping
- **Global state**: See [go-defensive](../go-defensive/SKILL.md) when replacing `init()` with explicit initialization or avoiding mutable globals

More from cxuu/golang-skills

SkillDescription
go-code-reviewUse when reviewing Go code or checking code against community style standards. Also use proactively before submitting a Go PR or when reviewing any Go code changes, even if the user doesn't explicitly request a style review. Does not cover language-specific syntax — delegates to specialized skills.
go-concurrencyUse when writing concurrent Go code — goroutines, channels, mutexes, or thread-safety guarantees. Also use when parallelizing work, fixing data races, or protecting shared state, even if the user doesn't explicitly mention concurrency primitives. Does not cover context.Context patterns (see go-context).
go-contextUse when working with context.Context in Go — placement in signatures, propagating cancellation and deadlines, and storing values in context vs parameters. Also use when cancelling long-running operations, setting timeouts, or passing request-scoped data, even if they don't mention context.Context directly. Does not cover goroutine lifecycle or sync primitives (see go-concurrency).
go-control-flowUse when writing conditionals, loops, or switch statements in Go — including if with initialization, early returns, for loop forms, range, switch, type switches, and blank identifier patterns. Also use when writing a simple if/else or for loop, even if the user doesn't mention guard clauses or variable scoping. Does not cover error flow patterns (see go-error-handling).
go-data-structuresUse when working with Go slices, maps, or arrays — choosing between new and make, using append, declaring empty slices (nil vs literal for JSON), implementing sets with maps, and copying data at boundaries. Also use when building or manipulating collections, even if the user doesn't ask about allocation idioms. Does not cover concurrent data structure safety (see go-concurrency).
go-declarationsUse when declaring or initializing Go variables, constants, structs, or maps — including var vs :=, reducing scope with if-init, formatting composite literals, designing iota enums, and using any instead of interface{}. Also use when writing a new struct or const block, even if the user doesn't ask about declaration style. Does not cover naming conventions (see go-naming).
go-defensiveUse when hardening Go code at API boundaries — copying slices/maps, verifying interface compliance, using defer for cleanup, time.Time/time.Duration, or avoiding mutable globals. Also use when reviewing for robustness concerns like missing cleanup or unsafe crypto usage, even if the user doesn't mention "defensive programming." Does not cover error handling strategy (see go-error-handling).
go-documentationUse when writing or reviewing documentation for Go packages, types, functions, or methods. Also use proactively when creating new exported types, functions, or packages, even if the user doesn't explicitly ask about documentation. Does not cover code comments for non-exported symbols (see go-style-core).
go-error-handlingUse when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is/As, even if the user doesn't ask about error strategy. Does not cover panic/recover patterns (see go-defensive).
go-functional-optionsUse when designing a Go constructor or factory function with optional configuration — especially with 3+ optional parameters or extensible APIs. Also use when building a New* function that takes many settings, even if they don't mention "functional options" by name. Does not cover general function design (see go-functions).