Skip to content

Commit a0d3cde

Browse files
authored
Improve style guide and blocking policy (#533)
Update the style guide with a focus on super-performance, readability, and a stricter blocking policy. This includes detailed guidance on instrumentation, performance, and code clarity.
1 parent 1f537c5 commit a0d3cde

File tree

1 file changed

+40
-170
lines changed

1 file changed

+40
-170
lines changed

.gemini/styleguide.md

Lines changed: 40 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,76 @@
1-
# Gemini Code Assistant - Style Guide (Performance‑First)
1+
# Gemini Code Assistant – Style Guide
2+
## Performance-First, Readability-Always (Super-Performant & Clear)
23

3-
> TL;DR: Treat performance as a feature. Prefer zero/lowoverhead code paths, avoid accidental allocations, and never pay logging costs on hot paths unless explicitly gated.
4+
> TL;DR: Treat performance as a feature **and** clarity as a constraint. We ship **super-performant code** that is easy to read, easy to change, and hard to misuse. Prefer zero/low-overhead paths, avoid accidental allocations, and never pay logging costs on hot paths unless explicitly gated. Keep control flow shallow and intent obvious.
45
56
---
67

78
## Quick Facts (Tracing Semantics You Must Know)
89

910
- **Default `#[instrument]` level is `debug`.**
1011
- Tracing levels are ordered: `error > warn > info > debug > trace`. Enabling `info` **does not** enable `debug`.
11-
- **Field expressions inside `#[instrument(... fields(...))]` are evaluated at function entry** (macro expansion time for that call site), before any subscriber decides to record or drop the span. If those expressions do heavy work (e.g., `pretty_print`, `format!`, `to_string`, serialization), you **pay that cost** regardless of whether the span is ultimately recorded.
12-
- Therefore, any nontrivial work inside `fields(...)` is a footgun even when you think "it's only at `debug`."
12+
- **Field expressions inside `#[instrument(... fields(...))]` are evaluated at function entry** (before any subscriber decides to record or drop the span). If those expressions do heavy work (e.g., `pretty_print`, `format!`, `to_string`, serialization), you **pay that cost** regardless of whether the span is recorded.
13+
- Therefore, any non-trivial work inside `fields(...)` is a foot-gun even when you think “it’s only at `debug`.
1314

1415
---
1516

16-
## Blocking Policy (exact)
17+
## Super-Performance Ethos
1718

18-
Gemini must **request changes (block the PR)** when any of the following appear:
19+
- **Zero-cost baseline:** When disabled, observability adds ~0 runtime cost.
20+
- **Hot-path budget ≈ near-zero:** Guard everything non-trivial, minimize allocs, avoid dynamic dispatch and stringification in loops, keep data on the stack/arenas/Bytes where possible.
21+
- **Measure, don’t guess:** PRs that plausibly affect p95/p99 latency or memory should include micro-benchmarks or before/after numbers.
22+
- **Prefer predictable memory:** Borrow over own, interning for high-cardinality strings, `SmallVec/ArrayVec` where sizes are bounded, arena/bump alloc for structured lifetimes.
1923

20-
- **A. `#[tracing::instrument]` uses the default level (`info`) on hot paths.**
21-
`info` spans are commonly enabled in production. Span creation + any eager field evaluation will run on hot paths. Use `level = "trace"` for hot code and record richer details lazily (see patterns below) when a higher level is actually enabled.
24+
---
2225

23-
- **B. `#[tracing::instrument]` uses `level = "debug"` (or higher verbosity) and either condition holds:**
24-
1) **`skip_all` is missing**, or
25-
2) **`fields(...)` performs any computation** (function/method calls, formatting, allocation, serialization, cloning, traversals).
26-
Even if the runtime filter disables that `debug` span, those field expressions are still evaluated at function entry - wasted work.
26+
## Blocking Policy (exact)
2727

28-
- **C. Any heavy work is performed just to log/trace on a hot path** without a guard (e.g., `tracing::enabled!` or a feature flag).
28+
Gemini must **request changes (block the PR)** when any of the following appear:
2929

