anti-slop-code
The prose rules in anti-slop cover text a developer reads. This covers the
code and the documentation around it.
Every rule here is a code-quality rule with a reason. None of them detects
authorship. Human code carries all of these patterns too, and a rule that fires
on "this looks generated" is a rule that fires on clean idiomatic code, on a
junior developer's first pass, and on anyone writing English as a second
language. Judge the artifact.
When to Use
- Before you commit code you or a model just wrote
- Reviewing a large diff, especially one you did not type
- Writing a README, an API reference, a docstring, or a commit body
- Cleaning a file that reads padded and you cannot say why
The Rules
Comments
A comment earns its place by carrying a fact the reader cannot get from the
code. Delete every comment that fails that test.
See references/comments.md. The short version:
| Cut |
Keep |
| Paraphrase of the line below |
The constraint that forced the shape |
// Step 1: // Step 2: narration |
The invariant a reader would break |
| A docstring restating the signature |
The failure this code already shipped |
| The story of how you found the answer |
The lock order, the null-row count, the limit |
| Deferral text: "for now", "temporary" |
A ponytail: marker naming the ceiling |
| Hedges: "should work", "hopefully" |
A measured number |
Structure
- Delete vacuous code:
if True, x == x, if flag == True, an if/else
returning True/False for a value the condition already holds, a branch
with the same body on both sides, except E as e: raise e.
- Write the code the task needs. Extra statements around the same task are the
most measurable tell in the literature, and they cost review time forever.
- Extract a function when logic repeats across files. Re-deriving near-identical
logic in a nearby scope is the failure mode, and it reads as thoroughness.
- Delete a
try/except around code that cannot throw. Name the exception you
expect and let the rest crash.
- Delete a null check on a value that a type or a caller already guarantees.
- One variable per value. Two names holding the same thing is a rename left
half-done.
- Collapse runs of blank lines inside a function body. One blank line separates
two ideas; three separate nothing.
Architecture rules that earn their keep: dependencies point one way, IO sits at
the edges, errors get translated once at the layer that owns the driver, a
constructor does no work, an abstraction waits for its second implementation.
Over-abstraction (an interface with one implementation, a factory for one
product, config for a constant that never changes) belongs to the ponytail
skill, which already owns that ground. Use it rather than duplicating it here.
Those patterns are ordinary over-engineering and predate any model.
See references/structure.md.
Imports and APIs
Resolve every import against the lockfile or the registry before you claim the
code runs. An invented package name is the one failure here with no judgment in
it: the package exists or it does not.
Same for a method on a library type. Read the signature, then call it.
Tests
- Write the test before the code. A test written after the code passes because the code runs, bugs included. A test written first defines the behavior the code must satisfy. The order matters: test → red → code → green → refactor.
- State the expected outcome from the requirement, not from running the code. An assertion built by pasting output encodes today's behavior. A test that went green on the first run never proved anything.
- A red test is a claim that the code is wrong. Fix the code. Change the test only when you can name why the expectation was wrong, and put that reason in the commit body.
- Never weaken an assertion to reach green. Loosening
== to is not None, widening a tolerance, skipping the case, deleting the case, or catching the exception the test exists to prove: each makes the suite green and the code no more correct.
- Test behavior at boundaries, not every line. A pure function with no branches needs one test. A function with three branches needs three. A getter needs zero. Coverage measures lines that ran, not bugs that would be caught; a 100% covered codebase with no boundary tests catches nothing.
- Assert behaviour, not implementation. A test that asserts which internal method was called in which order breaks on a refactor that changed nothing, and passes when the behaviour is wrong. Mock what hits the network, the clock, or the disk, and nothing else.
- No tautologies.
assert True, assert x == x, an expected value produced by calling the code under test.
- Cover the error path, which is where the bugs are and where a single happy-path test stops.
See references/tests.md.
Documentation
- A docstring on an exported symbol carries the contract: what it returns, what
it raises, what it does not handle. A docstring that restates the signature in
English carries nothing.
- A README says what the thing does, how to run it, and what breaks. A feature
tour with a section per capability is marketing.
- A commit body says what changed and why. The investigation goes nowhere.
- A PR description says what the diff does. Claiming a change the diff does not
contain is the expensive version of this mistake.
- No emoji section markers, no badge walls, no title case in headings, unless
the repo already does it. Match the file you are in.
See references/docs.md.
Checking
# comments and docstrings, extracted from source
python3 tools/lint.py --code src/
# structural checks that need a parse
python3 tools/ast_check.py src/
The linter reports what a regex can see. Everything above it needs a reader.
Boundaries
Will:
- Delete comments that carry no fact
- Cut defensive code around paths that cannot fail
- Rewrite tests that assert implementation into tests that assert behaviour
- Strip feature-tour prose from a README
Will not:
- Claim a file was machine-written
- Remove a comment that records a constraint, a bug, or an external cause
- Remove error handling at a trust boundary, or around IO that genuinely fails
- Flatten a complex solution to a genuinely complex problem
- Impose its own house style on a repo that already has one
1---2name: anti-slop-code3description: Audit and trim code itself — narrator comments, defensive boilerplate, dead generality, tests that assert mocks, docstrings that restate the signature. Use for source files; use anti-slop for prose.4---56# anti-slop-code78The prose rules in `anti-slop` cover text a developer reads. This covers the9code and the documentation around it.1011Every rule here is a code-quality rule with a reason. None of them detects12authorship. Human code carries all of these patterns too, and a rule that fires13on "this looks generated" is a rule that fires on clean idiomatic code, on a14junior developer's first pass, and on anyone writing English as a second15language. Judge the artifact.1617## When to Use1819- Before you commit code you or a model just wrote20- Reviewing a large diff, especially one you did not type21- Writing a README, an API reference, a docstring, or a commit body22- Cleaning a file that reads padded and you cannot say why2324## The Rules2526### Comments2728A comment earns its place by carrying a fact the reader cannot get from the29code. Delete every comment that fails that test.3031See `references/comments.md`. The short version:3233| Cut | Keep |34|---|---|35| Paraphrase of the line below | The constraint that forced the shape |36| `// Step 1:` `// Step 2:` narration | The invariant a reader would break |37| A docstring restating the signature | The failure this code already shipped |38| The story of how you found the answer | The lock order, the null-row count, the limit |39| Deferral text: "for now", "temporary" | A `ponytail:` marker naming the ceiling |40| Hedges: "should work", "hopefully" | A measured number |4142### Structure4344- Delete vacuous code: `if True`, `x == x`, `if flag == True`, an if/else45 returning `True`/`False` for a value the condition already holds, a branch46 with the same body on both sides, `except E as e: raise e`.47- Write the code the task needs. Extra statements around the same task are the48 most measurable tell in the literature, and they cost review time forever.49- Extract a function when logic repeats across files. Re-deriving near-identical50 logic in a nearby scope is the failure mode, and it reads as thoroughness.51- Delete a `try`/`except` around code that cannot throw. Name the exception you52 expect and let the rest crash.53- Delete a null check on a value that a type or a caller already guarantees.54- One variable per value. Two names holding the same thing is a rename left55 half-done.56- Collapse runs of blank lines inside a function body. One blank line separates57 two ideas; three separate nothing.5859Architecture rules that earn their keep: dependencies point one way, IO sits at60the edges, errors get translated once at the layer that owns the driver, a61constructor does no work, an abstraction waits for its second implementation.6263Over-abstraction (an interface with one implementation, a factory for one64product, config for a constant that never changes) belongs to the `ponytail`65skill, which already owns that ground. Use it rather than duplicating it here.66Those patterns are ordinary over-engineering and predate any model.6768See `references/structure.md`.6970### Imports and APIs7172Resolve every import against the lockfile or the registry before you claim the73code runs. An invented package name is the one failure here with no judgment in74it: the package exists or it does not.7576Same for a method on a library type. Read the signature, then call it.7778### Tests7980- **Write the test before the code.** A test written after the code passes because the code runs, bugs included. A test written first defines the behavior the code must satisfy. The order matters: test → red → code → green → refactor.81- **State the expected outcome from the requirement**, not from running the code. An assertion built by pasting output encodes today's behavior. A test that went green on the first run never proved anything.82- **A red test is a claim that the code is wrong.** Fix the code. Change the test only when you can name why the expectation was wrong, and put that reason in the commit body.83- **Never weaken an assertion to reach green.** Loosening `==` to `is not None`, widening a tolerance, skipping the case, deleting the case, or catching the exception the test exists to prove: each makes the suite green and the code no more correct.84- **Test behavior at boundaries, not every line.** A pure function with no branches needs one test. A function with three branches needs three. A getter needs zero. Coverage measures lines that ran, not bugs that would be caught; a 100% covered codebase with no boundary tests catches nothing.85- **Assert behaviour, not implementation.** A test that asserts which internal method was called in which order breaks on a refactor that changed nothing, and passes when the behaviour is wrong. Mock what hits the network, the clock, or the disk, and nothing else.86- **No tautologies.** `assert True`, `assert x == x`, an expected value produced by calling the code under test.87- **Cover the error path**, which is where the bugs are and where a single happy-path test stops.8889See `references/tests.md`.9091### Documentation9293- A docstring on an exported symbol carries the contract: what it returns, what94 it raises, what it does not handle. A docstring that restates the signature in95 English carries nothing.96- A README says what the thing does, how to run it, and what breaks. A feature97 tour with a section per capability is marketing.98- A commit body says what changed and why. The investigation goes nowhere.99- A PR description says what the diff does. Claiming a change the diff does not100 contain is the expensive version of this mistake.101- No emoji section markers, no badge walls, no title case in headings, unless102 the repo already does it. Match the file you are in.103104See `references/docs.md`.105106## Checking107108```bash109# comments and docstrings, extracted from source110python3 tools/lint.py --code src/111112# structural checks that need a parse113python3 tools/ast_check.py src/114```115116The linter reports what a regex can see. Everything above it needs a reader.117118## Boundaries119120**Will:**121- Delete comments that carry no fact122- Cut defensive code around paths that cannot fail123- Rewrite tests that assert implementation into tests that assert behaviour124- Strip feature-tour prose from a README125126**Will not:**127- Claim a file was machine-written128- Remove a comment that records a constraint, a bug, or an external cause129- Remove error handling at a trust boundary, or around IO that genuinely fails130- Flatten a complex solution to a genuinely complex problem131- Impose its own house style on a repo that already has one132133<!-- anti-slop: ignore-file (this file quotes the banned patterns) -->