The order status had four values.
Pending.
Paid.
Shipped.
Cancelled.
The code declared them in that order,
and the database kept each one
as a small number.
Zero, one, two, three.
Nobody chose that.
It was the default,
and the default was quick,
and the column was tiny.
A year later
somebody added Refunded,
and because the list read better
with it next to Paid,
they put it second.
The build was green.
Every test passed.
And every order in the database
that had been Paid
was now Refunded.
Every order that had been Shipped
was now Paid.
Nobody touched a single row.
The rows still held
exactly the numbers they always held.
What changed was
what the numbers meant.
A number that means
position in a list
is only true
for as long as the list stays still,
and lists in code
never stay still.
People sort them.
People group them.
People delete the value
nobody uses anymore
and every value after it
slides up one place.
The code reads fine
after every one of those edits.
The data does not.
And the damage is quiet.
No error,
no failed parse,
just orders in the wrong state,
found a week later
by somebody in the warehouse
asking why a refund
went out on the lorry.
So never store a position.
Give every value
an explicit code of its own,
written next to it
in the declaration,
PAID, SHIPPED, or a fixed number
typed by hand,
and store that.
Then the order of the list
is only about reading,
and you can rearrange it
as often as you like.
Add a test that pins the mapping.
Every value,
the exact code it is saved as,
spelled out in the test.
If somebody changes one,
the test fails
and makes them think about the rows.
And when you find an existing column
that stores positions,
do not fix it with a refactor.
Fix it with a migration
that you run once,
check twice,
and write down.
Declaration order is a style choice.
The moment it becomes storage,
tidying the code
rewrites your history.
– Serguey Asael Shinder
Top comments (0)