Code smellDispensables
Comments
Comments that explain what unclear code does are a sign the code should be renamed or restructured so it explains itself.
- Also known as
- Deodorant comments
Signs
- A comment restates the next line:
// increment retry countaboveretries++. - A block of code is introduced by a comment that names it:
// compute shipping, followed by 20 lines. - A comment explains what a cryptic variable or method name means.
- A comment states a precondition ("
itemsmust not be empty here") that nothing enforces. - Comments and code disagree, because the code changed and the comment did not.
Not every comment is a smell. Comments that explain why (a regulatory rule, a workaround for a vendor bug, a performance trade-off) carry information the code cannot. Public API docs are not this smell either.
Why it happens
The author knew the code was hard to read and patched the reader's experience instead of the code. Comments are cheaper to write than a good name or an extracted function, and they feel responsible. They then rot, because nothing tests them.
Example
A loyalty program computes a discount.
function discount(c: { y: number; o: number; t: string }, total: number): number {
// c.y = years as member, c.o = orders this year, c.t = tier
let d = 0;
// long-time members get 5%
if (c.y >= 3) d += 0.05;
// frequent buyers get another 5%
if (c.o > 12) d += 0.05;
// gold tier doubles it
if (c.t === "gold") d *= 2;
// never more than 25%
if (d > 0.25) d = 0.25;
// total must be positive
return total * d;
}After renaming, extracting and asserting, the comments have nothing left to say:
interface Member {
yearsAsMember: number;
ordersThisYear: number;
tier: "standard" | "gold";
}
const MAX_DISCOUNT_RATE = 0.25;
function loyaltyDiscount(member: Member, orderTotal: number): number {
if (orderTotal <= 0) throw new RangeError("orderTotal must be positive");
return orderTotal * discountRate(member);
}
function discountRate(member: Member): number {
const isLongTime = member.yearsAsMember >= 3;
const isFrequent = member.ordersThisYear > 12;
let rate = (isLongTime ? 0.05 : 0) + (isFrequent ? 0.05 : 0);
if (member.tier === "gold") rate *= 2;
return Math.min(rate, MAX_DISCOUNT_RATE);
}Treatment
- If a comment explains a complex expression, use Extract Variable and give the variable the comment's meaning.
- If a comment introduces a block of code, use Extract Method and name the method after the comment.
- If a comment explains what a method does because its name does not, use Rename Method.
- If a comment states an assumption about state, use Introduce Assertion so the assumption is checked.
- If a comment explains a magic value, use Replace Magic Number with Symbolic Constant.
- Keep, or add, comments that explain why. Delete comments that only explain what.
Payoff
- Names and structure stay in sync with behavior because the compiler and tests check them.
- Less text to read and nothing to go stale.
- Assertions turn silent assumptions into failures you can see.
When to ignore
- The comment explains intent, a non-obvious constraint, a link to a bug tracker or spec, or why a simpler approach was rejected.
- The comment is API documentation (
/** ... */, docstrings) consumed by tooling or library users. - The code is irreducibly complex (a numerical algorithm, a bit-twiddling hack), and the comment cites the source or derivation.
- License headers and generated-code markers.
Agent checklist
- For each comment you removed, its meaning now lives in a name, a type, a constant or an assertion.
- You kept every comment that explains why, cites a ticket, or documents a public API.
- You did not add new comments that restate the refactored code.
- Assertions you introduced do not change behavior on valid inputs, and tests pass.
- Any comment that disagreed with the code was resolved by checking which one is correct, not by deleting it blindly.
Treatments
- Extract VariableName a hard-to-read expression or sub-expression by assigning it to a well-named local variable.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- Rename MethodChange a method's name to say what it does, using a forwarding alias when callers cannot all be updated at once.
- Introduce AssertionState an assumption the code already relies on as an explicit check that fails fast, instead of leaving it in a comment or in the reader's head.
See also
- Long MethodA function that does several jobs in a row and has grown too long to read, name or test as one unit.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- Rename MethodChange a method's name to say what it does, using a forwarding alias when callers cannot all be updated at once.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.