name: writing-go-code description: >- Use when writing, modifying, or reviewing .go files, implementing new Go functions or packages, or when Go code style and conventions are relevant.
Writing Go Code
Style Priorities (in order)
- Clarity -- purpose and rationale clear to the reader
- Simplicity -- simplest way to accomplish the goal
- Concision -- high signal-to-noise ratio
- Maintainability -- easy to modify correctly
- Consistency -- consistent with surrounding codebase
Core Rules
- Follow Google Go Style Guide
- Format all code with
golangci-lint fmt - MixedCaps/mixedCaps only -- never snake_case (even constants:
MaxLengthnotMAX_LENGTH) - No fixed line length -- refactor long lines instead of splitting
- Shorter names in Go than other languages; context reduces need for verbosity
- Comments explain why, not what
- Allow code to speak for itself with self-describing symbol names rather than redundant comments
- Use least-powerful mechanism: language primitive > stdlib > external dependency
Formatting
- Imports grouped: stdlib, external, internal (blank line between groups)
- Avoid magic numbers -- use named constants
- No unnecessary levels of abstraction
Naming
- Exported:
PascalCase - Unexported:
camelCase - Short receiver names (1-2 chars matching type initial)
- Acronyms keep case:
HTTPClient,xmlParser - Package names: short, lowercase, no underscores, no
util/common/base - Names should not feel repetitive when used:
queue.New()notqueue.NewQueue() - Predictable names -- a user should be able to predict the name in a given context
Function Design
- Keep functions small and focused
- Prefer composition over embedding
- Use functional options pattern for flexible constructors
- Return concrete types, accept interfaces
- Avoid
init()unless absolutely necessary - Avoid variable shadowing
- Do not over-nest control flow (flatten with early returns)
Testing
- Table-driven tests where appropriate
- Name tests consistently:
TestXxx,BenchmarkXxx - Use
t.Helper()in helper functions - Use
t.Context()rather thancontext.Background()orcontext.TODO() - Avoid global state in tests
- Tests should provide clear, actionable diagnostics on failure
Linting and Formatting (MANDATORY)
After every change to .go files:
- Run
dev/lint-fix— runsgolangci-lintwith--fixto auto-format and fix lint issues - Verify
golangci-lint runpasses with 0 issues
Both must pass before the task is considered done. dev/lint-fix covers gofmt, gofumpt, golines, and all enabled linters.
Metrics
All metrics live in pkg/metrics/. The project uses Prometheus via github.com/prometheus/client_golang.
Adding a new metric
- Declare the metric variable (unexported) in the appropriate file:
api.go— API-layer metrics (connections, envelope rates, latency)sync.go— node-to-node replication metricsblockchain.go— chain interaction metricsindexer.go,payer.go,dbmetrics.go,migrator.go— domain-specific
- Register it in
registerCollectors()inmetrics.go - Expose it via an exported
Emit*function in the same file - Regenerate the metrics catalog:
dev/gen/metrics-catalog
Metric type guidance
| Use case | Type |
|---|---|
| Count of events (requests, envelopes, errors) | Counter — monotonically increasing, use _total suffix |
| Current state (open connections, queue depth) | Gauge — can go up and down |
| Duration / latency distribution | Histogram — use _seconds suffix, observe in seconds |
Naming conventions
- Prefix:
xmtp_<subsystem>_<name>_<type-suffix> - Examples:
xmtp_api_outgoing_envelopes_total,xmtp_sync_messages_received_count - Counter suffix:
_totalor_counter - Histogram suffix:
_seconds,_duration - Gauge suffix:
_gaugeor none
Emit function patterns
// Counter — simple increment
func EmitFoo() { fooTotal.Inc() }
// Counter — batch add
func EmitFoos(n int) { fooTotal.Add(float64(n)) }
// Histogram — observe duration
func EmitFooDuration(d time.Duration) { fooDuration.Observe(d.Seconds()) }
// Gauge with labels — use With()
func EmitBarGauge(label string, v float64) {
barGauge.With(prometheus.Labels{"label": label}).Set(v)
}
Checklist for new metrics
- Variable declared and registered in
metrics.go -
Emit*function exported frompkg/metrics - Called at the right point (on success, not on error path, unless counting errors)
-
dev/gen/metrics-catalogrun to updatedoc/metrics_catalog.md
Common Pitfalls
See these references for detailed guidance on frequent Go mistakes:
- Interfaces and type design -- when designing interfaces, choosing receivers, or structuring types
- Error handling patterns -- when handling, wrapping, or comparing errors
- Concurrency safety -- when writing goroutines, channels, or shared-state code
- Collections and numeric types -- when working with slices, maps, or numeric conversions