Rule: If code repeats 3+ times -> extract to function.
// DUPLICATION - same code 3 times
#[test]
fn test_case_1() {
let parsed = parse_query_string_raw(&raw);
let mut kv_pairs: Vec<(String, String)> = parsed
.into_iter()
.filter(|(k, _)| k != "hash" && k != "signature")
.collect();
kv_pairs.sort_by(|a, b| a.0.cmp(&b.0));
let data_check_string = kv_pairs.iter()...
let secret = Sha256::digest(bot_token.as_bytes());
let mut mac = HmacSha256::new_from_slice(&secret).unwrap();
mac.update(data_check_string.as_bytes());
let computed_hash = hex::encode(mac.finalize().into_bytes());
}
#[test]
fn test_case_2() {
// ... EXACT SAME CODE ...
}
#[test]
fn test_case_3() {
// ... AND AGAIN ...
}After refactoring:
// Function used everywhere
fn compute_telegram_hash(parsed: &BTreeMap<String, String>, bot_token: &str) -> String {
// Code in one place
}
#[test]
fn test_case_1() {
let hash = compute_telegram_hash(&parsed, bot_token); // One line!
}
#[test]
fn test_case_2() {
let hash = compute_telegram_hash(&parsed, bot_token);
}Benefits:
- Code 3 times shorter
- Changes in one place
- Less chance of errors
1. Visually:
- Scroll through file, look for similar blocks
- Watch for copy-paste
2. Automatically:
# cargo-geiger for duplicates
cargo install tokei
tokei --files
# Or cpd (copy-paste detector)3. By patterns:
# Same call chains
rg "\.iter\(\)\.filter.*\.map.*\.collect" --type rustDon't extract to function if:
- Code repeats < 3 times
- Context is different (similar syntax, different semantics)
- Makes code less readable
Example:
// OK - though similar, semantics are different
user.validate_email()?;
admin.validate_email()?;
// Don't need validate_any_email(user_or_admin)Bad names:
// Unclear what this is
fn proc(d: &str) -> i32 { ... }
fn handle(x: Vec<u8>) { ... }
fn do_it(s: &State) { ... }
// Too generic
fn manager() { ... }
fn process_data() { ... }
fn helper() { ... }Good names:
// Clear what it does
fn parse_user_id(raw_id: &str) -> Result<i32> { ... }
fn validate_telegram_data(init_data: &str) -> bool { ... }
fn compute_hmac_signature(message: &[u8]) -> String { ... }Naming rules:
- Functions: verb + what it does (
validate_data,compute_hash) - Variables: noun (
user_id,config,timestamp) - Bool variables: is_/has_/should_ (
is_valid,has_permission) - Constants: SCREAMING_SNAKE_CASE (
MAX_RETRIES,DEFAULT_TIMEOUT)
Problem:
// What does 86400 mean? What's "hash"?
fn validate(raw: &str) -> bool {
let age = get_age(raw);
if age > 86400 { return false; } // What's this number?
let data = parse(raw);
data.get("hash").is_some() // Why "hash"?
}Solution:
// Constants with names
const MAX_AUTH_AGE_SECONDS: u64 = 86400; // 24 hours
const FIELD_HASH: &str = "hash";
fn validate(raw: &str) -> bool {
let age = get_age(raw);
if age > MAX_AUTH_AGE_SECONDS {
return false;
}
let data = parse(raw);
data.get(FIELD_HASH).is_some()
}How to find:
# Numbers in code
rg "\b[0-9]{4,}\b" --type rust # Numbers with 4+ digits
# String literals
rg '"\w+"' --type rustRule: Function should fit on screen (~40-50 lines)
Problem:
// 200 lines - impossible to understand
fn handle_request(req: Request) -> Result<Response> {
// 200 lines of code...
// Parses something...
// Validates something...
// Saves something...
// Sends something...
// ...
}Solution:
// Split into understandable parts
fn handle_request(req: Request) -> Result<Response> {
let data = parse_request(&req)?;
validate_data(&data)?;
let result = process_data(data)?;
save_result(&result)?;
Ok(build_response(result))
}Single Responsibility Principle: One function = one task.
When needed:
// Explains WHY, not WHAT
fn validate_auth_date(timestamp: u64) -> bool {
let age = current_time() - timestamp;
// We check freshness to prevent replay attacks.
// According to Telegram docs: https://...
if age > MAX_AUTH_AGE {
return false;
}
true
}When NOT needed:
// Comment duplicates code
// Increment counter by 1
counter += 1;
// Obvious things
// Get user from database
let user = db.get_user(id);
// BETTER rename for clarity
let authenticated_user = db.get_user(id);By Development Protocol:
- Regular comments FORBIDDEN
- Only
///doc-comments for public API
Must document:
/// Validates Telegram Mini App initData
///
/// # Arguments
///
/// * `raw` - Raw initData string from Telegram Mini App
/// * `hash` - Expected hash value extracted from initData
/// * `bot_token` - Telegram Bot API token
/// * `max_auth_age` - Maximum age of auth_date in seconds
///
/// # Returns
///
/// `true` if data is valid and fresh, `false` otherwise
///
/// # Example
///
/// ```
/// use my_crate::validate_telegram_data;
///
/// let raw = "auth_date=123&user=...";
/// let hash = "abc123...";
/// let is_valid = validate_telegram_data(raw, hash, "BOT_TOKEN", 86400, false);
/// ```
///
/// # Security
///
/// This function validates auth_date freshness to prevent replay attacks.
/// See: <https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app>
pub fn validate_telegram_data(...) -> bool {
// ...
}What to include:
- What the function does
- Arguments (and their format, if not obvious)
- Return value
- Usage example (automatically tested!)
- Special cases (# Panics, # Errors, # Safety)
/// Computes SHA-256 hash of data
///
/// # Example
///
/// ```
/// use my_crate::compute_hash;
///
/// let hash = compute_hash(b"hello");
/// assert_eq!(hash.len(), 64); // SHA-256 = 64 hex chars
/// ```
pub fn compute_hash(data: &[u8]) -> String {
// ...
}Check:
cargo test --docIf example doesn't compile or assertion fails -> test fails!
For large modules:
//! Telegram authentication module
//!
//! This module handles validation of Telegram Mini App initData.
//!
//! # Security
//!
//! - Uses HMAC-SHA256 for signature validation
//! - Validates auth_date freshness to prevent replay attacks
//!
//! # Example
//!
//! ```rust
//! use crate::telegram::validate_telegram_data;
//!
//! let is_valid = validate_telegram_data(...);
//! ```
pub mod validation;
pub mod types;AAA Principle: Arrange, Act, Assert
#[test]
fn test_auth_date_expired() {
// Arrange - prepare data
let bot_token = "test_token";
let old_timestamp = 1640995200;
let raw = format!("auth_date={}&user=...", old_timestamp);
// Act - perform action
let result = validate_telegram_data(&raw, "hash", bot_token, 86400, false);
// Assert - check result
assert!(!result, "Should reject expired auth_date");
}Test:
- Normal case (happy path)
- Boundary values (0, MAX, -1)
- Invalid data
- Empty values
- All if/match branches
Example:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normal_case() {
// Normal case
}
#[test]
fn test_empty_input() {
// Empty string
}
#[test]
fn test_missing_required_field() {
// Missing required field
}
#[test]
fn test_invalid_format() {
// Wrong format
}
#[test]
fn test_boundary_values() {
// 0, MAX_VALUE, MIN_VALUE
}
}For generating many test cases:
use proptest::prelude::*;
proptest! {
#[test]
fn test_parse_never_panics(s in "\\PC*") {
// For ANY string parser should not panic
let _ = parse_data(&s);
}
#[test]
fn test_hash_is_deterministic(data in prop::collection::vec(any::<u8>(), 0..1000)) {
// Hash of same data is always same
let hash1 = compute_hash(&data);
let hash2 = compute_hash(&data);
prop_assert_eq!(hash1, hash2);
}
}Structure:
project/
|-- src/
| +-- lib.rs
+-- tests/ # Integration tests
|-- auth_tests.rs
|-- api_tests.rs
+-- common/ # Common helpers
+-- mod.rs
Example:
// tests/auth_tests.rs
use my_crate::*;
#[test]
fn test_full_auth_flow() {
// Test full flow from start to finish
let app = setup_test_app();
let response = app.request()
.post("/auth/telegram")
.json(&auth_request)
.send();
assert_eq!(response.status(), 200);
// ...
}Each module/function does ONE thing:
// Too many responsibilities
fn handle_user_action(user_id: i64, action: &str, data: &str) -> Result<()> {
// 1. Parses data
// 2. Validates
// 3. Updates DB
// 4. Sends email
// 5. Logs
// 6. Updates cache
// ...
}
// Separated by responsibility
mod parser { /* Parsing */ }
mod validator { /* Validation */ }
mod repository { /* DB */ }
mod notifier { /* Email */ }Testability and flexibility:
// Tightly coupled to specific DB
fn get_user(id: i64) -> Result<User> {
let conn = PostgresConnection::new()?; // Can't substitute in tests
conn.query("SELECT * FROM users WHERE id = $1", &[&id])
}
// Accept abstraction
trait UserRepository {
fn get_user(&self, id: i64) -> Result<User>;
}
fn get_user<R: UserRepository>(repo: &R, id: i64) -> Result<User> {
repo.get_user(id) // Can substitute mock in tests
}Project structure:
src/
|-- handlers/ # HTTP handlers (one file = one endpoint)
| |-- auth.rs
| |-- users.rs
| +-- profiles.rs
|-- models/ # Data structures
| +-- user.rs
|-- repository/ # DB operations
| +-- users.rs
|-- services/ # Business logic
| +-- auth_service.rs
|-- utils/ # Utilities
| +-- telegram.rs
+-- main.rs # Entry point
Rule: Each module solves ONE task.
- Function and variable names clear?
- No magic numbers and strings?
- Functions < 50 lines?
- No deep nesting (< 3-4 levels)?
- No code duplication?
- Common logic extracted to functions?
- Constants used?
- Public API documented?
- Usage examples present?
- Complex parts explained?
- Happy path covered?
- Edge cases covered?
- Error tests present?
- Integration tests for critical logic?
- Modules separated by responsibility?
- Dependencies explicit (not global)?
- Easy to test?
- Easy to extend?
- Test coverage: >= 95% (Protocol requires 100%)
- Cyclomatic complexity: <= 10 per function
- Function length: <= 50 lines
- Nesting: <= 4 levels
- Clippy warnings: 0
# Coverage
cargo tarpaulin --out Xml
# Complexity
cargo install cargo-geiger
cargo geiger
# Metrics
cargo install tokei
tokei
# Quality
cargo clippy -- -D warnings