go-packages
Scannednpx machina-cli add skill cxuu/golang-skills/go-packages --openclawGo Packages and Imports
This skill covers package organization and import management following Google's and Uber's Go style guides.
Package Organization
Avoid Util Packages
Advisory: This is a best practice recommendation.
Package names should describe what the package provides. Avoid generic names
like util, helper, common, or similar—they make code harder to read and
cause import conflicts.
// Good: Meaningful package names
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)
// Bad: Vague package names obscure meaning
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)
Generic names like util can be used as part of a name (e.g., stringutil)
but should not be the entire package name.
Package Size
Advisory: This is best practice guidance.
When to combine packages:
- If client code likely needs two types to interact, keep them together
- If types have tightly coupled implementations
- If users would need to import both packages to use either meaningfully
When to split packages:
- When something is conceptually distinct
- The short package name + exported type creates a meaningful identifier:
bytes.Buffer,ring.New
File organization: No "one type, one file" convention in Go. Files should be focused enough to know which file contains something and small enough to find things easily.
Imports
Import Organization
Normative: This is required per Go Wiki CodeReviewComments.
Imports are organized in groups, with blank lines between them. The standard library packages are always in the first group.
package main
import (
"fmt"
"hash/adler32"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
Use goimports to manage this automatically.
Import Grouping (Extended)
Combined: Google + Uber guidance
Minimal grouping (Uber): stdlib, then everything else.
Extended grouping (Google): stdlib → other → protocol buffers → side-effects.
// Good: Standard library separate from external packages
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
// Good: Full grouping with protos and side-effects
import (
"fmt"
"os"
"github.com/dsnet/compress/flate"
"golang.org/x/text/encoding"
foopb "myproj/foo/proto/proto"
_ "myproj/rpc/protocols/dial"
)
Import Renaming
Normative: This is required per Go Wiki CodeReviewComments and Google's Go style guide.
Avoid renaming imports except to avoid a name collision; good package names should not require renaming. In the event of collision, prefer to rename the most local or project-specific import.
Must rename: collision with other imports, generated protocol buffer packages
(remove underscores, add pb suffix).
May rename: uninformative names (e.g., v1), collision with local variable.
// Good: Proto packages renamed with pb suffix
import (
foosvcpb "path/to/package/foo_service_go_proto"
)
// Good: urlpkg when url variable is needed
import (
urlpkg "net/url"
)
func parseEndpoint(url string) (*urlpkg.URL, error) {
return urlpkg.Parse(url)
}
Blank Imports (import _)
Normative: This is required per Go Wiki CodeReviewComments and Google's Go style guide.
Packages that are imported only for their side effects (using import _ "pkg")
should only be imported in the main package of a program, or in tests that
require them.
// Good: Blank import in main package
package main
import (
_ "time/tzdata"
_ "image/jpeg"
)
Dot Imports (import .)
Normative: This is required per Go Wiki CodeReviewComments and Google's Go style guide.
Do not use dot imports. They make programs much harder to read because it is
unclear whether a name like Quux is a top-level identifier in the current
package or in an imported package.
Exception: The import . form can be useful in tests that, due to circular
dependencies, cannot be made part of the package being tested:
package foo_test
import (
"bar/testutil" // also imports "foo"
. "foo"
)
In this case, the test file cannot be in package foo because it uses
bar/testutil, which imports foo. So the import . form lets the file
pretend to be part of package foo even though it is not.
Except for this one case, do not use import . in your programs.
// Bad: Dot import hides origin
import . "foo"
var myThing = Bar() // Where does Bar come from?
// Good: Explicit qualification
import "foo"
var myThing = foo.Bar()
Avoid init()
Source: Uber Go Style Guide
Avoid init() where possible. When init() is unavoidable, code should:
- Be completely deterministic, regardless of program environment
- Avoid depending on ordering or side-effects of other
init()functions - Avoid global/environment state (env vars, working directory, args)
- Avoid I/O (filesystem, network, system calls)
// Bad: init() with I/O and environment dependencies
var _config Config
func init() {
cwd, _ := os.Getwd()
raw, _ := os.ReadFile(path.Join(cwd, "config.yaml"))
yaml.Unmarshal(raw, &_config)
}
// Good: Explicit function for loading config
func loadConfig() (Config, error) {
cwd, err := os.Getwd()
if err != nil {
return Config{}, err
}
raw, err := os.ReadFile(path.Join(cwd, "config.yaml"))
if err != nil {
return Config{}, err
}
var config Config
if err := yaml.Unmarshal(raw, &config); err != nil {
return Config{}, err
}
return config, nil
}
Acceptable uses of init():
- Complex expressions that cannot be single assignments
- Pluggable hooks (e.g.,
database/sqldialects, encoding registries) - Deterministic precomputation
Exit in Main
Source: Uber Go Style Guide
Call os.Exit or log.Fatal* only in main(). All other functions should
return errors to signal failure.
Why this matters:
- Non-obvious control flow: Any function can exit the program
- Difficult to test: Functions that exit also exit the test
- Skipped cleanup:
deferstatements are skipped
// Bad: log.Fatal in helper function
func readFile(path string) string {
f, err := os.Open(path)
if err != nil {
log.Fatal(err) // Exits program, skips defers
}
b, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}
return string(b)
}
// Good: Return errors, let main() decide to exit
func main() {
body, err := readFile(path)
if err != nil {
log.Fatal(err)
}
fmt.Println(body)
}
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
b, err := io.ReadAll(f)
if err != nil {
return "", err
}
return string(b), nil
}
Exit Once
Prefer to call os.Exit or log.Fatal at most once in main(). Extract
business logic into a separate function that returns errors.
// Good: Single exit point with run() pattern
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("missing file")
}
f, err := os.Open(args[0])
if err != nil {
return err
}
defer f.Close() // Will always run
b, err := io.ReadAll(f)
if err != nil {
return err
}
// Process b...
return nil
}
Benefits of the run() pattern:
- Short
main()function with single exit point - All business logic is testable
deferstatements always execute
Quick Reference
| Topic | Rule | Type |
|---|---|---|
| Import organization | std first, groups separated by blank lines | Normative |
| Import grouping | std → other (→ proto → side-effect) | Combined |
| Import renaming | Only when necessary; prefer renaming local/project import | Normative |
| Blank imports | Only in main packages or tests | Normative |
| Dot imports | Only for circular test dependencies | Normative |
| Util packages | Avoid; use descriptive names | Advisory |
| Package size | Balance cohesion vs. distinct concepts | Advisory |
| init() | Avoid; must be deterministic if used | Advisory |
| Exit in main | Only exit from main(); return errors | Advisory |
See Also
- For core style principles:
go-style-core - For naming conventions:
go-naming - For error handling patterns:
go-error-handling - For defensive coding:
go-defensive - For linting tools:
go-linting
Source
git clone https://github.com/cxuu/golang-skills/blob/main/skills/go-packages/SKILL.mdView on GitHub Overview
This skill teaches how to structure Go packages, organize imports, and manage dependencies following Google's and Uber's Go style guides. It covers meaningful package naming, when to group or split packages, and how to handle init() usage and side-effect imports.
How This Skill Works
Packages should have descriptive names rather than generic ones like util; use meaningful boundaries to minimize coupling. Import organization follows grouped blocks with standard library first, then external packages, and goimports can automatically format them. Renaming imports is limited to collision avoidance or readability, and blank imports are reserved for side effects in main or test code.
When to Use It
- Starting a new Go module and deciding how to structure packages and their boundaries.
- Organizing a file's imports when there are many dependencies, including stdlib and external modules.
- Resolving import name collisions or ambiguities by renaming specific imports (pb suffix for protobufs when needed).
- Using blank imports to trigger side effects (e.g., database drivers or tzdata) in the main package or tests.
- Deciding whether to keep related types together or split into separate packages based on conceptual distinctness and API clarity.
Quick Start
- Step 1: Name packages descriptively (avoid generic names like util) and group related types conceptually.
- Step 2: Organize imports with stdlib first, then external, and run goimports to format automatically.
- Step 3: Apply renaming only for collisions or readability; add blank imports in main/tests where side effects are required.
Best Practices
- Avoid generic package names like util, helper, or common; prefer meaningful names that describe what the package provides.
- Follow import grouping: standard library first, then external, with goimports managing formatting automatically.
- Rename imports only to resolve collisions or improve clarity; prefer not to rename unless necessary.
- Use blank imports only in the main package or tests where side effects are required; avoid widespread blank imports elsewhere.
- Balance package size and cohesion: group tightly coupled types together, but avoid forcing unrelated code into a single package.
Example Use Cases
- Good: Meaningful package names, e.g., using db := spannertest.NewDatabaseFromFile(...) instead of a vague test.NewDatabaseFromFile(...); keep io.SeekStart usage meaningful.
- Import grouping: separate stdlib imports from external packages, and place third-party packages after stdlib in the import block.
- Extended grouping example: group protos and side-effect imports distinctly, e.g., foopb "myproj/foo/proto/proto" and _ "myproj/rpc/protocols/dial" for init-time side effects.
- Import renaming: demonstrate proto packages renamed to pb suffix to avoid collisions, e.g., foosvcpb "path/to/package/foo_service_go_proto".
- Blank imports: show using _ imports in main to include time/tzdata or image/jpeg for side effects.