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:
MaxLength not MAX_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() not queue.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 than context.Background() or context.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 — runs golangci-lint with --fix to auto-format and fix lint issues
- Verify
golangci-lint run passes 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 metrics
blockchain.go — chain interaction metrics
indexer.go, payer.go, dbmetrics.go, migrator.go — domain-specific
- Register it in
registerCollectors() in metrics.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:
_total or _counter
- Histogram suffix:
_seconds, _duration
- Gauge suffix:
_gauge or 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
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
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: xmtp-xmtpd-writing-go-code3description: Writing Go Code4---56# Writing Go Code78## Style Priorities (in order)9101. **Clarity** -- purpose and rationale clear to the reader112. **Simplicity** -- simplest way to accomplish the goal123. **Concision** -- high signal-to-noise ratio134. **Maintainability** -- easy to modify correctly145. **Consistency** -- consistent with surrounding codebase1516## Core Rules1718- Follow [Google Go Style Guide](https://google.github.io/styleguide/go/guide)19- Format all code with `golangci-lint fmt`20- MixedCaps/mixedCaps only -- never snake_case (even constants: `MaxLength` not `MAX_LENGTH`)21- No fixed line length -- refactor long lines instead of splitting22- Shorter names in Go than other languages; context reduces need for verbosity23- Comments explain **why**, not what24- Allow code to speak for itself with self-describing symbol names rather than redundant comments25- Use least-powerful mechanism: language primitive > stdlib > external dependency2627## Formatting2829- Imports grouped: stdlib, external, internal (blank line between groups)30- Avoid magic numbers -- use named constants31- No unnecessary levels of abstraction3233## Naming3435- Exported: `PascalCase`36- Unexported: `camelCase`37- Short receiver names (1-2 chars matching type initial)38- Acronyms keep case: `HTTPClient`, `xmlParser`39- Package names: short, lowercase, no underscores, no `util`/`common`/`base`40- Names should not feel repetitive when used: `queue.New()` not `queue.NewQueue()`41- Predictable names -- a user should be able to predict the name in a given context4243## Function Design4445- Keep functions small and focused46- Prefer composition over embedding47- Use functional options pattern for flexible constructors48- Return concrete types, accept interfaces49- Avoid `init()` unless absolutely necessary50- Avoid variable shadowing51- Do not over-nest control flow (flatten with early returns)5253## Testing5455- Table-driven tests where appropriate56- Name tests consistently: `TestXxx`, `BenchmarkXxx`57- Use `t.Helper()` in helper functions58- Use `t.Context()` rather than `context.Background()` or `context.TODO()`59- Avoid global state in tests60- Tests should provide clear, actionable diagnostics on failure6162## Linting and Formatting (MANDATORY)6364After every change to `.go` files:65661. Run `dev/lint-fix` — runs `golangci-lint` with `--fix` to auto-format and fix lint issues672. Verify `golangci-lint run` passes with 0 issues6869Both must pass before the task is considered done. `dev/lint-fix` covers `gofmt`, `gofumpt`, `golines`, and all enabled linters.7071## Metrics7273All metrics live in `pkg/metrics/`. The project uses Prometheus via `github.com/prometheus/client_golang`.7475### Adding a new metric76771. **Declare** the metric variable (unexported) in the appropriate file:78 - `api.go` — API-layer metrics (connections, envelope rates, latency)79 - `sync.go` — node-to-node replication metrics80 - `blockchain.go` — chain interaction metrics81 - `indexer.go`, `payer.go`, `dbmetrics.go`, `migrator.go` — domain-specific822. **Register** it in `registerCollectors()` in `metrics.go`833. **Expose** it via an exported `Emit*` function in the same file844. **Regenerate** the metrics catalog: `dev/gen/metrics-catalog`8586### Metric type guidance8788| Use case | Type |89|---|---|90| Count of events (requests, envelopes, errors) | `Counter` — monotonically increasing, use `_total` suffix |91| Current state (open connections, queue depth) | `Gauge` — can go up and down |92| Duration / latency distribution | `Histogram` — use `_seconds` suffix, observe in seconds |9394### Naming conventions9596- Prefix: `xmtp_<subsystem>_<name>_<type-suffix>`97- Examples: `xmtp_api_outgoing_envelopes_total`, `xmtp_sync_messages_received_count`98- Counter suffix: `_total` or `_counter`99- Histogram suffix: `_seconds`, `_duration`100- Gauge suffix: `_gauge` or none101102### Emit function patterns103104```go105// Counter — simple increment106func EmitFoo() { fooTotal.Inc() }107108// Counter — batch add109func EmitFoos(n int) { fooTotal.Add(float64(n)) }110111// Histogram — observe duration112func EmitFooDuration(d time.Duration) { fooDuration.Observe(d.Seconds()) }113114// Gauge with labels — use With()115func EmitBarGauge(label string, v float64) {116 barGauge.With(prometheus.Labels{"label": label}).Set(v)117}118```119120### Checklist for new metrics121122- [ ] Variable declared and registered in `metrics.go`123- [ ] `Emit*` function exported from `pkg/metrics`124- [ ] Called at the right point (on success, not on error path, unless counting errors)125- [ ] `dev/gen/metrics-catalog` run to update `doc/metrics_catalog.md`126127## Common Pitfalls128129See these references for detailed guidance on frequent Go mistakes:130131- **[Interfaces and type design](interfaces-and-types.md)** -- when designing interfaces, choosing receivers, or structuring types132- **[Error handling patterns](error-handling.md)** -- when handling, wrapping, or comparing errors133- **[Concurrency safety](concurrency-safety.md)** -- when writing goroutines, channels, or shared-state code134- **[Collections and numeric types](collections-and-numerics.md)** -- when working with slices, maps, or numeric conversions135136---137> Converted and distributed by [TomeVault](https://tomevault.io/claim/xmtp) — claim your Tome and manage your conversions.138<!-- tomevault:4.0:skill_md:2026-04-11 -->