Skip to content
AtomicReps

A good name you did not choose

A lesson from Engineering Judgment When AI Writes the Code. Play it above, or read it through below.

Level I · Building a Mental Model of Code You Did Not Write
  • The loop inserted in the order its name states. The read-out returned a different order.

    The common read: an object keeps things in the order I put them in, so an index built latest-first reads back latest-first

    Whether an object keeps insertion order depends on its keys. The loop did what its name says: it sorted by openedAt descending and inserted 7, then 1042, then 310. Object.keys returned those three keys as 7, 310, 1042, which is ascending numeric order and not the order of insertion. The oldest incident is listed above the second-newest one, and the titles print in that same rearranged order.

    The rule is one clause of the specification: own-property enumeration emits every key that is an array index first, in ascending numeric order, so a key that looks like an array index is listed by its numeric value when the object is read, not in the order it was written. An id is a string. "7" and "1042" are strings shaped like array indices, and the object treats them as positions rather than as names.

    The triage list on the on-call dashboard has been ordered by that function since it shipped. The retro after the missed page had an action item to add a lint rule for unsorted output, and the output was sorted. Nobody checked the read-out, which is the one step where the order changed.

  • The function was never wrong. The choice of container was.

    The common read: the function is broken, so the same call on the same data returns the same wrong order whatever the ids look like

    Nothing in the function changed and the answer changed. With INC- in front of each id and the same loop, comparator and rows, the read-out returned INC-7, INC-1042, INC-310, which is newest first and matches the name on the first screen. The prefix did not repair a defect. It moved the keys out of the class that enumeration sorts numerically and into the class the specification enumerates in creation order.

    Compare the two runs and you can locate the fault. The sort and the insertion ran correctly in both runs. The only difference was the format of the key, which comes from data produced elsewhere. A reader searching for a bug inside the function was searching the one part of the system that had no bug, so reading that file more carefully does not find the cause.

    A fix in the wrong place still closes the ticket. Swapping the comparator passes a test whose ids happen to sort into the wanted order, and the ticket reopens the next time the id format changes, with no code change. Two repairs work for any id format: build a Map, which keeps every key in creation order, or sort Object.values(index) by openedAt where the list is read.

  • The keys that enumeration moves to the front are integer ids from 0 up to 2^32 - 2.

    The common read: integer-like keys are an edge case, so the rule does not reach the code I review

    Name the id formats this rule applies to, then read the boundary run. 9, 10 and 4294967294 were listed first, in numeric order; -1, 1.5, 007 and 4294967295 were listed after them. The upper limit is exact, and the run measured it: 4294967295 is one past the range and stays in creation order. A key is an array index when it is the canonical decimal spelling of an integer from zero up to 2^32 - 2, so 007 is a name and 7 is a position.

    That range covers most ids a service stores as small non-negative integers: 32-bit serial keys, port numbers, status codes and year buckets. A negative error code or a 64-bit id above 4294967294 is not an array index and keeps creation order. A Map has no such rule: the same loop against one returned 7, 1042, 310, which is the order the rows were inserted. Reading the name indexByLatestFirst does not tell you which container the function builds, because {} on one line inside the body chooses it.

    The review that approved the function read only the function. The id format that decides the answer comes from the code that produced rows, which was not in the diff and is in no file the review opened.

  • The name was a claim about the result. The code guarantees only the insertion order.

    The common read: a name I would defend in review is a name I can read past, so an accurate name is a safe name

    An accurate name can still mislead. indexByLatestFirst is accurate about the loop it runs, and it also reads as a claim about the object it returns. Nothing in the code guarantees that claim. Written out as a sentence and run as a check, that claim reads: the keys come back latest first. The check returned false on the plain ids and true on the prefixed ones, on identical code.

    The pattern to remember from this screen is a name asserting a property of the result while the code guarantees only a property of the process. indexByLatestFirst guarantees an insertion order. allShipmentsScanned, two screens from now, guarantees a quantifier. The version you can spot while reading is an ordering word (latest, sorted, ranked) in the name of a function that returns a plain object rather than an array or a Map, because a plain object cannot guarantee that order.

    The incident doc's root-cause field reads sorting bug in indexByLatestFirst, and the sort is the one line in that file that did what it says. A reader took a claim about the result from a name that only described the loop. That reader has read many accurate names and has never needed to check one.

  • The batch was empty and the gate returned true.

    The common read: a check named allShipmentsScanned fails closed, so an empty batch cannot pass it

    Name what a check called allShipmentsScanned returns for a batch with nothing in it, then read the line under this one.

    For an empty batch the check returns

    An upstream filter kept only shipments out for delivery, and on this manifest it matched nothing. The batch size was zero, allShipmentsScanned returned true, and the truck was released. The specification says: every acts like the for-all quantifier in mathematics, and for an empty array it returns true. Other batches return what the name suggests: false for one unscanned row, true for one scanned row, false for a mixed batch. Only the empty batch returns true when nothing has been scanned.

    This case shows the naming pattern from earlier in this lesson, a name that claims a property of the result, in a different construct, and a reader who predicted the index order correctly earlier can still get this one wrong: the name reads like a safety check, readers assume safety checks fail closed, and every does not fail closed. The postmortem lists the upstream filter as the root cause. The filter is correct, and it is the only component in the chain anyone reviewed for correctness.

  • You did not read that function. You recognised it.

    The common read: I read that carefully and still missed it, so more care on the next diff is the fix

    The miss was not a lack of care. Reading code quickly is recognition: the reader matches the name and the shape to something known, and checks the body against that match instead of tracing it. Felienne Hermans describes expert programmers reading this way: they do not go line by line, they say that this is the decorator pattern applied to that variable, and she is describing human beings reading code that other human beings wrote. Recognition is the skill, and it is also how this bug was missed.

    Take the name away and the same function reads differently. With the identifier reduced to f there is no claim to confirm, so the reader follows the loop into the object, and the id format is the first thing anyone looks at. A controlled study of professional developers reading production methods is consistent with this: it found that misleading names, or names that clash with their types, are worse than meaningless names like consecutive letters of the alphabet.

    None of that literature is about code that no person wrote. It measured people reading each other's work, and applying it to code an AI generated is an inference, not a finding: recognition depends on names, a name you did not choose was never checked against your model of the system, and the mechanism is the same whoever typed the name. The review comment on the diff said the names were clear. The comment was true, and the clear names are why the diff was approved in four minutes.

  • The name passed review because it was plausible.

    The common read: a name that misleads is a review finding, so a careful reviewer catches it

    A misleading name does not look misleading. The controlled study that measured misleading names as worse than meaningless ones did not select them on purpose: its authors recorded that they did not realise how bad the variable names were when they started, on methods drawn from production utility classes that working developers had written and shipped. Naming is subjective, and a name one developer finds meaningful is misleading to another. The author's review of their own name passes every time.

    So review cannot be relied on to find this defect. Inconsistent naming and documentation is a catalogued defect class, and the sub-class that surveyed developers universally agreed was a problem is the one where the code's behaviour contradicts its name. That sub-class still ships, and not because anyone thinks it is acceptable. Reading the name does not reveal the clash; running the code does.

    If your prediction on the first screen was 7, 1042, 310, the run printed 7, 310, 1042. That difference comes from how reading works, not from you: fluency and prediction are different operations, and reading practises only fluency. The blameless timeline records the approval and the four minutes it took. It does not record that nobody in the thread could have said what the function returned.

  • The name is a claim, and two lines of code can test it.

    The common read: the repair is to read more carefully, so the answer is a slower review

    Reading more slowly gives you more recognition, not more prediction. The check that separates the two is cheap, and it is not review: write the name down as a sentence about the result, say what that sentence predicts, and run it. Both names in this lesson were written out that way in one file. Both claims failed on the first run, on code that no reading anywhere in this lesson found suspicious.

    The check is not free and does not belong on every diff. Decide where to use it by what a wrong answer costs: where an almost-right answer costs a rerun, recognition is enough and reading the name is the whole review. Where a rearranged order or a vacuous true reaches a system that is reconciled months later, running two lines of code is the cheapest step, and the only step that produces an observed result. The shipment retro's action item was a naming-convention document, which every name here satisfies.

    A name you can defend and a claim you have checked are different things, and only the checked claim requires a run. Nothing here says the names were bad. indexByLatestFirst was a good name chosen for a loop, and it is still a good name. Nobody who wrote or approved it did anything a reviewer flags, and the index is read back in numeric order, not newest first, whenever the ids are integers from 0 up to 2^32 - 2.