Skip to content

Do you really need that abstraction or generic code? (YAGNI)

Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


You Aren’t Gonna Need It. The essence of YAGNI is simple: don’t build something today that you assume you’ll need in the future. However, there’s a nuance here because we also don’t want to handcuff ourselves. Let’s explore how this principle applies both to features and technical implementation.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

The Two Sides of YAGNI

First, let’s break down how people often think about YAGNI. On one hand, there’s the feature side. When developing a product, you might think you’ll need a closely related feature six months down the line. That’s where YAGNI comes in—maybe you won’t need it after all.

On the other hand, there’s the technical aspect, where developers often get caught up trying to make their code generic. They think, “What if I need to implement something similar in the future?” This slippery slope can lead to unnecessary complexity.

An Example: Shipment Management

Let’s consider a practical example. Imagine a system managing shipments. You want to implement a feature that sends a text message when a package is delivered. When the package is delivered, you handle the event, get the contact info, and use Twilio to send a text. Sounds good, right?

In the example code, MessageResource.CreateAsync is directly from the Twilio SDK/Package.

But here’s the catch: some of you might think, “I don’t like calling Twilio directly. What if I want to switch to another service later?” So, you might create an interface, calling it an SMS service. Now, instead of directly using Twilio, you’re injecting this abstraction ISmsService and have an implementation for it.

But here’s the problem: you don’t need it right now. When that time comes, you’ll have a clearer idea of the abstraction you’ll actually need.

Understanding Abstraction

When you define an interface, you base it on your current knowledge, which is often limited to your single implementation—Twilio. If you later want to implement another service, you might find that the original abstraction doesn’t fit. For example, Twilio requires a “from” number, but that might not be relevant for other services. In the case of SMS API, they don’t require a “from” part of their API.

We initially designed our interface around Twilio, which was our only implementation, with some assumption that all other implementations would be similar. And even in this simple example, they aren’t.

So were you creating the abstraction for testing purposes, so you could create a fake? Do you really need the fake?

Twilio provides a means for testing with success and failures by using different from/to phone numbers. But if your comment is that you don’t even want to hit Twilio at all, then what are you actually trying to test?

The Pitfalls of Generics

One of the pitfalls is trying to make things generic to accommodate more use cases. Sure, SMS and email are similar, but trying to create a generic notification service can lead to complexity. You might end up with a notification type that can be an SMS or an email address, and while that sounds good, it complicates things unnecessarily.

Instead of adding layers of abstraction, consider using an event-driven architecture. Going back to our shipment example, if we need to send an email when a package is delivered, we can implement that independently of the SMS feature. If we later decide to remove SMS, it won’t affect our email functionality. This approach allows for extension without modification, keeping your system adaptable.

The Cost of Ownership

When we think about YAGNI, we often focus on the cost of development—how long it takes to build features that may never be used. But we also need to consider the cost of ownership. Adding unnecessary complexity increases maintenance costs and leads to tech debt. Simplicity is key.

While YAGNI is a good principle to follow, context matters. Recognize when a bit of foresight is beneficial. Yes, some experience can help you anticipate future needs, but more often than not, sticking to YAGNI saves you from over-engineering.

Ultimately, write code and build features that have value now, not what might help in the future. This doesn’t mean you shouldn’t use design patterns or architectures that allow for evolution over time. It’s all about balance.

Join CodeOpinon!
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Leave a Reply

Your email address will not be published. Required fields are marked *