Why?
Yes, the title is a bit presumptuous. On purpose.
I'm a middle-aged dude who loves a Japanese classic, Principles of Programming (101 principles you should know by your third year). But something bugged me.
I was letting an AI build a tool, and it wrote the font colors for light mode and dark mode like this:
{ "fontColor": "#c0504d/#b2423f" }
Two values, packed into one string with a slash.
The moment you want to change only one side, or add a third mode, you have to rebuild the whole shape.
A textbook anti-pattern.
Obvious, right? But when I went back through my books, I couldn't find one short name for it.
First normal form and Primitive Obsession come close. But as far as I know, there is no short question you can point at a single JSON property or function argument.
We have DRY (Don't Repeat Yourself). We have SRP (Single Responsibility Principle).
Without a name like those, you can't call it out in one word during a review.
The soul of software lives in its names. No name, no thing.
What did I do?
I gave it two names and added them to my review standards.
- OKPN (One Kind Per Name) — one name takes one kind of value
- OMPV (One Meaning Per Value) — one value carries one meaning
- Bonus: Null Trap — if there is no value, leave the element out instead of writing null
How does it work?
First, one definition.
A data element, such as a JSON property or a function parameter, is a pair of a name (key) and the rule for the values that name takes.
OKPN (One Kind Per Name) — one name, one kind of value
The questions are just these:
- Does each name of a data element take values of one kind only?
- Does every name of a data element avoid taking values of more than one kind?
Here's the color, fixed:
// Bad: light and dark packed into one string
{ "fontColor": "#c0504d/#b2423f" }
// Good: one name, one kind of value
{ "fontColor": { "light": "#c0504d", "dark": "#b2423f" } }
With the fixed shape, you can add "monochrome" later and leave the existing values untouched.
With the packed string, you have to reinvent the separator every time you add one.
How to spot it: if a value contains a separator (/ ; , or a space), be suspicious.
More examples (click to expand)
// Bad: width and height packed into one string
{ "size": "1920x1080" }
// Good
{ "size": { "width": 1920, "height": 1080 } }
// Bad: sometimes a number, sometimes a string with a unit
{ "margin": 8 }
{ "margin": "8px" }
// Good: pick one kind, put the unit in the name
{ "marginPx": 8 }
OMPV (One Meaning Per Value) — one value, one meaning
Again, just these:
- Does each value of a data element have one meaning only?
- Does every value of a data element avoid carrying more than one meaning?
// Bad: 0 means both "an ID" and "no parent"
{ "taskId": 7, "parentId": 0 }
// Good: no parent, so no parentId
{ "taskId": 7 }
How to spot it: if the spec's prose gives a special value (0, -1, an empty string, 9999-12-31...) a second meaning ("if it's X, it means Y"), be suspicious.
More examples (click to expand)
// Bad: 9999-12-31 means "no deadline"
{ "deadline": "9999-12-31" }
// Good: no deadline, so no deadline element
{}
// Bad: -1 means "not counted yet"
{ "count": -1 }
// Good: not counted, so no count element
{}
Null Trap
Once you start thinking in OMPV, someone always asks:
"So what about null?"
Null gives "no value" two spellings: omission and null.
→ The writer and the reader handle null differently.
So here's the rule:
- Does the specification define how an omitted data element and a data element whose value is null are handled, and does the implementation follow that specification?
- Recommended: omit a data element with no value as a whole.
- Recommended: do not put null into the value of a data element with no value.
- Recommended: where a value cannot be omitted, as with a function's return value, express the absence in the way the language recommends, or with a discriminated union.
// Bad
{ "taskId": 7, "parentTaskId": null }
// Good (specification: a task with parentTaskId omitted is a top-level task; a null value is an error)
{ "taskId": 7 }
For a return value in TypeScript, something like this:
type FindTaskResult =
| { isTaskFound: false }
| { isTaskFound: true; task: { taskId: number } };
Check isTaskFound, and the type tells you whether task is there.
No more returning null and hoping the caller remembers to check.
Exception: if a standard defines it, follow the standard
Of course: where a referenced standard defines the specification of a data element, conform to that specification.
For example, an ISO 8601 time interval 2026-10-24/2026-10-31 packs two values with a slash.
But the standard defines that as one kind of value, so it's not an OKPN violation.
The problem is packing values on your own.
Is it worth it?
Two small rules. They work.
- One word in a review. "Isn't this an OMPV violation?" Everyone gets it.
- Easy to hand to an AI. The names and questions are short, so you can pass them as review criteria as they are.
Packing values into a string is the easiest thing to do today.
Giving 0 or -1 a special meaning is the easiest thing to do today.
But the shape of your data matters less on the day you write it, and more on the day you maintain it.You pay for today's shortcut later, usually by breaking compatibility.
And the one who pays is usually you."If a man take no thought about what is distant, he will find sorrow near at hand." — Confucius, The Analects
Checklist (copy and paste)
- OKPN: Does each name of a data element take values of one kind only? (No two values packed with a separator.)
- OMPV: Does each value of a data element have one meaning only? (No second meaning added to a special value.)
- Null Trap: Does the specification define how an omitted data element and a null value are handled, and does the implementation follow it? (No value -> omit it.)
- Exception: Where a referenced standard defines the specification of a data element, conform to that specification.
The full rules live in gr-sw-maker's review standards (R2 "OKPN/OMPV" and R3 "Null Trap").
I wish someone famous would spread the words OKPN/OMPV. I think the names are pretty cool...
I hope this helps someone.
Top comments (0)