- Extract shared logic to
common/modules - Use
request_helpersfor common request patterns - Prefer traits over code duplication across types
- When to extract: 2+ occurrences differing only in parameter values (same logic, types, and control flow)
- When NOT to extract: One-time code, or when extraction obscures intent
- One encoder/decoder per message type
- Modules own one domain (accounts, orders, market_data)
- Functions do one thing: encode, decode, validate, or orchestrate
- Max 50 lines per function; extract if larger
- Functions with 4+ parameters should use a builder pattern. The receiver (
self/&self/&mut self) does not count toward this budget —pub fn foo(&self, a, b, c)is compliant;pub fn foo(&self, a, b, c, d)is not. - Builder rationale matters. The rule exists to spare callers from positional-argument noise when there are optional / defaultable fields. A 4+ param function where every arg is required with no reasonable default gains little from a builder —
client.foo(a, b, c, d, e)is no worse thanclient.foo(a).b(b).c(c).d(d).e(e).run(). For all-required signatures, prefer grouping related args into a struct (e.g.DateRange { start, end }) or accept the violation with a comment, rather than mechanical builder conversion.
- Combine small, focused components to build complex behavior
- Use traits to define shared behavior across types
- Compose complex types from smaller building blocks:
// Good: fluent builder when 4+ params, optional params, or complex construction. let order_id = client.order(&contract) .buy(100) .limit(150.0) .condition(price_condition) .submit()?; // Bad: monolithic constructor with 4+ params. let order = Order::new(Action::Buy, 100.0, 150.0, Some(cond), None, None);
- Prefer
impl Traitfor flexible return types - Use newtype wrappers for domain constraints
- Clarity > DRY (if a reader must jump to another file to understand the flow, don't extract)
- SRP > brevity (split even if it adds lines)
- Consistency with existing code > ideal patterns
See extending-api.md#anti-patterns-to-avoid for code examples of these violations.
-
Keep comments concise and avoid redundancy. Don't state the obvious.
- ✅ Good: Complex logic that needs explanation
- ❌ Bad:
// Initialize connectionright beforeConnection::new() - ❌ Bad:
// Set flag to trueright beforeflag = true
-
Inline comments: Use sparingly, only when the code's intent isn't clear
-
Doc comments: Required for all public APIs, should explain the "what" and "why", not the "how"
- No unnecessary comments: Let the code speak for itself
- Meaningful variable names: Prefer descriptive names over comments
- Function length: Keep functions focused and short
- Error handling: Use proper Result types and error propagation
- Avoid unwrap() in production code: Use
?or proper error handling
Always run cargo fmt before committing code. The project uses default rustfmt settings.
Run clippy for both feature flags before committing:
cargo clippy --features sync -- -D warnings
cargo clippy --features async -- -D warnings- Types: PascalCase (e.g.,
MessageBus,AccountSummary) - Functions/Methods: snake_case (e.g.,
connect,server_time) - Constants: UPPER_SNAKE_CASE (e.g.,
MAX_RECONNECT_ATTEMPTS) - Module names: snake_case (e.g.,
market_data,order_management)
Group imports in this order:
- Standard library imports
- External crate imports
- Internal crate imports
- Super/self imports
Use blank lines between groups.