brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
go-refactoringSafe refactoring workflow for Go: behavior-preserving steps, compiler-checked renames, extracting packages, breaking circular dependencies, and strangler migrations — always behind green tests. Use when: "refactor this", "rename across the codebase", "extract a package", "break this circular dependency", "split this god package", "migrate callers", "clean u…
Independent GitHub snapshot · Automated classification · Original documentation governs
A refactor changes structure, never behavior. The definition of done is mechanical: same tests pass before and after every step, and every step is small enough to revert alone.
go build ./... && go test ./... must be green before
touching anything. If tests are missing around the target code,
write characterization tests FIRST — they pin current behavior,
even if that behavior looks wrong.go vet ./....refactor: commit; they must scrutinize a
mixed one.If step 3 fails and the fix isn't obvious in a minute, revert the step rather than debugging a half-applied transformation.
# Preferred: gopls (understands types, interfaces, embedding)
gopls rename -w internal/service/user.go:#offset newName
# Module path or package import path changes:
# update go.mod, then rewrite imports mechanically
find . -name '*.go' -exec sed -i 's|github.com/acme/old|github.com/acme/new|g' {} +
go build ./... # the compiler is the reviewer
Never rename an exported identifier of a published library without a
deprecation cycle: add the new name, mark the old one
// Deprecated: use NewName., delete in the next major version.
Moving code out of a god package, in compiler-checked steps:
gopls
or cut/paste), leaving everything else.type User = user.User (aliases, =, not definitions).This keeps every commit green with an arbitrarily large caller base.
Packages a → b and b → a won't compile; near-cycles show up as
god packages. Three escapes, in order of preference:
a and b actually depend on a
type — move it to a third package c that imports nothing.store calls back into service,
define the callback interface IN store (consumer side) and let
service implement it. The arrow flips at compile time.For exported functions with many callers:
// Step 1 — add the new form alongside the old
func (s *Service) ProcessCtx(ctx context.Context, id string) error { ... }
// Step 2 — old form delegates; mark deprecated
// Deprecated: use ProcessCtx.
func (s *Service) Process(id string) error {
return s.ProcessCtx(context.Background(), id)
}
// Step 3 — migrate callers batch by batch, building each batch
// Step 4 — delete the old form (same module) or keep until next major (library)
Inside a single module, prefer atomic signature changes when the compiler can find every caller for you: change it, then chase the build errors — that's the compiler enumerating your TODO list.
Replacing a subsystem (old store, legacy client) too big for one PR:
refactor: commits contain no behavior changego build ./... proves it)go vet ./... and the full test suite pass at every commit, not just the last