Sefism is free for learners. We are here to help you grow, one step at a time.Join for free
Sefism.
code reviewtestingproductivity

Six Rules That Keep a Codebase Worth Working In

Six clean code principles, separation of concerns, DRY, KISS, YAGNI, test-driven design and writing for the next reader, each with what it protects against, where it came from and how it goes wrong.

Tauseef Fayyaz
Tauseef Fayyaz

Jul 2, 202613 min read

0

The short answer: six clean code principles, and how they go wrong

Every one of these clean code principles gets quoted as an acronym in code review, usually by someone who read the expansion and not the reasoning. Applied without judgement, they produce code that is worse than if nobody had heard of them. Applied with judgement, they are the difference between a codebase people enjoy working in and one they quietly try to leave.

So here are the six I think matter most: separation of concerns, DRY, KISS, YAGNI, test-driven design, and writing for the next reader. For each one I will cover what it actually protects against, where it came from, and the specific way it goes wrong when taken too far.

I am a Lead Full Stack Engineer, and I have seen every one of these misapplied, including by me. If you are a student or a junior developer, learning the failure mode of each rule is what will make your code reviews go well. If you are more senior, this is the version to send the teammate who keeps adding boolean parameters in the name of DRY.

The six rules at a glance

RuleProtects againstGoes wrong when
Separate concernsChanges that ripple everywhereYou split for the sake of splitting
Don't repeat yourself (DRY)Knowledge drifting out of syncYou merge code that only looks alike
Keep it simple (KISS)Clever code nobody can debugYou confuse short with simple
You aren't gonna need it (YAGNI)Paying for imagined futuresYou ignore requirements that are genuinely coming
Let tests shape the designHard-to-change, untestable codeYou treat it as all or nothing
Write for the next readerLost context and wrong assumptionsComments restate the code instead of the reason

1. Separate concerns

Break a large problem into pieces, and give each piece one job. The phrase goes back to Edsger Dijkstra, who in his 1974 essay On the role of scientific thought called "the separation of concerns" the only technique he knew for effectively ordering one's thoughts.

A practical test: can you describe what a function or module does without using the word "and"? If the description is "it validates the input and saves it and sends the confirmation email", that is three things. The day you need to save without emailing, or email from a different place, you will find out why that matters.

Where it goes wrong: splitting for the sake of splitting. Six files with one function each, calling each other in a chain, is not separation of concerns. It is the same logic with more places to look. Split when a piece has its own reason to change, not because a rule says functions must be short. John Ousterhout's A Philosophy of Software Design makes this case well: a module with a simple interface and substantial implementation is usually better than many shallow ones.

2. Don't repeat yourself (the DRY principle)

The same logic in three places will get fixed in two of them. That is the whole argument, and it is a good one. Duplicated knowledge drifts, and drift is where bugs live.

The original wording, from Andy Hunt and Dave Thomas in The Pragmatic Programmer (1999), is worth reading closely: "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system" (source). Notice the word knowledge. Not lines. Not characters.

Where it goes wrong: constantly. Two functions that look similar today but exist for unrelated reasons are not duplication. Merging them creates one function with a boolean parameter that does two jobs badly, and the next requirement adds a second boolean. Sandi Metz put it best in The Wrong Abstraction: "duplication is far cheaper than the wrong abstraction".

If you are not sure whether two things are the same thing, wait. The third occurrence usually tells you. That rough "rule of three" is a good default for when to extract.

3. Keep it simple (the KISS principle)

Between two solutions that work, take the one that is easier to read. The phrase is usually credited to Kelly Johnson, the Lockheed Skunk Works engineer who wanted jet aircraft that an average mechanic could repair in the field with a handful of tools. That is a good way to think about code too: can the average engineer on your team fix this at 3am with the tools they have?

Clever code is a debt taken out against your future self. Nobody has ever opened a file during an incident and been pleased to find something clever.

The practical version: write it the obvious way first. Only make it less obvious when you have a measurement that says you need to, and leave a comment explaining what that measurement was.

Where it goes wrong: simple is not the same as short. A dense one-liner with three chained operations and a nested ternary is short, and it is not simple. Simple means the reader can predict what the code does without running it. This matters more with AI tools in the loop, because generated code is often compact and plausible rather than clear. If you are accepting code you do not fully follow, read am I learning if I do not understand AI-generated code.

4. You aren't gonna need it (YAGNI)

Build what is required now, not what you think might be required later. YAGNI comes from Extreme Programming, and Martin Fowler's write-up of it is the clearest explanation: building a presumptive feature early costs you the build effort, delays what is actually needed, and adds complexity that everyone has to carry.

Speculative flexibility is one of the most expensive habits in software. The configuration option nobody sets. The plugin system with one plugin. The abstraction layer over the one database you will ever use. Every one has to be maintained, understood by every new joiner, and worked around when the real requirement arrives in a shape you did not guess.

Where it goes wrong: two ways. First, it is not an excuse to ignore things you know are coming. If three customers have asked for a second currency, that is a requirement with a slow arrival, not speculation. Second, as Fowler notes, YAGNI applies to features, not to the work that keeps code easy to change. Refactoring, tests and good names are not "gold plating"; they are what make YAGNI safe.

5. Let tests shape the design (test-driven development)

Write the test first, watch it fail, write the smallest code that makes it pass, then clean up. Kent Beck developed test-driven development as part of Extreme Programming, and the loop is usually called red, green, refactor.

The failing step is not ceremony. A test you never saw fail might be passing for a reason unrelated to your code, and you will not find out until it matters.

The part people miss is that TDD is a design technique before it is a verification technique. As Fowler puts it, thinking about the test first forces you to think about the interface to the code first. Code that is hard to test is usually badly structured, and writing the test first is how you find that out in ten minutes rather than after a week of building.

