Go Developer
Idiomatic Go implementation at ChainSafe. The full reference is languages/go/developer.md; load that file for complete guidance. This skill is the operational summary.
Tooling baselines
- Linter:
golangci-lintpinned to a specific version in CI. Use.golangci.ymlat repo root. - Mocking:
golang/mock(gomock). Per-package, kept inmocks_test.go(generated) +mocks_generate_test.go(the//go:generatedirective). Never export mocks. - Modules:
go.mod+go.sumcommitted;go.sumnever edited by hand. - Race detector:
go test -racein CI for any package touching concurrency.
Mock discipline (most-flagged in review)
- No
gomock.Any(). Use concrete arguments or custom matchers. - No
.AnyTimes(). State exact call counts. - Always set
.Return(...)for functions that return. - For subtests, build a fresh
gomock.NewController(t)per subtest. Mocks built from parenttleak between subtests. - Use functional mock builders (
func(ctrl *gomock.Controller) Fetcher) for table-driven tests with mocks.
Argument passing
- Default to pass-by-value. Reduces nil-risk; clearer behavior.
- Slices: by value if you mutate elements;
*[]Tonly to change slice length. - Maps, channels, most interfaces: always by value (pointers under the hood).
- Structs: by value if performance allows; by pointer if you need to mutate non-pointer fields or if >80 bytes.
- Mutexes: always by pointer.
Panic
panicis only for programmer error — impossible states, sentinel-invariant violation, default switch on enum.- I/O errors, network errors, parse failures, user input — return
error. Never panic. recoveronly in test code (assert.PanicsWithValue).
Context
func DoThing(ctx context.Context, arg T) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// ...
}
ctx is the first parameter. Always. Never context.TODO() in committed code. Pass ctx down; do not store in structs except in narrow framework cases.
Every external call — network, database, stream read — gets an explicit deadline via context.WithTimeout (or a socket/read deadline where ctx isn't threaded through). An inherited ctx with no deadline is not a timeout.
Errors
data, err := fetch(ctx, url)
if err != nil {
return fmt.Errorf("fetching %s: %w", url, err)
}
%wto preserve the chain.- Lowercase first letter, no trailing punctuation.
- Context before the error:
"fetching %s: %w", not"%w while fetching". - Callers use
errors.Is/errors.As.
Testing
- Table-driven tests for >1 case. Map-keyed for free naming; subtests for isolation.
t.Parallel()when tests don't share mutable state.go test -race ./...for race-sensitive packages.
CI baseline
go vet ./...gofmt -l .→ no outputgolangci-lint rungo test -race ./...go build ./...- Mocks regenerated;
git diff --exit-codeconfirms no drift.
Anti-patterns
panicfor non-fatal errors.- Goroutines without
context.Context. init()connecting to external services.interface{}/anyparameters (use generics where possible).- Mocks with
gomock.Any(). - Package-level mutable state.
Related
- Full reference:
languages/go/developer.md - Idioms cheat sheet:
languages/go/idioms.md - Gotchas:
languages/go/gotchas.md - Sister roles:
chainsafe-go-architect,chainsafe-go-reviewer - Workflow:
chainsafe-research-plan-implement