Lesson 02 / 16
Names Instead of Comments
Measuring the mismatch that follows from a comment being uncompiled, unchecked text: auditing whether the number claims in the comments of a shipment fee module match their counterparts in the code, the refactoring that turns claims into names, and the kinds of comments that cannot be turned into a name.
Contents
The previous lesson left an observation in the named version: the VOLUMETRIC_DIVISOR
constant needed no comment line, because its name already said what it was. The same
information could only be given through a comment in the abbreviated version. Does this
observation generalize? Is the presence of a comment a sign that the code had to say
something it could not say itself?
Tying the question to a number starts from the technical property of a comment. A comment does not compile, does not run, is not checked. Code defends itself with its behavior on every change; a comment does nothing and stays exactly where it was written. This asymmetry produces a drift over time: the code goes one way, the comment goes another, and the reader reads the comment.
The File, Six Months Later
The fee module of the shipment library lived for a while. In that time the tariff changed: a fourth weight tier was added, the minimum fee went up, the contracted customer discount was renegotiated. Whoever made the changes updated the code.
mkdir -p commented named
// commented/fee.mjs // Fee calculation. // The volumetric divisor is 3000. // There are three weight tiers: 1, 5, 10 kg. // The minimum fee is 45. // The contracted customer discount is 12%. const B = 3000; const A = 52; const S = 0.15; const K = [1, 5, 10, 30]; const U = [38, 26, 21, 17]; export function calculate(shipment, zoneCoefficient) { // volumetric weight const d = (shipment.widthCm * shipment.lengthCm * shipment.heightCm) / B; // the larger one is charged const a = Math.max(shipment.weightKg, d); // find the tier let i = 0; while (i < K.length - 1 && a > K[i]) i += 1; // the discount is applied before the minimum return Math.max(U[i] * a * zoneCoefficient * (1 - S), A); }
The comments do two jobs at once. Some repeat what the code does (// volumetric weight, // find the tier); these have moved the information a name should carry into a
comment line. Some assert domain facts: the number of tiers, the minimum fee, the
discount rate. The second group can be audited.
Measuring the Mismatch
The tool below separates a file’s comments, extracts the numbers from the comments, and checks whether each one is present among the code’s numeric invariants. A number in a comment with no counterpart in the code is proof that the comment has drifted from the code. The file’s first line is the marker carrying the filename, so it is excluded from the count.
// comment-check.mjs — counts comments and checks whether the numbers a comment claims match the code import { readFileSync } from "node:fs"; const numbers = (text) => text.match(/\d+(?:\.\d+)?/g) ?? []; function check(file) { // The first line is the filename marker; it is not counted as a comment. const lines = readFileSync(file, "utf8").split("\n").slice(1); const comments = lines.map((s) => s.match(/\/\/(.*)$/)?.[1]).filter((s) => s !== undefined); const code = lines.map((s) => s.replace(/\/\/.*$/, "")).join("\n"); const inCode = new Set(numbers(code)); const claims = comments.flatMap(numbers); return { comments: comments.length, claims: claims.length, missing: claims.filter((s) => !inCode.has(s)) }; } for (const file of process.argv.slice(2)) { const r = check(file); console.log(`${file.padEnd(18)} comments=${r.comments} numeric claims=${r.claims} not in code=[${r.missing.join(", ")}]`); }
node comment-check.mjs commented/fee.mjs
commented/fee.mjs comments=9 numeric claims=6 not in code=[45, 12]
Nine comment lines, six numeric claims, two not present in the code. The comment says “minimum fee 45,” the code applies 52. The comment says “discount 12%,” the code applies 0.15. It does not state the tier count as a number, so the “three tiers” claim is not caught by this tool; the code carries four. The limit of the audit shows itself here too: the tool compares only numeric claims, not claims written in plain words.
The Cost of Drift
Two mismatched numbers are not an abstract flaw. Someone reading the comment who wants to verify a shipment’s fee by hand uses the values the comment gives.
// deviation-demo.mjs — the calculation the comment describes versus the calculation the code performs import { calculate as commented } from "./commented/fee.mjs"; const shipment = { weightKg: 2.4, widthCm: 30, lengthCm: 24, heightCm: 18 }; const ZONE_COEFFICIENT = 1.35; // The calculation done by hand with the values the comment states: minimum 45, discount 12%. const byComment = Math.max(26 * 4.32 * ZONE_COEFFICIENT * (1 - 0.12), 45); console.log(`by comment = ${byComment.toFixed(2)}`); console.log(`code result = ${commented(shipment, ZONE_COEFFICIENT).toFixed(2)}`);
node deviation-demo.mjs
by comment = 133.44 code result = 128.89
The gap of 4.55 produces no error message. The program runs correctly, the tests pass; the only thing wrong is the documentation, and because the documentation sits inside the code it is assumed to be correct. A wrong comment costs more than no comment.
Translating a Comment into a Name
The steps of the refactoring are mechanical. Every comment describing what the code does
turns into the name of the part doing that job: the comment // volumetric weight
becomes a function named volumetricWeightKg. Every comment asserting a domain fact
turns into the name of the constant holding that fact: the comment // the minimum fee
becomes the constant MINIMUM_FEE. The claim and the value now sit on the same line, and
drifting apart becomes impossible.
// named/fee.mjs — the comments translated into names const VOLUMETRIC_DIVISOR = 3000; // set by the carrier contract, not decided here const MINIMUM_FEE = 52; const CONTRACTED_CUSTOMER_DISCOUNT = 0.15; const WEIGHT_TIERS = [ { upperLimitKg: 1, ratePerKg: 38 }, { upperLimitKg: 5, ratePerKg: 26 }, { upperLimitKg: 10, ratePerKg: 21 }, { upperLimitKg: 30, ratePerKg: 17 }, ]; const volumetricWeightKg = (s) => (s.widthCm * s.lengthCm * s.heightCm) / VOLUMETRIC_DIVISOR; const billableWeightKg = (s) => Math.max(s.weightKg, volumetricWeightKg(s)); const selectTier = (weightKg) => WEIGHT_TIERS.find((t) => weightKg <= t.upperLimitKg) ?? WEIGHT_TIERS.at(-1); const applyDiscountBeforeMinimumFee = (fee) => Math.max(fee * (1 - CONTRACTED_CUSTOMER_DISCOUNT), MINIMUM_FEE); export function calculate(shipment, zoneCoefficient) { const weightKg = billableWeightKg(shipment); return applyDiscountBeforeMinimumFee( selectTier(weightKg).ratePerKg * weightKg * zoneCoefficient); }
The upper limit of a tier and that tier’s rate per kilogram now sit inside the same object. In the previous version these lived in two separate arrays, tied together only by matching indices; forgetting to update both arrays when a tier was added produced a silent error. The name change fixed the structure along with it.
node comment-check.mjs commented/fee.mjs named/fee.mjs
commented/fee.mjs comments=9 numeric claims=6 not in code=[45, 12] named/fee.mjs comments=1 numeric claims=0 not in code=[]
Nine comments dropped to one, six numeric claims to zero. Zero claims means the possibility of a mismatch is also zero: a claim translated into a name changes together with the code.
The Comment That Remains
One comment remains, and it needs to remain:
const VOLUMETRIC_DIVISOR = 3000; // set by the carrier contract, not decided here
This comment does not say what the code does; it says why it is so. No name can carry where the number comes from, because that information sits outside the code. Whoever deletes the comment assumes 3000 is a value open to improvement and tries to change it.
The kinds of comments that cannot be translated into a name are limited, and all of them point outside the code:
- Rationale: the source of a value or a decision is a contract, standard, or organizational decision outside the codebase.
- Warning: an operation has a consequence not visible at first glance; a call’s order matters, or crossing a limit is expensive.
- Intentional deviation: the code behaves differently from what is expected in one place, on purpose, and the comment records the deviation and its reason.
- Legal and license text: text required to be present at the top of a file.
Every comment outside these four kinds is a candidate: for a name, a function, or a test. The comment “this function does not accept a negative weight,” once translated into a test or a guard clause, becomes a claim that is checked; left as a comment, it stays unchecked.
Summary
- A comment does not compile and is not checked; the code changing while the comment stays put produces a silent error. The mismatch is measured by comparing the numeric claims in a comment against the code.
- The commented version of the fee module had nine comments, six numeric claims, and two claims with no counterpart in the code; the calculation by comment gave 133.44, the code’s result was 128.89.
- After the refactoring that turns comments into names, the comment count dropped to one and the numeric claim count to zero; a claim translated into a name changes together with the code and cannot drift apart.
- The name change also fixed the structure: two arrays held together by matching indices became a single tier object.
- Comments that cannot be translated into a name point outside the code: rationale, warning, intentional deviation, and legal text. Every remaining comment is a candidate for a name, a function, or a test.
Next Step
Translating comments into names split a single function into four parts: volumetric weight, billable weight, tier selection, and discount application. The split points were set by where the comments happened to sit — an accidental measure. When should a function be split? Is line count a measure, or can a ten-line function also contain something that should be split? The next lesson pulls the measure out of line count and ties it to the single level of abstraction rule: an analyzer counts the levels the names a function calls belong to, and the two versions of the fee calculation are compared by that count.
To keep your progress and take notes, Log in
My notes
Log in to take notes.