30-
> **Baseline for hot paths:** `#[instrument(skip_all, level = "trace", fields(... cheap only ...))]` + gated/lazy recording for anything expensive or no instrumentation at all.
30+
### Tracing / Instrumentation
31+
- **A. `#[instrument]` at `level = "info"` (or higher) on hot paths.**
32+
`info` is commonly enabled in prod; span creation + eager field eval will run in hot code. Use `level = "trace"` on hot paths and record richer details lazily when enabled.
33+
- **B. `#[instrument]` at `debug` (default) on hot paths without `skip_all`, or with computed fields.**
34+
Even if disabled at runtime, `fields(...)` are evaluated. Require `skip_all` and **cheap fields only**.
35+
- **C. Any heavy work is performed just to log/trace on a hot path** without a guard (`tracing::enabled!`, feature flag, or config knob).
36+
37+
> **Hot-path baseline:**
38+
> `#[instrument(skip_all, level = "trace", fields(... cheap only ...))]` + gated/lazy recording for anything expensive, or no instrumentation.
39+
40+
### Readability / Simplicity
41+
- **D. Deeply nested control flow** (more than **2 levels** on hot paths, **3** elsewhere) when it can be flattened with guard clauses, early returns, or `match`.
42+
- **E. Large monolithic functions** (> ~80 lines, or doing multiple responsibilities) when they can be split into focused helpers.
43+
- **F. Clever/opaque iterator chains** that obscure intent or allocate inadvertently; prefer a clear `for` loop or smaller helpers.
44+
- **G. Non-idiomatic error handling:** `unwrap/expect` in non-test code (except proven cold init/CLI); missing context on propagated errors where it matters.
45+
- **H. Naming that hides intent:** cryptic abbreviations, misleading words, type-hiding via wild generics without good reason.
46+
- **I. Unbounded growth patterns:** maps/vectors with unbounded cardinality in hot paths without explicit caps/eviction.
47+
- **J. Unsafe without proof:** `unsafe` blocks without precise safety comments, tests, or measurable wins.
3148

3249
---
3350

3451
## Goals
3552

36-
- **Always review PRs through a performance lens.**
37-
- **Block** changes that add avoidable runtime overhead (allocations, stringification, extra clones, unnecessary async/await boundaries, unbounded maps/vecs, etc.).
38-
- **Strict rules for `tracing`** so instrumentation doesn't silently tax hot paths.
39-
- **Review PRs through a "best-practices in Rust" lens.**
53+
- **Performance-first:** avoid avoidable runtime overhead (allocations, cloning, stringification, needless async boundaries, dynamic dispatch in tight loops).
54+
- **Readability-always:** shallow control flow, small focused functions, explicit intent, idiomatic Rust.
55+
- **Strict `tracing` rules:** instrumentation must not silently tax hot paths.
56+
- **Ergonomic APIs:** make the correct thing easy and the slow/confusing thing hard.
4057

4158
---
4259

4360
## Default Review Posture
4461

45-
- If the change touches a **hot path** (request handling, routing, query planning, JSON/IO, allocators, pools), assume **budget = near zero**.
62+
- If code touches a **hot path** (request handling, routing, query planning, JSON/IO, allocators, pools), assume **budget = near zero**.
4663
- If a PR adds logging/tracing/metrics:
4764
- Confirm **runtime cost ≈ 0** when disabled or below the current level.
48-
- If cost is non‑trivial, **require gating** (`tracing::enabled!`, feature flags) or relocation to cold paths.
49-
- Prefer **benchmarks or micro‑benchmarks** for anything that plausibly affects p95/p99 latency or memory.
65+
- If cost is non-trivial, **require gating** or relocation to cold paths.
66+
- Prefer **benchmarks/micro-benchmarks** for any plausible p95/p99 or memory impact.
67+
- Enforce **nesting depth limits** and **function size** constraints unless there’s a strong, documented reason.
5068

5169
---
5270

5371
## `tracing` Rules (Allowed vs Banned)
5472

