|
| 1 | +# Go Style Guide |
| 2 | + |
| 3 | +In the URnetwork code, the following Go style is used. A few conventions — notably the `self` receiver name and the verbose usage+type field naming — intentionally diverge from idiomatic Go; they are deliberate house rules, not oversights. |
| 4 | + |
| 5 | +## Naming |
| 6 | + |
| 7 | +- Our receiver name is `self`: `func (self *T) f(...)` (pointer receiver) or `func (self T) f(...)` (value receiver). |
| 8 | +- Our canonical name for a `sync.Mutex` guarding state is `stateLock`. |
| 9 | +- Field and variable names are slightly more verbose than standard Go, so usage and type can be inferred from the name. The scheme is usage + type: |
| 10 | + - Maps: `map[K]V` is `usageKVs` — e.g. `activeNetworkIdUsers` for a `map[Id]User` of active users. Note the plural `s`. |
| 11 | + - Slices: `[]T` is `usageTs` — e.g. `activeNetworkIds` for a `[]Id` of active networks. Note the plural `s`. |
| 12 | + - Variables: `T` is `usageT` — e.g. `activeNetworkId` for the active network `Id`. |
| 13 | + - Use the shortest type token that stays unambiguous: `Id`, not `Identifier`; but never abbreviate past recognition (`activeNetworkIds`, not `activeNetIds`). |
| 14 | + - When the value already implies its type, drop the type token: concrete objects are named by usage alone (`session`, `client`, `transferBalance`). Keep the type when dropping it would confuse an identifier with the object it names — an `Id` stays `clientId`, never `client`. |
| 15 | + |
| 16 | +## Comments |
| 17 | + |
| 18 | +- Prefer short inline comments (`// ...` inside a function), 1–2 lines per point unless more is truly needed. |
| 19 | +- Put a doc comment at the top of each file, type, and function/method declaration, summarizing the architecture and edge cases. These can be as long as needed, but aim to be concise. Push information that applies throughout a type or file up to the type or file header instead of repeating it — in particular, concurrent goroutine safety. |
| 20 | + |
| 21 | +## Concurrency and goroutine safety |
| 22 | + |
| 23 | +- By default, package-level functions are assumed safe for concurrent use, and a type's methods are assumed NOT safe unless the type documents otherwise. |
| 24 | +- Hold a lock across the smallest scope that needs it. The idiom is an immediately-invoked closure: `func() { self.stateLock.Lock(); defer self.stateLock.Unlock(); ... }()`. |
| 25 | +- Every potentially infinite loop must take a context (for cancellation) and rate-limit itself (to avoid busy-spinning). |
| 26 | +- Use `connect.Reconnect` for reconnect rate limiting. |
| 27 | +- Use `connect.Monitor` for change notifications. Grab the channel before reading the monitored state — `update := monitor.NotifyChannel()` — then `select` on it while waiting for changes. Subscribing before the read guarantees an update can't be missed. |
| 28 | + |
| 29 | +## Goroutine lifecycle |
| 30 | + |
| 31 | +- Start a type's internal management goroutines in its constructor, so the returned object is already fully running. The lifecycle loop is conventionally `func (self *T) run()` — lowercase, internal, started by the constructor. |
| 32 | +- Exception: when an external manager must clean up after the lifecycle, expose `func (self *T) Run()` (uppercase) instead. The manager calls `Run()` after construction and tears the object down when `Run()` returns. Casing carries the meaning: lowercase `run()` is internal and self-started; uppercase `Run()` is externally driven. |
| 33 | +- Wait with `time.After` inside the run loop; we don't use `time.Timer`. Reusing a `time.Timer` would save the per-iteration allocation, but we don't optimize for that. |
| 34 | + |
| 35 | +## Formatting and structure |
| 36 | + |
| 37 | +- Format with `gofmt`. |
| 38 | +- Inline single-use helper logic as a local closure at its call site (`f := func(...){ ... }; f()`) rather than pulling it out into a separate named function. |
0 commit comments