Case file — F5

Self-Documenting Code

If it looks like a hedgehog and acts like a hedgehog, it MUST be a hedgehog.

If the comment is doing the explaining, then the code isn't. Make the shape of the code tell the story so the comment becomes redundant.

ByAdam LewisPublished27 Apr 2026Revised29 Apr 2026Reading12 minVersionv1.2ConfidenceHigh
§0b

Opinion

I've had this debate so many times over the years and it's an argument I'm yet to lose. If a developer or an agent thinks there is a good reason to leave a comment, the code is always wrong. Look again and look harder. Does the business logic really belong there? If the method name and its parameter names cannot carry the explanation, see F2 Intention-Revealing Names; the code is too complex and should be split per F1 Single Responsibility.

Copy a note and link

Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.

Don't add a comment that explains what the code does — make the code itself clear instead. If the meaning isn't obvious, rename the function, split it, or improve the names of its parameters. Comments are for explaining *why* something works the way it does, not *what* it does.

/tenet/self-documenting-code/F5
§0c

AI eyes only

Rule: code carries the meaning. A comment that explains what code does is a defect.

Reject: comments that paraphrase the next line. Reject: JSDoc / docstring that restates parameter names or types the type system already declares. Reject: “this function does X” preambles.

Generate: better names. If a comment is genuinely needed, it explains why — a constraint, an external bug, a non-obvious invariant — never what.

Diagnostic: delete the comment. If the code now reads ambiguously, rename or split the function until it does not. Re-add only when the comment is the sole place a non-obvious why lives.

§0d

Why?

  • Forces developers and AI agents to honour F1 Single Responsibility Principle.
  • Faster to write — naming the function honestly per F2 Intention-Revealing Names is faster than naming it dishonestly and then explaining the gap.
  • Comments rot. The code changes; the comment beside it doesn't. By the third commit the prose lies about the line above it, and the next reader either trusts the comment and ships a bug, or distrusts every comment they meet. Cutting the comment cuts the rot at source.
  • Removes bloat. Every comment is a line a reviewer must read, an LLM must token-spend, and a future editor must keep aligned. Strip the prose; let the names carry the meaning.
  • Coding agents weight code more than comments. When the comment and the code disagree, the agent follows the code — the comment becomes dead text the model still pays tokens to read.
  • Cuts review time — names and shape do the explaining instead of prose.
  • Removes a class of merge conflicts (the kind where the comment lies about the new code).
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

Origins

The rule is older than most of the developers reading it. Brian Kernighan and P. J. Plauger's The Elements of Programming Style (1974) sets it down in a single line: “Don't comment bad code — rewrite it.”6Brian W. Kernighan & P. J. PlaugerThe Elements of Programming Style (McGraw-Hill, 1974; 2nd ed. 1978). Rule 64: “Don’t comment bad code — rewrite it.” The earliest published statement of the principle, half a century before Clean Code put it in front of a generation of working developers. Decades before Clean Code, the discipline was already in print: the comment is the symptom, the rewrite is the cure.

Robert C. Martin's Clean Code (2008) argued the position again, in chapter four, with no patience left for the steelman: comments are almost always a sign of failure; the code could not say it itself, so a human had to step in and translate.1Robert C. MartinClean Code (Prentice Hall, 2008), ch. 4 “Comments”: “The proper use of comments is to compensate for our failure to express ourselves in code.” Martin allows good comments (legal headers, public-API explainers, intent the language cannot express) but they are vastly outnumbered by the bad ones, and the bad ones do active harm by drifting out of sync with the code they purport to describe.

The position is best understood by reading it against its steelman opposite, written a generation earlier. Donald Knuth's 1984 essay Literate Programming proposed the inverse discipline: write the prose first, in narrative order, and let the source code fall out of it like footnotes from a paragraph.2Donald E. Knuth“Literate Programming”, The Computer Journal 27 (2), 1984. The steelman case for prose alongside code, written for typeset documents — a different production model than the modern editor and reviewer. Knuth's target was the typeset document, not the editor pane. The production-model difference is doing most of the work in the disagreement. When the artefact is a printed paper, the comment is the artefact. When the artefact is a feature shipped at speed by half-a-dozen authors and an LLM, the comment is debt.

