Same Same is Good Enough

Ayomikun OsotaAyomikun Osota
4 min read

As a developer, I’ve been fed a consistent message throughout my career: Don’t Repeat Yourself (DRY). Every course I've taken, whether from Andrei, Angela, John, or other instructors, has drilled this principle into my head. The message is always the same: when you find yourself copying and pasting code, you're likely making a mistake somewhere. It’s a valuable lesson, no doubt—one every programmer should take to heart. But you're probably wondering, "Ayo, we already know this, so why bring it up again?"

Well, what if I told you that sometimes repetition is actually good? Yes, too much abstraction can be just as harmful as no abstraction at all. There's such a thing as going overboard with DRY. Now, this advice isn’t necessarily for junior developers. If you’re just starting out and working on small projects to build your portfolio, by all means, practice abstraction as much as you can. It’s a crucial skill to learn.

But in professional environments, I’ve found myself abstracting less and less—and not doing it automatically. This realization surprised me, especially since I used to consider myself a master at code abstraction. I would look down on developers who repeated code, thinking they were sloppy or inexperienced. Now, I sometimes find myself doing exactly what I used to preach against.

Here’s the thing: instructors often create sample projects that remain static. If you’re building a project for your portfolio, you’ll likely never revisit that code once it's done. But production code is different—it changes all the time. Designers, project managers, and even marketing teams can decide on changes or experiments whenever they feel like it, often in unpredictable ways.

Imagine this:

  • Designer: "Let’s make the call-to-action button invisible but with a subtle glow when you hover over the logo. It’ll be super minimalist, but also a little mysterious, like the button is playing hard to get."

  • PM: "We need to make the entire user interface horizontal instead of vertical. People scroll too much these days; we’re flipping the script—literally. Everyone loves swiping sideways, just look at Tinder!"

  • Marketer: "So, I read this article, and apparently, puppies boost engagement. Let’s add an animated puppy in the bottom-right corner of every page. And when users click it, it should bark and give them a 10% discount code."

These changes might give any developer a headache, but hey—I’ve seen even worse! 😄
This constant state of flux is where too much DRY can become a problem. It can make the codebase harder to maintain, defeating the very purpose of abstraction in the first place. And if you're collaborating with other developers, overly abstracted code can slow the team down and lead to confusion.

Here’s an example of over-abstraction that can go wrong:

function renderButton(text: string, onClick: () => void, styles: string) {
  return `<button class="${styles}" onClick="${onClick}">${text}</button>`;
}

This function might seem DRY and reusable at first glance. But as the design changes, you may have a button that needs custom icons, tooltips, or conditional rendering. Suddenly, this single function becomes a nightmare of edge cases and parameters:

function renderButton(
  text: string, 
  onClick: () => void, 
  styles: string, 
  icon?: string, 
  tooltip?: string, 
  isDisabled?: boolean
) {
  let buttonHtml = `<button class="${styles}" onClick="${onClick}" ${isDisabled ? 'disabled' : ''}>`;

  if (icon) buttonHtml += `<i class="icon-${icon}"></i>`;

  buttonHtml += `${text}</button>`;

  if (tooltip) buttonHtml += `<div class="tooltip">${tooltip}</div>`;

  return buttonHtml;
}

It’s become overly complex for something that should be simple. Not to mention, each time the marketing or design team wants a new button variation (with puppy animations, for instance), you'll have to keep expanding this function.

A simpler, more straightforward solution? Sometimes repetition is okay:

<button class="primary-button" onClick="handleSubmit()">Submit</button>
<button class="secondary-button" onClick="handleCancel()">Cancel</button>

Yes, you repeated two <button> elements. But now each button can be customized individually without worrying about how it fits into a general, overly abstract function.

When to Abstract, and When to Stop?

In the past, I used to abstract code the moment I copied and pasted it once. But that approach got me into a lot of trouble. Now, I wait until after the third copy-paste. By that point, I have a better sense of what parts of the code are genuinely reusable. It also gives me time to think about whether I should create a new component, use Object-Oriented Programming (OOP), or both. It’s important to predict if the code is likely to change, and if so, what those changes might be.

Also, you should stop abstracting when it becomes more work than just rewriting the code. For example, if a component starts requiring too many props, it can become a hassle. And don’t abstract code just because you think you might need it later—wait until that "later" arrives. After all, You Aren’t Gonna Need It (YAGNI).

When Separation of Concerns Becomes a Problem

Separation of concerns is a great principle, but it falls apart when one piece of code ends up with too many concerns. You’ll only learn the balance with real-life experience—or by reading articles like this one.

0
Subscribe to my newsletter

Read articles from Ayomikun Osota directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ayomikun Osota
Ayomikun Osota