Catalin Bozianu

Four defect classes you only find in production

I run a small number of short term rentals and built the tool that handles the operations. It has one real user and that user is me, which means I know exactly what every defect did, with no filter between the bug and its consequence.

A year of that gave me four defect classes I now look for everywhere, in client work as much as my own. All four are invisible to tests by construction. Each has a cheap check that finds it.

One: a model of the world that was reasonable until it met a case

Booking calendars do not behave the way you assume. One platform publishes a nearly empty calendar during maintenance. Another re-cuts a stay that is in progress at local midnight, so the row representing it disappears and a different row replaces it.

My code treated a disappearance as a cancellation, which is correct for every case I had seen when I wrote it.

The check: for every external feed, write down what you believe it guarantees, then go looking for the case that breaks each guarantee. Ask the provider's documentation for the word "may". A guard that triggers on volume is also worth having, which I did have, and which was calibrated just loosely enough to let a real event through.

Two: a value read from where it appears rather than from what it is

To match payouts to properties, part of my tooling read the property from a calendar view by its position on screen. It worked perfectly until I added a listing, at which point every row shifted by one and a year of payouts attached themselves to the wrong properties. Fifty two of them.

The check: grep your code for anything that identifies an entity by index, offset, position, or order. Every one of those is correct today and will be wrong the day the list changes. Match on the identifier, always, even when the position is right there and works.

Three: a computation that depends on something existing that does not yet

An expense was created automatically when a job was marked done, but only if a rate existed at that moment. I set the rates a week into the month, so for the first eight days the jobs were recorded and the expenses were not. The totals came out short.

The check: for any rule of the form "do X if Y exists", ask what happens to the records that arrived before Y existed. Then write the backfill at the same time as the rule, not after someone notices. This is the class I find most often in other people's systems and it is almost always in money or permissions.

If it computes what somebody is owed, the failure mode is not a crash. It is a number that is wrong, delivered to somebody who has no way to audit it. That one deserves a test even when nothing else does.

Four: a failure nobody is listening for

A scheduled job had not run once since January, because a database extension it depended on was never installed. Every attempt failed. Nothing surfaced. Twenty four thousand failures into the void.

The check: every scheduled job needs a heartbeat on success, not an alert on failure. Failure alerts assume the job runs well enough to fail. A job that never starts produces no alert at all, forever, which is the quietest and longest lived defect there is.

I found this one while looking for something else, which is the only way these ever get found.

Why one user is not low stakes

The story people tell about building for yourself is that you can move fast because the stakes are low.

The stakes were not low. There were real reservations, real money, and a year of financial records that were wrong and looked right. One user does not mean low stakes. It means nobody else is checking, which is the opposite.

And none of these four happened because I was working fast with agents. They happened because they are hard. The speed just meant I got to be wrong about more things per month, which is also how I found all four inside a year instead of across a career.

Back to the diary