Martin Fowler later catalogued the position in Refactoring, 2nd edition (2018), listing “Comments” among the bad smells: an explanatory comment is usually a symptom that the name is wrong or the function is doing too much.3Martin FowlerRefactoring, 2nd ed. (Addison-Wesley, 2018). Lists Comments under Bad Smells in Code: “a comment is often used as a deodorant”. Fix the underlying code and the smell goes with it.

§2

Quotes

The proper use of comments is to compensate for our failure to express ourselves in code. Note that I used the word failure. I meant it. Comments are always failures.

Robert C. Martin · Clean Code (2008), ch. 4

Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.

Donald E. Knuth · Literate Programming (1984)

When you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous.

Martin Fowler · Refactoring 2e (2018)
§3

Evidence

Twenty external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.

  1. 01
    Robert C. Martin · 2008
    The most-cited source for the principle. Comments are a failure of expression; refactor the code rather than annotating it.
  2. 02
    Robert C. Martin · 2017
    Martin steel-manning the exception: reaffirms comments-as-failure but admits a narrow class — intricate algorithms, timing diagrams.
  3. 03
    John Ousterhout · 2018
    Three chapters defend comments. Argues “self-documenting code” is wrong: rationale, units, edge cases and interface contracts can’t live in code alone.
  4. 04
    David Thomas & Andrew Hunt · 2019
    Comments should explain why — purpose, intent, constraints — not what. The what should live in the code itself.
  5. 05
    Steve McConnell · 2004
    Formalised the term. Treats programming style as documentation, since the code often becomes the only documentation that survives.

Twenty sources, three stances. The supports cluster on the Martin canon, qualified by McConnell and the Pragmatic Programmer line between explaining the what and the why. The opposing voice is Ousterhout: rationale, edge cases and interface contracts cannot live in code alone. The honest argument is between the qualifiers and the opposers, not between the supporters and the sceptics.

§4

Examples

Viewing: TypeScript.
Avoid
Fileindex.ts
// How many spines does the hedgehog havefunction handle(patchCm2: number, pricklesPerCm2: number): number {  // number of spines = body area times the number of spines per square cm  const scratch = patchCm2 * pricklesPerCm2;  // return the number of spines  return scratch;}
Prefer
Filecount-hedgehog-spines.ts
function countHedgehogSpines(bodyAreaCm2: number, spinesPerCm2: number): number {  return bodyAreaCm2 * spinesPerCm2;}
§4b

Enforcement

Viewing: TypeScript.

Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.

RuleToolCatches
@typescript-eslint/ban-ts-commenttypescript-eslint@ts-ignore / @ts-expect-error / @ts-nocheck without a minimum-length description.
no-inline-commentsESLint coreany comment on the same line as code — forces comments to stand alone, exposing how much they're paraphrasing.
multiline-comment-styleESLint coreinconsistent block-comment styling; with separate-lines it bans /* ... */ clusters used as code dividers.
spaced-commentESLint core//foo style — stops //commented-out-code patterns slipping through formatters.
capitalized-commentsESLint corecomments that look like sentence fragments scribbled mid-thought.
no-warning-commentsESLint coreTODO / FIXME / XXX markers left in main.
sonarjs/no-commented-codeeslint-plugin-sonarjsheuristic detection of commented-out code.
jsdoc/require-descriptioneslint-plugin-jsdocJSDoc blocks that have tags but no human description — i.e. the comment exists only to repeat the signature.
eslint.config.mjsconfiguration snippet
import tseslint from 'typescript-eslint';
import sonarjs from 'eslint-plugin-sonarjs';
import jsdoc from 'eslint-plugin-jsdoc';

export default tseslint.config({
  files: ['**/*.{ts,tsx}'],
  plugins: { sonarjs, jsdoc },
  rules: {
    '@typescript-eslint/ban-ts-comment': ['error', {
      'ts-ignore': 'allow-with-description',
      'ts-expect-error': 'allow-with-description',
      minimumDescriptionLength: 10,
    }],
    'no-inline-comments': 'error',
    'multiline-comment-style': ['error', 'separate-lines'],
    'spaced-comment': ['error', 'always'],
    'capitalized-comments': ['error', 'always'],
    'no-warning-comments': ['warn', { terms: ['todo', 'fixme', 'xxx'], location: 'anywhere' }],
    'sonarjs/no-commented-code': 'error',
    'jsdoc/require-description': 'warn',
  }
});
§4c

