comment-quality · diff
v1.0.1 to v1.0.1
20 added, 4 removed. Audit A to A.
---
name: comment-quality
- description: "Use when comments restate code, lack WHY on subtle logic, or contradict the change. WHY not WHAT."
+ description: "Use when comments or docstrings drift from the code: stale, retracted, or lacking WHY."
version: 1.0.1
author: code-hygiene contributors
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
- tags: [stale, why, useless, misleading]
+ tags: [stale, why, useless, misleading, retracted, docstring, removed]
related_skills: [code-structure, changelog-quality, upstream-hygiene]
---
# Comment Quality
<!-- GC-20 --> Comments explain WHY code does this, not WHAT the next line does.
## Rules
### GC-20 — WHY not WHAT, density
- "/* increment counter */" → delete (code says WHAT, comment must say WHY).
- Multi-paragraph essay / numbered plan comment → compress to 2-8 line WHY block. Link to issue/pr for long history, not in comment.
- Doc scaffolding `/** @param` on private/internal helper → downgrade to plain `/*` WHY block or delete. Reserve full doc for exported public APIs. Grep for `name - summary` + `@param:` to catch — scaffolding appears under both markers.
- 50-word para cap aim (see changelog GC-12) applies to comments too.
### GC-21 — Placement, one source of truth at definition
- Doc comment lives at definition, not prototype/header when they differ. Header can have brief one-liner or none — deep WHY at def.
- Helper inserted? Doc left describing wrong function after insert still compiles — read for it.
### GC-22 — Subtle logic MUST have WHY
Genuinely subtle logic with no WHY comment is a bug. Check:
- Locking / ordering / lifetime / invariant / barriering / ownership transfer.
- Security / validation decision: why safe here but not elsewhere.
- Concurrency: who serializes, what can race, what barrier pairs where.
If you can answer "why not simpler?" you need a comment.
- ### GC-23 — Comment now contradicted by code change → rewrite in same diff
+ ### GC-23 — Comment contradicted by a code change → rewrite in the same diff
- Stale comment is worse than none. When changing code, grep nearby comments for contradiction and update them in same commit. Don't let comment explain dead step as if required.
+ Stale comment is worse than none, and a stale DOCSTRING is worse than a stale
+ comment: a comment narrates, a docstring makes a contract — one naming a
+ parameter, return key or field the code no longer has reads as an invitation to
+ call something that is not there. Rank the drift by what the text claims, not by
+ how close it sits to the edit.
+
+ - **Radius is the identifier, not the neighbourhood.** Removing a key, field,
+ flag, function or config name is a rename in reverse: grep the WHOLE tree for
+ that name — code, tests, docs, docstrings, comments — and settle every hit in
+ the same commit (`change-splitting` GC-36 owns the completion evidence). The
+ survivors are the ones no compiler reports: a docstring in the file you edited,
+ or a comment in ANOTHER module that justified its own field by the removed thing.
+ - **Write what is true now, not what was true.** The replacement says why the code
+ does what it does today; the retraction is history and belongs in the changelog,
+ where a reader looking for it goes. GC-24 already bans provenance in shipped
+ comments — a retraction note is provenance. A comment whose only justification
+ was the removed thing is rewritten to that thing's own contract, or deleted.
### GC-24 — Public-only context
Per `upstream-hygiene`: no private infra, no internal mount paths, no "this is like our fix in X" cross-project lore. If comment needs external pairing, name only upstream public symbols, e.g. "Pair with update_cache() — must hold lock per contract in cache.c".
### GC-25 — Comment removal is cleanup, not fix — separate drive-by
Removing a long-wrong comment mixed with logic change inflates diff. Do: separate commit "Remove stale comment — no behavior change" or fold only when that stale comment explains logic you are changing now.
## Anti-patterns
| Bad | What to do |
|-----|-----------|
| `i++; // increment i` | delete |
| 20-line plan comment explaining iteration history | compress to invariant + Link to pr/issue |
| `/** This helper does X @param y */` on static private | `/* X because Y */` or delete if obvious |
| Comment says "see jira IN-123" | public link or gist: "See #123" |
| Comment describes other project's fix | rewrite for this project's invariants |
## Positive framing for comments too
Applies from generic-principles "Positive framing". Comment that says "This is not kernel code" or "This is not X" is defining by negation — rewrite to what it IS and its contract. Provenance ("was previously ...") belongs in commit message or docs/references, not in code comment that ships.
## Verification
Use placeholder tokens for verification examples (self-dogfoods upstream-hygiene).
```bash
# Stale pattern: probe nearby file for contradictory TODO
git diff --unified=0 | grep -A2 -B2 "TODO\|XXX\|HACK"
# Private leakage — real scanner lives in private overlay; skeleton here uses placeholder marker
git diff HEAD 2>/dev/null | grep -E "^\+\s*(//|#|/\*|\*)" | grep -i "example\.invalid" && echo "REVIEW: placeholder marker still present — strip or rewrite" || echo OK
```
## Related
- `code-structure` owns helper extraction — this skill owns what comment moves with it.
- `upstream-hygiene` owns banned classes — this skill references it for examples.
- `generic-principles` Positive framing — where tagline rule encoded.
- `project-discovery` — sample the changesets of the files you touch for the project's comment style, terminology, and length.