From 44c2d1502c257cfd713cfaa0febcff08e18798f4 Mon Sep 17 00:00:00 2001 From: Bluscream Date: Sun, 18 Oct 2026 21:52:48 +0200 Subject: [PATCH] docs(coding): files 1000 lines max, functions 200 lines max, no earlier split point Co-Authored-By: Claude Opus 5.5 --- code-languages/js-typescript.md | 6 +++--- code-languages/python.md | 5 ++--- code-languages/rust.md | 8 ++++++-- coding-dev.md | 32 +++++++++++++++----------------- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/code-languages/js-typescript.md b/code-languages/js-typescript.md index 038a56a..5df7033 100644 --- a/code-languages/js-typescript.md +++ b/code-languages/js-typescript.md @@ -139,9 +139,9 @@ Attach an `AbortController` to every long-lived `fetch` and abort it in cleanup. - **Imports**: prefer the configured alias (`@/…`) over relative climbs. - Indentation and quote/semicolon style: **match the file you are editing**. Do not introduce a new formatter or lint config into a project that already has one. -- **One file until it earns a split** — around 250 lines for a self-contained plugin/feature, - or when a second file is genuinely reusable (`utils.ts`, `types.ts`, `components/`). - Hard limits from the general rules still apply: ESLint's `max-lines` and +- **One file until it earns a split** — when it nears the 1000-line limit, or when a second + file is genuinely reusable (`utils.ts`, `types.ts`, `components/`). + The limits from the general rules (1000 lines per file, 200 per function) apply: ESLint's `max-lines` and `max-lines-per-function` cover both natively, so prefer those over a separate test here. --- diff --git a/code-languages/python.md b/code-languages/python.md index a783880..d55e0e5 100644 --- a/code-languages/python.md +++ b/code-languages/python.md @@ -225,9 +225,8 @@ When talking to a rate-limited API (a chat platform, a store, a tracker): function-length rule — `PLR0915` (`too-many-statements`, default 50) and `C901` (cyclomatic complexity) are the closest proxies and are worth enabling, but neither measures lines. Put **both** the function-length and file-length limits in a pytest - case (see [`coding-dev.md` §4](skills-public/coding-dev.md)): file hard 1000 / soft 600 (soft - prints a note rather than failing), function hard 100 measured by walking the `ast` rather than - counting lines of text. **Guard the guard** with a test asserting the glob matched anything at + case (see [`coding-dev.md` §4](skills-public/coding-dev.md)): file 1000 lines max, function + 200 lines max measured by walking the `ast` rather than counting lines of text. **Guard the guard** with a test asserting the glob matched anything at all — a pattern that matches nothing makes every limit vacuous and still passes. --- diff --git a/code-languages/rust.md b/code-languages/rust.md index 2b869f4..9d32dc9 100644 --- a/code-languages/rust.md +++ b/code-languages/rust.md @@ -73,8 +73,12 @@ workspace = true wrong for the *entire* codebase — not a shortcut for one stubborn call site. It silences the rule in code nobody has written yet. - **Re-test every `#[allow]` periodically.** If removing one produces no warning, delete it. -- `too_many_lines` is the mechanical enforcement of the 100-line function limit. A function - that trips it wants extracting, not an allow. +- `too_many_lines` is the mechanical enforcement of the 200-line function limit. Clippy's + default threshold is 100, so set it in `clippy.toml`: + ```toml + too-many-lines-threshold = 200 + ``` + A function that trips it wants extracting, not an allow. - Clippy has **no file-length lint**, so the 1000-line file limit belongs in a test — see [`coding-dev.md` §4](skills-public/coding-dev.md) for the pattern. Put it in the workspace root's `tests/` so one test covers every member crate, walking `src/` plus each diff --git a/coding-dev.md b/coding-dev.md index c5bdfc5..2d44d79 100644 --- a/coding-dev.md +++ b/coding-dev.md @@ -138,19 +138,23 @@ depends on those. ## 4. Size Limits — Files and Functions -| Unit | Soft target | Split at | Hard limit | -| :--- | :--- | :--- | :--- | -| **Source file** | ~400 lines | 600 lines | **1000 lines** | -| **Function** | under 50 lines | — | **100 lines** | -| **Function parameters** | 3–4 | — | ~5, then pass a struct/object | -| **Nesting depth** | 2 | — | 3, then extract | +| Unit | Limit | +| :--- | :--- | +| **Source file** | **1000 lines** max | +| **Function** | **200 lines** max | +| **Function parameters** | ~5, then pass a struct/object | +| **Nesting depth** | 3, then extract | + +These are the limits, not targets to stay well under: there is no earlier "split at" point. +A file of 700 lines with one coherent responsibility is fine and is not split for its size. ### Enforce both mechanically, never by eye A limit nobody checks is a suggestion. Pick the **highest** applicable option: 1. **A linter rule**, where one exists — the function limit is usually covered natively - (Clippy's `too_many_lines`, ESLint's `max-lines-per-function`, `golangci-lint`'s `funlen`). + (Clippy's `too_many_lines` with `too-many-lines-threshold = 200`, ESLint's + `max-lines-per-function`, `golangci-lint`'s `funlen`), configured to 200 lines. A function that trips it wants extracting, not a suppression. Note that some linters only offer *proxies* for length — Ruff's `PLR0915` counts statements, not lines — in which case enable the proxy and put the real limit in a test. @@ -160,24 +164,18 @@ A limit nobody checks is a suggestion. Pick the **highest** applicable option: repository rather than living in one machine's shell script. 3. **The build script**, only as a fallback when the project has no test suite to hang it on. -Whichever you use, it should **fail over the hard limit** and separately *name* anything past -the soft split point — so the seam gets found while splitting is still cheap, rather than at -the moment the hard limit finally trips. +Whichever you use, it should **fail over the limit** and name the offending files. ```rust #[test] fn no_source_file_exceeds_limits() { - const HARD: usize = 1000; - const WARN: usize = 600; + const LIMIT: usize = 1000; let mut over = Vec::new(); for path in source_files() { let lines = std::fs::read_to_string(&path).unwrap().lines().count(); - if lines > HARD { - over.push(format!("{} — {lines} lines (limit {HARD})", path.display())); - } else if lines > WARN { - // Not a failure: surfaces the seam while splitting is still cheap. - eprintln!("note: {} is {lines} lines, approaching the {HARD} limit", path.display()); + if lines > LIMIT { + over.push(format!("{} — {lines} lines (limit {LIMIT})", path.display())); } }