Frameworks come and go. Principles stay. ✨
After a few years of writing software — from MVPs built from scratch 🚀 to legacy systems nobody dared to touch 👻 — I've come to realize something: the developers I admire most aren't the ones who know the most frameworks. They're the ones who follow a few simple principles, consistently, on every line they write.
Over time, I've collected my own set of principles. They act like a compass 🧭: whenever I'm unsure how to approach a problem, I come back to them. None of them are mine — they've been shaped by people much smarter than me — but together they form the way I work. Let me walk you through each one, with examples from real life. 👇
⚡ Make it work, make it right, make it fast
This one, from Kent Beck, is the backbone of everything else. It's an order of priorities, and the order matters:
- ✅ Make it work — first, get a working solution. Don't optimize, don't over-engineer. Just solve the problem.
- 🧹 Make it right — now refactor. Clean names, clear structure, tests. Make it readable for the next person (often your future self).
- 🏎️ Make it fast — only then, if needed, optimize for performance.
An example 💡. You need to display a list of users. The trap is to immediately think about caching, pagination, and database indexes. Don't. First, fetch the list and render it (it works). Then extract a clean component and add a test (it's right). Then, only if the page is actually slow, add caching (it's fast).
Most performance problems we obsess over never materialize. Respect the order, and you'll move faster overall. 🎯
🔮 YAGNI — You Aren't Gonna Need It
We love to build for a future that rarely comes. 🔭 "We might need this option later", "let's make it configurable just in case"... and we end up maintaining code nobody uses.
An example 💡. You're asked to export data as CSV. You think: "I'll build a generic exporter that also supports JSON, XML, and PDF, so we're ready for anything." Six months later, only CSV is used — but you're still maintaining (and debugging 🐛) three formats nobody asked for.
YAGNI is a reminder: build for the need you have today, not the one you imagine for tomorrow. ⏳ When tomorrow actually arrives, you'll have better information to build the right thing.
🎲 The Principle of Least Surprise
Code should behave the way the reader expects it to. The best code is boring: when someone opens your file, nothing makes them go "wait, what?" 😵 Surprise is where bugs hide.
An example 💡. A function called getUser() that also silently updates the last-login timestamp in the database. The name promises a read; the body does a write. Six months later, someone calls it in a loop to display names — and accidentally hammers the database. The code wasn't complex. It was surprising. A function should do what its name says, nothing more.
Here's a subtle one — and it sets up the next principle nicely: predictable often wins over simple. Sometimes slightly more verbose, "boring" code that follows the conventions of your codebase beats a clever one-liner that makes the next reader stop and squint. Be the developer whose code holds no surprises. 🧘
✂️ KISS — Keep It Simple
The simplest solution that solves the problem is almost always the best one. Complexity is easy to add and very hard to remove. 🧨
An example 💡. I've seen a single function grow to 200 lines because it tried to handle every imaginable case with a pile of flags: processData(data, true, false, true). 😵💫 Nobody could read it. Split into three small, well-named functions, the same logic became obvious at a glance.
Before writing a clever abstraction, ask yourself: do I really need this, or am I just showing off? 🤔 Simple code is code your teammates can read, debug, and change without fear. That's worth more than cleverness — but remember the previous principle: if "simple" means "surprising," go for predictable instead.
♻️ DRY — Don't Repeat Yourself
Every piece of knowledge should have a single source of truth in your codebase. When the same logic lives in three places, fixing a bug means remembering all three — and you never do.
An example 💡. A password validation rule (8 characters, one uppercase, one digit) copy-pasted into the signup form, the reset form, and the back-end. The day the rule changes, you'll fix two of them and ship the bug in the third. 😬 Extract it once, use it everywhere.
But be careful ⚠️ — don't apply DRY too early. Two pieces of code that look similar today might evolve in different directions tomorrow. Forcing them into one abstraction creates a tangled mess. As the saying goes: duplication is cheaper than the wrong abstraction. Wait until you see the pattern repeat a third time before extracting it.
🧱 SOLID — Five Letters for Maintainable Code
SOLID is a set of five principles for object-oriented design. People recite them like a spell, but each one solves a very concrete pain:
-
S — Single Responsibility. A class (or function) should have one reason to change. A
Userclass that handles authentication and sends emails and formats invoices will break in three different ways. Split responsibilities. -
O — Open/Closed. Open for extension, closed for modification. Adding a new payment method shouldn't mean editing a giant
if/else. You should be able to plug in the new case without touching the existing, tested code. -
L — Liskov Substitution. A subtype must work anywhere its parent is expected. If your
SquareextendsRectanglebut breaks when you set width and height independently, your hierarchy is lying to you. - I — Interface Segregation. Many small, focused interfaces beat one giant one. Don't force a class to implement methods it doesn't need just because they live in the same interface.
- D — Dependency Inversion. Depend on abstractions, not concrete details. Your business logic shouldn't know which database you use. Pass it an interface, and you can swap the implementation (or mock it in tests) freely.
You don't need to recite them. You need to feel them: code that's easy to extend and hard to break. 💪
👣 Baby Steps — Small, Validated Increments
Big changes are scary because they fail in big, mysterious ways. Small changes fail small, and they tell you exactly what went wrong.
An example 💡. Instead of writing 300 lines and running everything at the end (then spending an hour figuring out which part broke), I write a few lines, run the tests, commit, and repeat. When something fails, I know it's in the last tiny change — not somewhere in 300 lines. And because I commit often, git bisect becomes a superpower. 🦸
Progress made of tiny, reversible steps is faster than it looks — and far less stressful. 😌
🌳 The Mikado Method — Taming the Big Refactor
Sometimes a change looks simple, then you pull the thread and the whole sweater unravels. 🧶 The Mikado Method is how I handle that.
An example 💡. You want to upgrade a library. You change it — and 12 files break. The naive approach is to fix them all at once and pray. 😅 The Mikado approach: try the change, see what breaks, write down the prerequisites, then revert. Now tackle one prerequisite at a time, each as its own small, safe commit. Once the groundwork is done, the original change becomes trivial.
The result: no more half-finished refactors stuck in a branch for three weeks. You always have working code, and a clear map of what's left. 🗺️
🧩 How they fit together
None of these principles work alone:
- 🪶 KISS, YAGNI and Least Surprise keep me from building too much — and from building it weird.
- 🛠️ DRY and SOLID keep what I build maintainable.
- 🛡️ Baby Steps and Mikado keep me safe when I change existing code.
- 🧭 And "make it work, make it right, make it fast" sets the order for all of it.
But here's the uncomfortable truth nobody tells you when you first learn these: principles contradict each other. 💥 YAGNI says don't build it, while SOLID says add this layer of abstraction. DRY says extract the duplicate, while Least Surprise says keep it consistent and obvious. So which one wins?
The trick is to treat them as a hierarchy, not a checklist. 🏛️ The foundational ones come first, and a "higher" principle is only allowed to apply when it doesn't break a "lower" one. Roughly: make it work → YAGNI → least surprise → KISS → DRY → SOLID → and make it fast dead last. When two principles clash, the more foundational one wins. That's how you stop arguing in circles in code review.
They're not rules I follow blindly — every one of them has a "but" attached, as you've seen. They're a starting point, a default. The judgment of when to bend them — and which one outranks which — comes with experience.
So next time you're staring at a problem, unsure where to start, ask yourself the simplest question:
"What's the smallest, simplest thing that makes this work?"
Start there. Your code, your teammates, and your future self will thank you. 🙌
These are my principles — but I'm always looking to sharpen my compass. 🧭
What about you? Which principle has saved you the most pain? Is there a rule you swear by that I didn't mention here? Or maybe one of these you've learned to break on purpose?
👇 Drop your favorite tip in the comments — I read every one, and I'd love to learn from yours. And if this article resonated with you, a ❤️ or a 🔖 helps it reach another dev who needs it.
Happy coding! 🚀
Top comments (2)
KISS and YAGNI are my favorites. Both require an understanding of the product and its environment as well as engineering expertise. Especially YAGNI depends on knowing what likely futures of your product will look like. Surprisingly often you can get that out of the business analyst or product owner with a little gentle probing.
But my favorite is KISS, and I would add to that: KIR. Keep it readable. The best thing you can do is write code that is easy to understand, even if you come back to it only 6 months later. It beats everything else honestly, or kind of many good things follow. If I understand the code, it's also easy for me to refactor it. If I understand the code it's easy to see where a problem is if we have one. But if I don't understand the code, there's not much I can do with it. So code that can easily be understood should be the highest priority.
Don't be clever, and don't think less code is always better. That's a conventional wisdom which is right as a tendency, but wrong as an absolute law. Sometimes verbose code is easier to understand than "clever" code which does everything in a way I have to think about for 10 minutes before I understand it.
Love this, Andreas. Funny thing: KIR is kind of already baked in — "Make it Right" is basically "make it readable," and Least Surprise = code you get without thinking. But you're right none of them says it out loud.
Giving it a name makes it stick. And 100% on verbose > clever.