Where it goes wrong: treating it as all or nothing. Very few people write every line test-first. Use it where the behaviour is well defined and the logic is worth getting right, which in most codebases is a minority of the code and the most important minority. For deciding which tests are worth writing, see five kinds of API test and when each one earns its place. And if you are dropped into code with no tests at all, Working Effectively with Legacy Code is the book for that exact situation.

6. Write for the person who arrives later

That person is usually you, six months on, with no memory of any of this. As Python's style guide PEP 8 puts it, quoting Guido van Rossum, code is read much more often than it is written.

The rule for comments: the code says what, the comment says why. A comment restating the line above it is noise that will eventually contradict the code. A comment explaining why the timeout is 4,500 milliseconds, or why you are deliberately not using the obvious library here, is the most valuable thing in the file.

The same applies outward:

  • Pull request descriptions: the diff already says what changed. Say why this approach, and what else you considered.
  • Commit messages: a short summary line, then the reason. Future you, running git blame during an incident, will be grateful.
  • Names: a good variable name removes the need for a comment entirely. retryDelayMs beats d plus a comment.
  • Code review: explain your reasoning when you ask for a change. I wrote about how to give code review feedback without creating conflict.

Where it goes wrong: writing for an imaginary reader who knows nothing. You do not need to explain what a for loop does. Write for a competent teammate who lacks your context, not for a beginner.

Clean code principles in practice: when two rules conflict

They will. DRY says extract; KISS says keep it obvious. YAGNI says do not build it; separation of concerns says give it its own module. Some common collisions and how I resolve them:

ConflictUsually preferWhy
DRY vs KISSKISS, until the third occurrenceA wrong abstraction costs more than a little duplication
YAGNI vs separation of concernsSeparate only what changes independently todayExtra layers for imagined changes are speculative
TDD vs deadlineTest the risky logic first, not everythingTests pay off most where bugs are expensive
Comments vs clean namesRename first, then comment the whyA good name cannot go stale the way a comment can

The habit that makes all six stick: leave it better than you found it

Not a rewrite. You are in a file for an unrelated reason, you notice a badly named variable, you rename it. A function has grown to two hundred lines and you pull out the obvious piece. Small, in passing, unremarkable. Refactoring by Martin Fowler is the catalogue of how to do these safely, and the free Refactoring Guru site covers the same ideas with examples.

Codebases rarely rot through one bad decision. They rot through thousands of small deteriorations that everybody noticed and nobody had time for. The habit of fixing the small thing in front of you is what stops that. It is also one of the least visible good habits in engineering, so do not expect credit for it. Expect, instead, to become the engineer people trust with the hard parts; I wrote about that in how to become an engineer your team trusts.

How to learn software design principles properly

If you are aiming for the next level, clean, readable code is one of the first things seniors notice. See twelve moves that take you from mid-level to senior and the career growth roadmap.

The rule behind the rules

All six are the same instruction in different clothing: optimise for reading, not for writing.

Code is read far more often than it is written, mostly by people without the context you have now, frequently under time pressure. Every one of these rules trades a little convenience while writing for a lot of clarity while reading. When two of them conflict, fall back on one question: which version will the next person understand faster?

Learn it on Sefism

FAQ

What are the main clean code principles?

The ones that matter most day to day are separation of concerns, DRY, KISS, YAGNI, letting tests shape the design, and writing for the next reader. Each protects against a specific kind of pain, and each goes wrong when applied without judgement.

What is the difference between DRY and KISS?

DRY is about keeping each piece of knowledge in one place so it cannot drift. KISS is about choosing the solution that is easiest to read. They conflict when removing duplication would create a confusing abstraction; in that case, simplicity usually wins until the pattern is clear.

Is it ever okay to duplicate code?

Yes. If two pieces of code look alike but exist for different reasons, keeping them separate is often correct. Duplication is far cheaper than the wrong abstraction. Wait for a third occurrence before extracting.

Do I need to do test-driven development all the time?

No. Use it where the behaviour is well defined and the logic is important, such as business rules and tricky calculations. Most teams mix test-first and test-after depending on the code.

What are software design principles in simple words?

Rules of thumb that help you write code that is easy to read, change and trust. They are not laws; they are defaults that you should be able to explain and, occasionally, break for a good reason.

Ask your own question

If your situation is different from anything here, browse the career questions library first; someone has probably asked something close.

Want a learning roadmap built around you, written answers to your own questions and a place to discuss your FYP? Create your free Sefism account. Members can also book a one to one session when they want to talk it through.

Stay in touch

  • LinkedIn: where I share roadmaps, career advice and honest takes on hiring. Follow, or message me.
  • X (Twitter): short practical thoughts on engineering and careers.
  • Instagram: quick tips and a more personal side.
  • Sefism Discord: meet other students and engineers working through the same job market and share what is working.
  • Sefism on YouTube: walkthroughs and talks.
code reviewtestingproductivity
0

Keep learning

Tauseef Fayyaz

Written by

Tauseef Fayyaz

Lead Full Stack Engineer & Career Mentor. I lead an engineering team by day and mentor engineers through job hunts, promotions and career switches the rest of the time.

Comments (0)

Comments are closed for now.

No comments yet.

From the blog

All posts
Work with me

Stuck on something specific?

Writing only gets you so far. If you want an answer to your situation rather than the general case, book a session and we will work through it together. Sessions are free for approved Sefism members, and a few slots open each week.

  • 1:1 Mentorship
  • Fresh Graduate Mentorship
  • Career Guidance

Brand deals, promos and partnerships: tauseeffayyaz@gmail.com