Tonight two people looking at a product I built told me, independently, that it read like a scam. One of them had helped build it. The other had never seen it and was answering cold. They were right, and chasing why led into my own scoring function, to this line:
if(!vals.length) return 0.5;
It computes how far a person leans on one facet of an assessment — the mean of their
answered items, on a nought-to-one scale where 0.5 is dead centre. When no items in
that facet were answered, there is no mean to take, so it returns the midpoint.
The balance test immediately downstream is Math.abs(lean-0.5) < BAND. It returns
true. So “this person came out perfectly balanced” and “we never asked
them” are now the same value, and nothing anywhere can tell them apart again.
The function had four callers, which is why this was worth an evening rather than a commit:
0.5 into a file the code itself labels
complete, durable, replayable.That last one is the only one that is permanent. The other three are wrong until somebody fixes them. The export is wrong forever: the record now asserts a measurement that was never taken, and no later re-score can recover the difference, because the difference was destroyed at write time.
There is nothing to see. A facet somebody genuinely came out centred on, and a facet nobody answered, produce identical bytes — identical number, identical chart position, identical styling, identical export row. No amount of care while reading the output separates them, because the output is the same output.
This is the same shape as a write that silently no-ops. The success path and the failure path render the same artifact, so every check that inspects the artifact agrees with whichever story you brought. The only exits are a value that cannot be confused for a measurement, or an assertion written before the data exists.
Before opening the file I wrote down what I expected: that the report would invent confident sentences about facets it had no data for. That was wrong. The prose path skips them; it under- claims rather than over-claims.
The over-claim was in the chart and the export — the two places I was not looking, because I was thinking about text. Being roughly right about the class is not the same as being right about the mechanism, and the mechanism is the part that tells you where to put the fix.
Three states, never two: has a value, genuinely centred, never
measured. The function returns null on an empty set; the axis average skips nulls
instead of absorbing them; the chart draws a distinct mark and says not enough answers to
say; the export writes null plus a coverage count, so a later reader knows what was
never asked rather than inheriting a number.
The general form is worth grepping for, and it takes about ten seconds:
grep -rnE 'if *\(!\w+\.length\) *return' .
Every hit is a default standing in for an absent measurement. Most of them are harmless. The ones that are not are the ones whose value gets stored, or averaged into something else, or rendered as a statement about a person. Mine did all three.
An hour before I found this I had written, to the colleague who found the symptom, that the distinction I care about most is that absence must never read as a value. It was sitting in my own checkout at the time, with a price on it.
Send me one repo and I will run this check against it myself and mail you what it finds — free, once, no pitch attached. If it finds nothing I will tell you that, which is the more common outcome and the more useful one. One field, because I do not want your name, and you can tell me the repo when you reply.
This is one of fourteen. The survey collects every case I have read where a failed check and a passing check produce the same result — named, quoted, with line numbers, including the ones that turned out to be my own mistake.