Skip to content
AtomicReps

You learned the trick, not the mechanism

A lesson from The Model You Stopped Building. Play it above, or read it through below.

  • The pattern is shared. So is the position it stopped at.

    The common read: A regex literal is a value that describes a shape, so test answers about the string it was handed and nothing about the call before it.

    The prediction failed on a version that matches the pattern. VERSION carries a lastIndex, a regex with the g flag reads that number before it scans and writes it back after, so the first call matched to the end of 2.4.1, left lastIndex at 5, and handed the second call a five-character string to start reading at index 5, where the ^ anchor cannot hold. The failure path resets it to 0. That reset is why the third version reads true.

    Write the rule down now and it comes out as a fact about one method: repeated calls on a global regex alternate. That sentence is true, it predicts this transcript exactly, and it is the sentence this screen hands you. The compatibility check ran for four months. The versions it dropped were never reconciled against the manifest, and the ticket that eventually landed was filed against the check's ordering.

  • The rule was about a method. The position belongs to the object.

    The common read: replace takes a line and returns a new line, so adding a redaction call next to an audit cannot change what the audit reports.

    The audit got worse when a correct function ran beside it. replace on a global regex sets lastIndex to 0 before it scans, and its own scan loop runs until the pattern fails, which sets the field to 0 a second time, so every redact call in the second loop hands the next hasSecret call a position at the start of the line. That is why lines two and three audit correctly. Line one does not.

    Line one inherited 13 from the loop above it, 13 is past the end of a thirteen-character string, and the audit answered no on a line holding a key. Nothing between the two loops touches SECRET. The rule the previous screen handed you was correct, and it was a rule about test. The position is not test's. It belongs to the object, and both functions in this file write to it.

    The report from that run reads zero findings for the first line. That is a correct report of what hasSecret returned, and hasSecret is a correct wrapper around test. The review comment on this file asked whether the pattern belonged in a shared constants module.

  • Adding a call that touches nothing changed the answer.

    The common read: The audit missed a line because the audit is wrong, so the fault is inside the function that missed it.

    Name what the audit prints on the third line when a call that cannot touch it runs beside it. redact takes a string and returns a string. Its body holds no assignment token and mutates no argument, and a reviewer can prove both of those from the one line it is written on, which is the reason this screen asks about the third line in the list rather than about the first.

    With redact interleaved the third line reads true; without it, false. One line changed its verdict because a call beside it moved a number neither of them names, the field the compatibility check carried four screens ago. The audit is not less careful in one run than in the other. All three lines hold a key and no pass in either run reports all three. The postmortem for this miss reads that the scan passed and the reviewer approved a one-line function, and both are accurate.

  • Both functions are correct. The file is not.

    The common read: A defect lives in a function, so a review that reads every function carefully finds every defect in the diff.

    Name the function you would send back, then price the change. hasSecret is a correct predicate: it asks the shared pattern whether the line matches and returns the answer, which is what its name promises and all its body does. redact is a correct transform: it masks the pattern and returns a new string. Read either one alone and the margin stays empty, which is not a failure of attention, it is the accurate result of reading a correct function.

    The fault is in the pair, and the pair is twelve lines in one file with nothing offstage: the flag, the two call sites, the loop order and the shared binding are all on the screen and the reader has seen every one of them. A reviewer who read every line correctly still cannot produce false, true, true from that reading, because the sequence depends on a value that no line of this file prints, names or assigns.

    The unit a review is organised around is the function and the diff. The value that decides this file's behaviour is on neither. The retro landed on process and its action item was a lint rule for module-level regex literals, which would have fired on a regex that is correct.

  • The repair fixed the half you had seen.

    The common read: The carried position is the fault, so removing the g flag removes the fault and the file is correct again.

    Removing the flag does repair the audit, and it repairs it for the reason the repair was reached for. What it also removes is the replacement loop, because under g the specification runs replace until the pattern stops matching and without g it stops after one, so a line carrying two keys comes back with the first one masked and the second one printed in full. One character decides which of the two failures the file ships.

    Neither version reports anything. The change that lands this is a one-character diff with a title like fix regex statefulness. It is approved in under a minute, because it is one character and the reviewer can see the whole of it.

  • A pattern is not a model. A half-model points the wrong way.

    The common read: I know the mechanism now, so the next file with this in it is one I will catch.

    Knowing the trick made the second prediction worse, not better. A rule that arrives from a first encounter is keyed on the symptom that encounter showed, and the symptom on the four schema versions was alternation, so the rule reads: repeated calls alternate. Every run since has been a variant where alternation is the wrong prediction, and the rule fired anyway on every one of them, because a rule fires on the shape it was keyed to and not on the state that produced the shape.

    A held model is smaller than the rules it replaces. One mutable field lives on the regex object, both operations in this file write to it, and the value at any call site is whatever the last write left. That sentence predicts the compatibility check, the audit, the redaction and the one-character repair, and it fits on one line.

    A half-model is worse than no model on this axis and only on this axis. With no model the reader says unsure and runs it. With the rule, the reader says alternates and does not run it, and is confident in the wrong direction, which is the direction that produces an approval. The postmortem note reads that the team had seen this class of thing before, which is true and is the mechanism.

  • The question that finds it is asked before you read.

    The common read: I missed it because I read too fast, so the repair is another pass over the same lines.

    Name the state before you read the lines. One question produced every answer in this lesson and it is one sentence long: which values here outlive a call, and what writes to them. SECRET is a module-level binding, lastIndex is a mutable field on it, and both test and replace write to that field. Answering that costs about as long as reading the file once, and it is available before the first line of either function.

    Another pass over the lines does not produce that answer, because the answer is not in a line: the g in the literal implies it, the two call sites depend on it, and no statement in this file names the field, assigns to it or prints it. The reading that finds it starts from the object and asks what happens to it over time, which is a different pass rather than a slower one.

    Carried state is this course's phrase and not a specification term: a value that outlives the call that touched it. The review comment that would have caught this file reads: what does lastIndex hold when the second loop starts. That is a question rather than a finding, and a review interface has no field for it.

  • Predicting costs one line and it is the only step that can be wrong out loud.

    The common read: Reading a diff carefully is how I check it, so a careful read and a prediction are the same act done at different speeds.

    Price the check before deciding where to spend it. The act itself is already yours: write down what the code prints, run it, and read the difference. What these twelve lines add is where that minute is worth buying, because the careful read of them is exactly what produced a confident wrong answer on the one-character repair, and the prediction beside it is the only step in this review whose result can contradict you. Reading cannot contradict you. That is not a difference of pace.

    It does not scale to every diff, and treating it as a standard is how it stops happening at all. Spend it where a wrong answer is expensive and where the code carries state across calls: a shared module-level value, anything holding a position or a cache, anything whose second call could differ from its first. Everywhere else, read it and move on. The recommendation on this file was more time per review, and more time on these twelve lines produces the same wrong answer.

    The ticket was titled intermittent scanner false negatives. Every word of that is accurate except intermittent.