Building · 31 Aug 2026
The bug that would have shipped
I spent this week building a mileage tracker. It is the kind of app that looks finished long before it works.
By Sunday afternoon it launched, detected drives, drew a nice card for each one, let you swipe left or right to say whether a trip was for work, added up a deduction and exported a CSV. Every screen looked right. The tests were green. I could have put it on TestFlight and felt good about it.
Then I read the drive detection line by line, and found that it recorded almost nothing.
The bug
GPS gives you a stream of positions. To measure a trip you take each new position, work out how far it is from the last one, and add that up. The complication is that GPS jitters: park a phone on a table and it will wander a few metres on its own. So you set a floor. Ignore anything under, say, twelve metres, because that is noise rather than travel.
I set the floor. What I got wrong was one line below it.
When a step was under the threshold I skipped the addition, and then I updated the reference point anyway. So the next comparison started from the new position. The distance was not deferred until it grew big enough to count. It was thrown away.
On a motorway that barely matters: at 70 mph, positions arrive far enough apart to clear any threshold. In a city, at thirty, they do not. Every step falls under the floor, every step gets discarded, and the total stays near zero.
It gets worse. The same block that added the distance also refreshed a "last moved at" timestamp, used to decide when a trip has ended. That timestamp never advanced either. So five minutes in, the app concluded the car had been stationary since the start, closed the trip, measured it at roughly zero metres, and deleted it for being shorter than the minimum worth recording.
The most common trip a person takes would have been logged as nothing, then silently thrown away.
There is no error state for this. Nothing crashes. You drive to a client, come back, open the app, and the drive simply is not there. You would assume you had set something up wrong. You would probably delete the app.
Four ways to get the money wrong
The same review turned up four separate ways the app could put a wrong number on a tax return. Every one of them looked fine on screen.
The first: the rate. In the United States a business mile is worth about seventy cents. In a country using kilometres that same rate is about forty-three cents, because a kilometre is shorter than a mile. I had built the conversion and got it right in the calculation — and then, in the settings screen, defaulted the text field to the literal string "0.70" regardless of unit.
So on a phone set to kilometres, the app calculated with 0.435 and displayed 0.70. Two different rates, in the same app, on the same launch. Worse: switching the unit picker read the displayed 0.70, assumed it was a per-mile figure, and converted it up to $1.13 per mile. Someone's ten thousand euro claim becomes sixteen thousand, and nothing on screen suggests anything happened.
The second: history. Deductions were calculated with whatever rate was currently in settings. Update the rate next January and last year's report — the one already filed — quietly changes to a different number. Rates are now stamped onto each drive when it is recorded, so a filed year stays filed.
The third: commas. Much of the world writes 0,55 rather than 0.55. The parser I used is locale-independent, so it read "0,55" as zero, silently fell back to the default, and produced reports about twenty percent short. The app already handled this correctly in a different screen, which is the most annoying kind of bug — the fix existed twelve lines away.
The fourth is smaller but says the most. The headline showed a total. Directly beneath it, a caption said "155.3 miles × $0.70". Those two numbers did not quite agree, because the total rounded every drive to the cent before adding, while the caption implied one multiplication. Pennies. But if someone checks the arithmetic on their own tax figure and it does not add up, they are right to stop trusting the app, and they should.
What I actually take from this
None of these were exotic. No race condition, no memory corruption, nothing that needed a debugger. Four of them were a single line. They survived because everything about the app looked correct, and looking correct is what I was checking.
The habit that caught them was reading the code as an adversary rather than as its author: assuming each function was wrong and trying to prove it, instead of reading along and nodding. That is a different activity from testing, and it found things the tests could not, because the tests encoded the same assumptions the code did.
Everything above is now a test with a name that states the danger. testDriveSnapshotsItsRate asserts that changing the rate does not alter a drive already recorded. testCommaDecimalRateIsAccepted types 0,55 like a French user would. They exist so that a future version of me, in a hurry, cannot quietly undo the fix.
The app goes to TestFlight this week. It will have other problems — real driving will find things a simulator cannot. But it will not record a city commute as zero miles, and it will not put a number on a tax return that its own caption disagrees with.
← All writing