55-
### ✅ Allowed (safe‑by‑default)
56-
73+
### ✅ Allowed (safe-by-default)
5774
```rust
5875
use tracing::instrument;
5976

@@ -63,150 +80,3 @@ async fn handle(user: &User, req: &Request) -> Result<Response> {
6380
tracing::trace!(%user.id, %req.id, "entered handler");
6481
Ok(...)
6582
}
66-
```
67-
68-
- `level = "trace"` on `instrument` for hot paths.
69-
- `skip_all` + **explicit, cheap fields** (`%`/`?` over already‑available, small values).
70-
- No allocations or heavy computation in attribute arguments.
71-
72-
### ❌ Banned (examples)
73-
74-
```rust
75-
// 1) Default level (info) + no skip_all.
76-
#[instrument] // reject
77-
fn hot_path(a: &Big, b: &Bigger) { /* ... */ }
78-
79-
// 2) Debug‑level span with expensive field computation.
80-
#[instrument(level = "debug", fields(details = %self.pretty_print()))] // reject
81-
fn parse(&self) { /* ... */ }
82-
83-
// 3) Any format/to_string/clone/serialize in fields
84-
#[instrument(skip_all, level="info", fields(blob = %format!("{:?}", self.blob)))] // reject
85-
async fn foo(self) { /* ... */ }
86-
```
87-
88-
---
89-
90-
## Patterns for Safe, Lazy Recording
91-
92-
When you truly need expensive fields, **gate them** and **record lazily**:
93-
94-
### Record after `enabled!` check
95-
96-
```rust
97-
use tracing::{self, Level};
98-
99-
#[instrument(skip_all, level = "trace", fields(details = tracing::field::Empty))]
100-
fn process(&self) {
101-
if tracing::enabled!(Level::DEBUG) {
102-
// Compute only when actually enabled at runtime.
103-
let details = self.pretty_print(); // expensive
104-
tracing::Span::current().record("details", &tracing::field::display(details));
105-
}
106-
// ...
107-
}
108-
```
109-
110-
---
111-
112-
## Field Hygiene
113-
114-
- **Cheap only:** ids, small scalars, already‑borrowed references.
115-
- **Forbidden in fields:** `.pretty_print()`, `format!`, `.to_string()`, (de)serialization, cloning big structs, traversing collections.
116-
- Prefer `fields(foo = %id)` over `fields(foo = id.to_string())`.
117-
118-
---
119-
120-
## Reviewer Checklist (what Gemini should look for)
121-
122-
- [ ] Any `#[instrument]`?
123-
- [ ] Uses `skip_all`? If **no****request changes**.
124-
- [ ] Uses `level = "trace"` for hot paths? If default or `level = "info"` on hot code → **request changes**.
125-
- [ ] **No function calls/allocs** inside `fields(...)`? If present → **request changes** with Pattern A/B.
126-
- [ ] New logs/metrics in hot paths?
127-
- [ ] Gated via `enabled!` or a feature flag?
128-
- [ ] New `clone()` / `to_string()` / `collect()` on hot paths? Ask for justification or refactor.
129-
- [ ] Async boundaries added? Avoid splitting critical sections if not needed.
130-
- [ ] Allocations: prefer stack/arena/borrowed views; avoid intermediate `String`/`Vec` unless necessary.
131-
- [ ] Config toggles: if a costly path is optional, require a **feature flag** or runtime knob.
132-
133-
---
134-
135-
## Comment Templates (Gemini → PR)
136-
137-
**1) Instrumentation level & missing `skip_all`**
138-
139-
> Performance check: `#[instrument]` here uses the default level (`info`) and/or doesn't specify `skip_all`.
140-
> This creates spans at common prod filters and may evaluate fields eagerly.
141-
> Please switch to:
142-
> ```rust
143-
> #[instrument(skip_all, level = "trace", fields(... only cheap fields ...))]
144-
> ```
145-
> If you need richer context, record it after an `enabled!` guard.
146-
147-
**2) Expensive field computation**
148-
149-
> The `fields(...)` contains an eager computation (`pretty_print`/`format!`/`to_string`).
150-
> These are evaluated at function entry even if the span isn't ultimately recorded.
151-
> Please either:
152-
> - Gate with `tracing::enabled!(Level::DEBUG)` + `Span::current().record(...)`, or
153-
> - Use a lazy wrapper and still gate if expensive (seePatterns for Safe, Lazy Recording”).
154-
155-
**3) Hotpath logging**
156-
157-
> New logs on a hot path detected. Can we gate by level/feature or move to a colder edge?
158-
> Aim for zero cost when disabled.
159-
160-
---
161-
162-
## QuickFix Snippets Gemini Can Suggest
163-
164-
- Replace:
165-
```rust
166-
#[instrument]
167-
```
168-
With:
169-
```rust
170-
#[instrument(skip_all, level = "trace")]
171-
```
172-
173-
- Replace (eager fields):
174-
```rust
175-
#[instrument(skip_all, level="debug", fields(details = %self.pretty_print()))]
176-
```
177-
With (gated record):
178-
```rust
179-
#[instrument(skip_all, level="trace", fields(details = tracing::field::Empty))]
180-
fn f(&self) {
181-
if tracing::enabled!(tracing::Level::DEBUG) {
182-
let d = self.pretty_print();
183-
tracing::Span::current().record("details", &tracing::field::display(d));
184-
}
185-
}
186-
```
187-
188-
---
189-
190-
## When It's Okay to Use `info`
191-
192-
- Truly **cold** admin/maintenance paths (schema reload, health checks, CLI tools) **may** use `level = "info"` with **cheap** fields only.
193-
- Still **avoid** expensive field computation in attributes; prefer gated record.
194-
195-
---
196-
197-
## Rationale (why we're strict)
198-
199-
- `#[instrument]` **creates spans** and **evaluates its `fields(...)`** at function entry. With the default `info` level, this happens under typical production filters; with `debug`, the field expressions are still evaluated even if the span is dropped later.
200-
- Stringification/pretty printing can dominate latency on tight loops; a handful of these across hot paths quickly becomes a measurable tax.
201-
- The safe baseline (`skip_all`, `level="trace"`, gated recording) keeps the lights on without sacrificing debuggability when you need it.
202-
203-
---
204-
205-
## PR Author Checklist (pre‑submit)
206-
207-
- [ ] No default/`info`/`debug` `#[instrument]` on hot paths unless justified and still `skip_all` + cheap fields only.
208-
- [ ] Every `#[instrument]` has `skip_all`.
209-
- [ ] No function calls/allocs in `fields(...)`.
210-
- [ ] Hot‑path logs/metrics are gated or relocated.
211-
212-
---

0 commit comments

Comments
 (0)