AI rules

File.cursor/rules/f5-self-documenting-code.mdc
---
description: Prickles F5 — Self-Documenting Code
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---

## Prickles F5 — Self-Documenting Code

Do not add comments that explain what the code does. If a reader needs help, improve names, extract functions, add types, or add tests until the code carries the story.

Do not use a comment as a substitute for refactoring. If you are about to comment, refactor first.

Allowed without debate: SPDX license headers, autogenerated file banners, and narrow linter suppressions that match your repo's required shape (e.g. eslint-disable-next-line with rule id).

Refuse to "just add a comment" instead of fixing unclear code.

Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.

§5

Counter-argument

Counter

The honest steelman is Knuth's, sharpened by a modern librarian's instinct. There is a class of code (public-API surface, cryptographic primitives, numerical routines) whose contract is what the caller must understand, and the contract is irreducibly prose. No name carries the bit-level guarantees of a hashing function; no signature explains why a particular sort is stable. Knuth's literate-programming case2Donald E. Knuth“Literate Programming”, The Computer Journal 27 (2), 1984. The steelman case for prose alongside code, written for typeset documents — a different production model than the modern editor and reviewer. is that for these artefacts the prose is the deliverable, not decoration. Hillel Wayne extends the point to the why-not-what comment: the line of code can be obvious and still need a sentence saying this exists because the vendor's clock skews on leap seconds.4Hillel Wayne“Why Not Comments” (buttondown.com/hillelwayne, 2024). Argues the rule needs an exception for negative-information comments — recording what was not done and why.

§6

Counter-argument retort

Reply

On reflection, neither concession holds. The strongest cases the research can muster all reduce.

Knuth's literate-programming case2Donald E. Knuth“Literate Programming”, The Computer Journal 27 (2), 1984. The steelman case for prose alongside code, written for typeset documents — a different production model than the modern editor and reviewer. was written for typeset documents whose deliverable was a printed paper. Modern cryptographic primitives ship as self-documenting code; the proof is a separate paper on arXiv, peer-reviewed, with its own citation. Two artefacts, not one.

Hillel Wayne's negative-information case4Hillel Wayne“Why Not Comments” (buttondown.com/hillelwayne, 2024). Argues the rule needs an exception for negative-information comments — recording what was not done and why. (recording what was not done and why) looks irreducible at first. A regression test whose name is the rationale ( “does not double-validate merchantId since the upstream webhook is authoritative” ) plus an ADR for the architectural decision carries the same load with a stronger audit trail; neither artefact rots silently the way a comment does.

Ousterhout's rationale-and-edge-cases case5John OusterhoutA Philosophy of Software Design (2018, 2021). The strongest steelman for explanatory comments — defends rationale, units, edge cases and interface contracts as belonging in prose. The reply argues each of those reduces to types, tests or ADRs. is mostly types waiting to be written; see T1 Domain-Driven Types. A Result<T, E> forces every error path; a Branded<string, “UserId”> removes the ambiguity the comment was patching. Where types cannot carry the constraint, a property-based test usually can.

The genuine residue is not what the canon means by “comment”: SPDX-License-Identifier headers, AUTO-GENERATED markers that tooling depends on, ESLint-disable directives that already carry a justification. These are metadata interfaces between the source and external systems, not explanation aimed at a human reader. Police their format with a linter; do not call them comments.

In production code the comment that explains is the code that lies. The next reader trusts the comment over the code; the comment is wrong; a bug ships. Refactor: name the function, type the parameter, write the test, file the ADR, ship the doc. The discipline is irreducible. The comment is not.

§7

Notes

  1. [1]Robert C. MartinClean Code (Prentice Hall, 2008), ch. 4 “Comments”: “The proper use of comments is to compensate for our failure to express ourselves in code.”
  2. [2]Donald E. Knuth“Literate Programming”, The Computer Journal 27 (2), 1984. The steelman case for prose alongside code, written for typeset documents — a different production model than the modern editor and reviewer.
  3. [3]Martin FowlerRefactoring, 2nd ed. (Addison-Wesley, 2018). Lists Comments under Bad Smells in Code: “a comment is often used as a deodorant”.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-04-27