Dev Philosophy

The principles I actually code by — not abstract ideals, just what I've learned works through building real things.

These aren’t rules I follow because someone told me to. They’re things I’ve had to learn through making the wrong call first.

Typed or it didn’t happen

No any. No implicit unknowns. If you can’t describe the shape of the data, you don’t understand what you’re building yet. TypeScript’s type system isn’t bureaucracy — it’s a way of thinking clearly about what can and can’t exist in a given context. The friction it creates is almost always friction that was already there, just invisible.

Spec first, build second

Define interfaces and types before writing implementation. This sounds obvious and is consistently skipped under time pressure. When you skip it, you end up building the wrong thing with confidence. Five minutes of type design at the start usually saves an hour of untangling later.

Ship small, ship often

Finished is better than perfect. A feature that exists and works is infinitely more useful than a feature that almost exists but has better architecture. The goal is to have working software in the world. You can improve things that exist. You can’t improve things that are still in your head.

Vibe-first at home, rigorous at work

Context matters. A personal project at midnight is a different activity than a PR that other people will maintain. At home I’m exploring — I’ll write messy code to see if an idea works. At work I’ll slow down: clean names, clear structure, tested, readable by someone who doesn’t know what I was thinking. Applying work standards to hobby projects kills the joy. Applying hobby standards to work code is a gift to no one.

Read the codebase before touching it

Before writing a single line, read the code around what you’re changing. The existing patterns are almost always intentional. You want to extend, not diverge. The goal is a codebase that looks like it was written by one person.

Comments explain why, not what

Good code explains what it does through naming. A function called retryWithExponentialBackoff doesn’t need a comment that says “retries with exponential backoff”. A comment that explains why you’re retrying, or why the backoff uses base 1.5 instead of 2, or that there’s a downstream API with undocumented rate limiting that forced your hand — that’s worth writing.

The right abstraction at the right time

Three similar functions is fine. Copy-paste is fine. The temptation to abstract early is almost always wrong — you’re generalising before you know what the general case actually is. Let the pattern emerge from real usage, then abstract. A premature helper is code debt in disguise: it looks clean but it couples things that weren’t ready to be coupled.

back