Skip to main content

readable-cpp

Readable C/C++/Rust/CUDA code rules inspired by The Art of Readable Code. Use when writing, reviewing, or refactoring C, C++, Rust, or CUDA code. Enforces short functions, flat control flow, clear naming, readable structure, and idiomatic patterns.

Source facts

Repository
crazyguitar/cppcheatsheet
Last source activity
April 4, 2026 at 06:03
Detected SKILL.md language
English
Stars
287
Forks
51

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
readable-cpp
description
Readable C/C++/Rust/CUDA code rules inspired by The Art of Readable Code. Use when writing, reviewing, or refactoring C, C++, Rust, or CUDA code. Enforces short functions, flat control flow, clear naming, readable structure, and idiomatic patterns.
# Readable C/C++/Rust/CUDA Rules (/readable-cpp) Apply these rules when writing, reviewing, or refactoring C, C++, Rust, or CUDA code. Inspired by *The Art of Readable Code* by Dustin Boswell and Trevor Foucher. **Core principle: Code should be easy to understand.** The time it takes someone else (or future you) to understand the code is the ultimate metric. ## 1. Keep Functions Short and Focused - A function should do **one thing**. If you can describe what it does with "and", split it. - Aim for functions that fit on one screen (~15-25 lines). If it's longer, extract sub-tasks. - Each function should operate at a **single level of abstraction** — don't mix high-level logic with low-level details in the same function. ## 2. Flatten Control Flow — No Deep Nesting - **Never nest more than 2 levels deep.** If you have a loop inside a loop, or an `if` inside a loop inside an `if`, extract the inner block into a helper function with a descriptive name. - Use **early returns / guard clauses** to handle edge cases at the top, keeping the main logic flat. - Prefer `continue` or `break` to skip iterations rather than wrapping the body in a conditional. - Replace complex conditionals with well-named helper functions or variables that explain the intent. ```cpp // Bad: nested and hard to follow for (auto& user : users) { if (user.is_active()) { for (auto& order : user.orders()) { if (order.is_pending()) { process(order); } } } } // Good: flat, each function name explains what it does auto active_users = get_active_users(users); for (auto& user : active_users) { process_pending_orders(user.orders()); } ``` ## 3. Name Things Clearly - **Pack information into names.** Use specific, concrete words — `fetch_page` not `get`, `num_retries` not `n`. - **Avoid generic names** like `tmp`, `data`, `result`, `val`, `info`, `handle` — unless the scope is tiny (2-3 lines). - **Use names that can't be misconstrued.** If a range is inclusive, say `max_items` not `limit`. If a boolean, use `is_`, `has_`, `should_`, `can_` prefixes. - **Match the name length to the scope.** Short names for small scopes, descriptive names for wide scopes. - **Don't use abbreviations** unless they're universally understood (`num`, `max`, `min`, `err` are fine; `svc_mgr_cfg` is not). ## 4. Make Control Flow Easy to Follow - Put the **changing/interesting value on the left** side of comparisons: `if (length > 10)` not `if (10 < length)`. - Order `if/else` blocks: **positive case first**, simpler case first, or the more interesting case first. - Minimize the number of variables the reader has to track. Reduce the **mental footprint** of each block. - Avoid deeply nested ternary operators — if it's not immediately obvious, use an `if/else`. ## 5. Break Down Giant Expressions - Use **explaining variables** to break complex expressions into named pieces. - Use **summary variables** to capture a long expression that's used more than once. - Apply **De Morgan's laws** to simplify negated boolean expressions. ```cpp // Bad if (!(age >= 18 && has_id && !is_banned)) { deny(); } // Good bool is_eligible = age >= 18 && has_id && !is_banned; if (!is_eligible) { deny(); } ``` ## 6. Extract Unrelated Subproblems - If a block of code is solving a **subproblem unrelated to the main goal** of the function, extract it. - The helper function should be **pure and self-contained** — it shouldn't need to know about the calling context. - This is the single most effective way to improve readability: separate *what* you're doing from *how*. ## 7. One Task at a Time - Each section of code should do **one task**. If a function is doing parsing AND validation AND transformation, split them into separate steps. - List the tasks a function does. If there's more than one, reorganize so each task is in its own block or function. ## 8. Reduce Variable Scope - **Declare variables close to where they're used.** Don't declare at the top of a function if it's only used 30 lines later. - **Minimize the "live time" of a variable** — the fewer lines between its assignment and last use, the easier it is to follow. - **Prefer write-once variables.** Variables that are assigned once and never modified are easier to reason about. - **Eliminate unnecessary variables.** If a variable is used only once and doesn't clarify anything, inline it. ## 9. No Magic Numbers or Strings - Replace **magic numbers and strings** with named constants: `if (retries > MAX_RETRIES)` not `if (retries > 3)`. - If a value has meaning, give it a name. The name documents the intent. - Group related constants together. ## 10. Fewer Function Arguments - Aim for **3 or fewer arguments** per function. More than that is a smell. - Group related arguments into a **struct, class, or tuple**. - If a function needs many config-like options, pass a single config/options object. - Boolean flag arguments are a sign the function does two things — split it instead. ## 11. Consistency - If the codebase does something one way, **do it the same way**. Don't mix styles. - Consistent naming patterns, consistent structure, consistent error handling. - When joining an existing codebase, **match the existing conventions** even if you'd prefer a different style. - Surprise is the enemy of readability — predictable code is readable code. ## 12. Write Less Code - The best code is **no code at all**. Question whether a feature is truly needed before implementing. - **Don't over-engineer.** Solve the problem at hand, not hypothetical future problems. - Remove dead code. Commented-out code is dead code. - Use standard libraries before writing custom solutions. ## 13. Comments: Explain Why, Not What - Don't comment **what** the code does — the code already says that. Comment **why** it does it. - Comment **flaws and workarounds**: `// TODO:`, `// HACK:`, `// XXX:` with explanation. - Comment **surprising behavior** or non-obvious decisions — things where a reader would ask "why?". - **Don't comment bad code — rewrite it.** If you need a comment to explain what a block does, extract it into a well-named function instead. ## 14. Design Code to Survive Auto-Formatting - Write code that looks good **after** the auto-formatter runs. If a chained expression or repeated pattern would be broken across 4+ lines by the formatter, extract a helper function instead. - **Prefer one-line helper calls** over long inline chains that the formatter will expand vertically. - The formatter is your reader's first impression. Run it *before* committing — if the result looks ugly, that's a signal to refactor, not to disable the formatter. ```rust // Bad: rustfmt expands this to 4 lines per field — noisy and repetitive fn from_dict(cfg: &Bound<'_, PyDict>) -> PyResult<Self> { Ok(Self { rom: cfg.get_item("rom")?.ok_or_else(|| missing("rom"))?.extract()?, // ... each field becomes 4 lines after rustfmt }) } // Good: extract a helper so each field stays one clean line fn get_required<T: FromPyObject>(cfg: &Bound<'_, PyDict>, key: &str) -> PyResult<T> { cfg.get_item(key)? .ok_or_else(|| PyKeyError::new_err(key.to_string()))? .extract() } fn from_dict(cfg: &Bound<'_, PyDict>) -> PyResult<Self> { Ok(Self { rom: get_required(cfg, "rom")?, actions: get_required(cfg, "actions")?, }) } ``` ```cpp // Bad: clang-format wraps this into a hard-to-scan block auto result = container.find(key)->second.get_value().transform(func).value_or(default_val); // Good: name the intermediate step auto& entry = container.find(key)->second; auto result = entry.get_value().transform(func).value_or(default_val); ``` --- # C-Specific Rules ## 15. RAII-Like Patterns with goto Cleanup - In C, use the **goto cleanup pattern** for resource management — allocate at the top, clean up at a single labeled block at the bottom. - Never scatter `free()` calls across multiple return paths. A single cleanup section is easier to audit. - Use `__attribute__((cleanup))` (GCC/Clang) when available for automatic cleanup. ```c // Good: single cleanup path int process_file(const char *path) { int ret = -1; FILE *fp = fopen(path, "r"); if (!fp) return -1; char *buf = malloc(BUF_SIZE); if (!buf) goto cleanup_file; // ... do work ... ret = 0; cleanup_buf: free(buf); cleanup_file: fclose(fp); return ret; } ``` ## 16. Use `const` Liberally - Mark pointers `const` when the function doesn't modify the pointed-to data: `const char *msg`. - Mark local variables `const` when they don't change after initialization. - This documents intent and helps the compiler catch mistakes. ## 17. Prefer Sized Types for Data Structures - Use `<stdint.h>` types (`uint32_t`, `int64_t`) for data that crosses boundaries (files, network, hardware). - Use `size_t` for sizes and counts, `ptrdiff_t` for pointer differences. - Use `int` and `unsigned` for simple loop counters and local arithmetic. ## 18. Defensive Macro Hygiene - Wrap macro bodies in `do { ... } while(0)` for statement-like macros. - Parenthesize all macro parameters: `#define SQUARE(x) ((x) * (x))`. - Prefer `static inline` functions over macros when possible (type safety, debuggability). - Use `_Generic` (C11) for type-safe "overloading" instead of macro tricks. --- # C++-Specific Rules ## 19. Use RAII for All Resources - Every resource (memory, file handles, locks, sockets) should be owned by an RAII object. - Use `std::unique_ptr` for exclusive ownership, `std::shared_ptr` only when shared ownership is genuinely needed. - Write custom RAII wrappers for non-standard resources (e.g., C library handles). - Never use raw `new`/`delete` in application code — let smart pointers and containers handle it. ## 20. Prefer Value Semantics and Move - Pass small objects by value, large objects by `const&`. - Return objects by value — rely on RVO/NRVO and move semantics. - Implement move constructors/assignment for types that own resources. - Use `std::move` only when you truly want to transfer ownership — don't `std::move` from things you'll use again. ## 21. Use Modern C++ Over C Idioms - Use `std::array` over C arrays, `std::string` over `char*`, `std::vector` over `malloc`/`realloc`. - Use `std::optional` over sentinel values, `std::variant` over type-unsafe unions. - Use range-based `for` loops: `for (const auto& item : container)`. - Use structured bindings (C++17): `auto [key, value] = *map.begin();`. - Use `std::format` (C++20) or `fmt::format` over `sprintf` / string concatenation. ## 22. Templates: Keep It Simple - Use concepts (C++20) to constrain templates — errors become readable. - Prefer `if constexpr` over SFINAE when possible. - Don't write template metaprogramming unless the benefit is clear and the team can maintain it. - A non-template solution that's slightly less generic is often better than a template solution nobody understands. ## 23. Use `constexpr` and `const` Aggressively - Mark functions `constexpr` when they can be evaluated at compile time. - Use `constexpr` variables instead of `#define` for constants. - Use `const` on member functions that don't modify state. - `consteval` (C++20) for functions that *must* be compile-time evaluated. ## 24. Error Handling: Pick One Pattern - Use exceptions for truly exceptional conditions, `std::expected` (C++23) or `std::optional` for expected failures. - Don't mix error codes and exceptions in the same layer. - If using exceptions, make them specific — derive from `std::runtime_error`, not `std::exception`. - Use `noexcept` on functions that cannot throw (destructors, move operations). --- # Rust-Specific Rules ## 25. Embrace the Ownership Model - Don't fight the borrow checker — redesign your data flow instead. - Prefer passing references (`&T`, `&mut T`) over cloning. Clone only when ownership transfer is genuinely needed. - Use lifetimes explicitly only when the compiler can't infer them — don't annotate unnecessarily. - Prefer `&str` over `String` in function parameters when you don't need ownership. ## 26. Use Iterators and Combinators - Prefer iterator chains (`.iter().filter().map().collect()`) over manual loops with indices. - Use `for item in &collection` instead of `for i in 0..collection.len()`. - Use `enumerate()`, `zip()`, `chain()`, `chunks()` — the iterator API is rich. - Avoid `.unwrap()` in production code — use `?`, `unwrap_or`, `unwrap_or_else`, or pattern matching. ```rust // Bad: manual indexing let mut names = Vec::new(); for i in 0..users.len() { if users[i].is_active { names.push(users[i].name.clone()); } } // Good: idiomatic iterator chain let names: Vec<_> = users.iter() .filter(|u| u.is_active) .map(|u| u.name.clone()) .collect(); ``` ## 27. Use Enums and Pattern Matching - Use `enum` with data variants instead of class hierarchies or tagged unions. - Use `match` exhaustively — the compiler ensures you handle all cases. - Use `if let` / `while let` for single-variant matching instead of full `match`. - Prefer `Result<T, E>` over panicking — make errors part of the type signature. ## 28. Leverage the Type System - Use **newtype wrappers** (`struct UserId(u64)`) to prevent mixing up same-typed values. - Use `Option<T>` instead of sentinel values or null pointers. - Use `#[must_use]` on functions whose return values shouldn't be ignored. - Prefer `From`/`Into` traits for type conversions over manual conversion functions. ## 29. Module Organization - Keep `pub` surfaces small — expose only what's needed. - Use `pub(crate)` for crate-internal visibility instead of full `pub`. - Group related types and functions in modules — one concept per module. - Re-export key types at the crate root for ergonomic imports. --- # CUDA-Specific Rules ## 30. Name Kernels and Device Functions Clearly - Kernel names should describe **what** they compute, not that they're kernels: `reduce_sum` not `kernel1` or `myKernel`. - Use a consistent naming convention to distinguish execution spaces: e.g., `reduce_sum_kernel` for `__global__`, `warp_reduce` for `__device__` helpers.
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub