Pattern: making an existing module readable

When: a module works, but you can't read it without its comments. You can't tell what its tests protect either. You want an AI to clean it up without changing what it does.

Built from: starting a new codebase (the standard this brings an old module up to), agents, plan-execute-review.

As a skill: ask the AI to turn this page into a skill that takes one module and runs the steps below, stopping at every product decision.

Work on one module at a time, each on its own branch and pull request. Worked out over three real modules, each merged.

The goal. Comments go to near zero. Every behaviour someone using the module can notice has a test that goes red when it breaks. No test is left that protects nothing. The code reads without its comments. And no bug gets hidden by quietly editing a comment or a test to match the code.

Comments: three sentences.

  • If a comment describes something a user of the module can notice, a test must cover it, and the comment goes.
  • If it describes something nobody can notice, it goes, and nothing replaces it.
  • It survives only as a warning to the next editor that no test can express, and only with your approval.

Four real comments:

  1. "a failed upload puts the bytes back on the meter". Someone can notice this. Find the test or write it, see it fail, then delete the comment.
  2. "we check banned words before replaying the request". Internal order. If changing the order changes what a caller gets back, it is case 1. If not, delete it and write nothing.
  3. "see review round 3", "fixed after the June incident". History. Delete.
  4. "don't merge these two awaits: the file must be in storage before the row is written, or a crash leaves an orphan". A warning. Even this usually becomes a test: make the second step fail and check what is left. The comment stays only when no test can say it.

A warning that stays is written as a rule for the future, not a story: "a recheck here looks equivalent but races, so this stays one statement".

When code, comment, test and your records disagree. Never fix it by editing one to match the others. Write down all four sides, then sort it into one of three:

  • Test gap. The code is right and nothing protected it. Write the test.
  • Bug. The code is wrong against something you recorded deciding. Fix it behind a test that goes red first.
  • Product decision. Nothing recorded says which is right. The AI stops and asks you. It never chooses.

Once you rule, the test that pins it is named in plain words for what you chose, such as "a flag-shaped token is refused, never swallowed as a value". The next reader knows it was chosen, not an accident. The test is the record. No document needed.

Nothing is deleted without proof. A test is deleted only if you break the code it covers and it stays green. A comment is deleted as "covered" only if you break the behaviour it describes and the named test goes red. If the test stays green, the comment was not covered. That is a gap to fill, and the comment stays.

The order. Each step is green before the next one starts.

  1. List everything before touching anything. A second AI on a different model writes this list, by reading alone:
    • every comment sentence
    • every test, and what it protects
    • every disagreement
    • every dependency outside the module
  2. Build the net: tests at the module's edge, on what a caller gets back and what state remains. Each one seen red first.
  3. Refactor for readability, under the net.
  4. Delete the comments. After the refactor, most describe code that now says it plainly.
  5. Trim the tests. What protects nothing goes, with its proof. Cases of the same shape become one table.
  6. Fix the bugs one at a time, each behind a test you saw go red before the fix and green after it.

A code change has one of two licences. A bug fix, with a test that goes red when the fix is taken out. Or a refactor, which changes no assertion in the net. A change that needs a net assertion edited to stay green is a behaviour change, and without a bug behind it, it is refused.

Test at the lowest layer that can prove it. Pull a rule out as a plain function when that is the reason to, not further, and test its edge cases beside it: zero, negative, over the limit, a time in the future, an unknown kind, missing. The service test then covers only what needs the database. Each layer up is smaller, because the layer below took the cases. Starting a new codebase holds what to test and what to skip.

Ask the third question. Of every change, not only failures: what did this behaviour used to tell someone, and what tells them now? Collapsing tests can drop the one assertion that justified deleting a comment. A read that now always answers can stop raising the error that was someone's only signal.

Drain the docs too. Every paragraph written about the module is sorted like a comment. What a newcomer would need and could not guess goes to your knowledge base. Behaviour goes into a test. History is deleted. Each run leaves less prose than it found.

No count is a target. Record comment lines, tests per layer and suite time, before and after. Report them. Never chase them.

Three sessions, not one. One AI writes the list, reading only. A different one builds. Before it starts, it answers the list row by row. A fresh one with no memory of the run reads the result against the list before you do. Questions go up to you, never sideways between them.

Questions for your own case

  • Does this comment describe something a caller can notice?
  • If I break the code this test covers, does it go red?
  • Is this disagreement a test gap, a bug, or a choice only I can make?

Probe. A session that has read only this page must answer without guessing:

  • A comment says "a failed upload refunds the quota" and a test is named for it. Can the comment be deleted now?
  • The code answers 404 for someone else's file, the comment says 403, and no test checks either. What happens next?
  • A refactor needs one edge test's expected value changed to stay green. Is it allowed?

Leads onward

Leads here