| name | go-packages |
| description | Use when creating Go packages, organizing imports, managing dependencies, or structuring a Go project. Covers meaningful package names, package size, import grouping (stdlib first, then external), blank/dot imports, the run() pattern in main, init() restrictions, and CLI flag conventions. Apply proactively when starting a new module or splitting a growing codebase, even if the user did not explicitly ask about package layout. Does not cover identifier naming inside packages (see go-naming). |
| user-invocable | false |
| license | MIT |
| compatibility | Designed for Claude Code or similar AI coding agents. Plain Go (any supported version). |
| metadata | {"author":"muratmirgun","version":"0.1.0","openclaw":{"emoji":"📦","homepage":"https://github.com/muratmirgun/gophers","requires":{"bins":["go"]},"install":[]}} |
| allowed-tools | Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) |
Go Packages and Imports
A package is a unit of meaning, not a folder of files. Name it for what it provides, keep imports tidy, and put startup logic where it belongs.
Core Rules
- Package names describe what the package provides.
util, helper, common, misc are not names.
- Imports are grouped: stdlib first, then external.
goimports will keep this honest.
- Avoid
init() — and when unavoidable, keep it deterministic and I/O-free.
os.Exit / log.Fatal only inside main. Library code returns errors.
- Use the
run() pattern so main has a single exit point and deferred cleanup runs.
- CLI flags belong in
package main. Libraries take configuration as parameters.
- Blank imports belong in
main or tests. Dot imports are essentially never appropriate.
Decision: How to Split a Package
| Question | If "yes" |
|---|
| Can you state the package's purpose in one sentence? | Probably right-sized |
| Do its files never share unexported symbols? | Likely two packages glued by directory |
| Do distinct caller groups touch distinct files? | Split along caller boundaries |
| Is the godoc index so long callers cannot find things? | Split for discoverability |
| Does splitting create import cycles? | Don't split |
Read references/package-layout.md when deciding how to split a growing package, organizing cmd/, internal/, or designing a library API surface.
Naming Packages
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)
Generic words may appear as part of a name (stringutil, iotest) but not as the whole name. Match the package to a concept the caller already knows.
Imports
import (
"fmt"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
| Rule | Guidance |
|---|
| Group order | stdlib, then external; extended order may also separate protos and side-effect imports |
| Renaming | Avoid unless there is a collision; rename the more-local import |
Blank import (import _) | Only main and tests |
Dot import (import .) | Effectively never; rare in test files for circular deps |
Read references/imports-and-main.md for extended import grouping, proto pb suffixes, the run() pattern, and CLI flag conventions.
Avoid init()
When you must use init(), make it:
- Deterministic — same result every run.
- Independent of the order of other
init()s.
- Free of environment state (env vars, working dir, args).
- Free of I/O (filesystem, network, syscalls).
Acceptable uses:
- Precomputing a constant that cannot fit in a single expression.
- Registering pluggable hooks (
database/sql drivers).
If your init reads a file or calls a network API, refactor it into an explicit Setup() the caller invokes.
Exit Only in main
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
return nil
}
Why:
log.Fatal and os.Exit skip defer. Anywhere except main, that means leaked files, half-flushed buffers, undeleted temp dirs.
- The
run() pattern gives you one place to log a clean error and one place to set the exit code.
CLI Flags
- Define flags in
package main.
- Flag names use
snake_case: --output_dir, not --outputDir.
- Libraries accept configuration through function parameters, never reach for
flag.Lookup.
func main() {
outputDir := flag.String("output_dir", ".", "directory for output files")
flag.Parse()
if err := mylib.Generate(*outputDir); err != nil {
log.Fatal(err)
}
}
Read references/init-and-globals.md for the boundaries between safe init-time computation, mutable globals, and dependency injection.
Anti-Patterns
| Anti-pattern | Why it hurts | Do this instead |
|---|
package util | Meaningless name; import conflicts | Name after the concept |
| One huge package with 50 files | Hard to navigate, slow builds | Split by responsibility |
init() reads config from disk | Side effect at import time | Explicit Setup() in main |
log.Fatal in library code | Skips defers, untestable | Return an error |
os.Exit in a request handler | Same — plus crashes the server | Return an error to the framework |
import _ "pkg" in a library | Side effects on every importer | Register explicitly |
import . "pkg" to "save typing" | Tools lose track of where names come from | Use the package qualifier |
| Library reads a flag at import time | Untestable, non-reusable | Accept config as parameter |
Verification Checklist
References