YAGNI is (or at least should be) a familiar acronym for many developers. It stands for:
You Ain’t Gonna Need It.
It basically means, “don’t develop something unless you’ve been specifically asked to and there’s a need for it right now“.
Since everyone loves car analogies, it’s like spending extra money for snow tires since, you know, you might want to go drive around in the snow one day. Also, you live in Hawaii.
Or, to bring it back to the development world, and pluck a random example from thin air: let’s build a book store. We’ll call it… Slamazon. Our users can sort books by title, author, review rating. Sounds good, but you know what, let’s also add a feature to allow users to sort by ISBN.
Is it cool? Well, I suppose it’s different at least. Is it useful? Prrrrrobably not. Are you gonna need it? Let me hear you say with me:
You Ain’t Gonna Need It
Remember that when developing something, the time and effort investment includes not only the initial coding, but the time needed to test, maintain and integrate the code into the overall app. If you skip over all those steps, what’s the point of implementing potentially buggy, app-crashing code in the first place?
So yes, the YAGNI rule of thumb is one to keep in mind. But how do you know when things you’re working on are going to hit the “YAGNI Point”? The first question to ask yourself when adding a new method or implementation is, like with the ISBN sorting function, are you gonna need it? Obviously, a solid yes-or-no answer is best, but, well, any dev knows that the answer is so often “maybe”.
I recently worked on two separate projects and bits of code that both could kind of be considered a YAGNI situation. But as I’ll explain, each scenario came with different circumstances that shifted those extra add-ons from excessive to necessary.
Situation 1: YAGNI, but it’s best practice
In this first case, the project needed a wrapper around a connection to a NoSQL database (let’s call the database… Smongo). The wrapper was a facade to abstract user fetching and make the interface simpler.
If you were the one coding this into being, of course you’d want to write nice Python code with type hinting and so on. So maybe you’d do something like this:
from smongo import SmongoConnection class SmongoUserFacade: connection: SmongoConnection def __init__(self, connection: SmongoConnection) -> None: self.connection = connection def get_user(self, user_id: int) -> None: return self.connection.query("users").filter(id=user_id)
Now that you’ve got clean solid code, you’d naturally use it in a controller or view, etc.
class UserViewController: user_database: SmongoUserFacade def __init__(self, user_database: SmongoUserFacade) -> None: self.user_database = user_database
Now, let’s say there’s a chance the client might want to use a different NoSQL database at some point in the future. Maybe one called GravenDB.
We could do this by writing a new class called
GravenUserFacade, and renaming all references. But our case is simple; we’ve only referred to
SmongoUserFacade a couple of times. It gets a lot harder when it’s peppered throughout the application. Also, if you end up needing to use one DB in test and one in production, we’d be going back and forth renaming all the time!
This sounds like we’re getting dangerously close to YAGNI territory, but what if we could spend literally one extra minute of development time and solve any of these future issues?
If we introduce a base class, more specifically, an abstract base class, we can make this problem go away:
import abc from smongo import SmongoConnection class UserFacadeBase(abc.ABC): @abc.abstractmethod def get_user(self, user_id: int) -> dict: pass class SmongoUserFacade(UserFacadeBase): connection: SmongoConnection def __init__(self, connection: SmongoConnection) -> None: self.connection = connection def get_user(self, user_id: int) -> dict: return self.connection.query("users").filter(id=user_id)
Then our view controller can refer to the base class instead.
class UserViewController: user_database: UserFacadeBase def __init__(self, user_database: UserFacadeBase) -> None: self.user_database = user_database
Eventually when we add a
GravenUserFacade class we don’t need to change any of the type annotations. Making use of the Abstract Base Class makes sure the subclasses have the right interface.
Why don’t I consider this a YAGNI solution, even if it’s something we may never use?
- It's a very small amount of extra code and took about a minute to write, so there's no big budget-blowout by implementing it.
- We hit this code path all the time. Since we're using
UserFacadeBase, via its subclass(es), it's not unused code that's just hanging around untested.
- You could consider this the "best practice" method anyway. In some languages (Java), this is the preferred way of implementing classes. I've worked on some enterprise applications where literally every class consisted of a
Baseinterface and then a
Situation 2: YAGNI but it’s literally no extra code
This example comes from the very blog you’re reading right now. I write this blog using Markdown, and then Mistune converts from Markdown to HTML.
Images are included in the normal Markdown way:
This works fine most of the time, the standard responsive image classes do their thing. But, occasionally, I need the images to be a certain width and height, so a while back I adjusted my Markdown renderer to work with
height query parameters, like so:
These are parsed out and the image renders like so (spacing added for readability, and caption markup omitted):
<img src="http://www.example.com/path-to-image.jpg" style="width: 150; height: 100;" title="Title" > <!-- and some caption markup -->
Notice the new
style attribute and missing
height parameters from the URL.
However, there’s an edge case that, while really implausible, could theoretically happen.
- I want to include an image without hosting it myself (unlikely, since I think hotlinking is bad).
- The image must be requested dynamically and adjusted with
heightparameters (unlikely that these would be required parameters).
- These parameters are called
height(possible, but they could also be
h, for example).
As you might be able to guess, since
height parameters are stripped from the URL, they wouldn’t be passed on to the source host.
This is such a minor problem that I asked myself: should I bother to fix it? Or are we getting close to YAGNI territory?
Luckily, in this case the fix was super easy! Since I could pick the parameters, I just changed them to something that’s unlikely to ever conflict. Here’s the parameters I really use:
Yes, it’s certainly more verbose. But is it YAGNI? I’d say no:
- It's not separate extra code, I've just used different words.
- It didn't take any more time to do it this way.
And, with this minimal effort, I’ve freed my mind of any need to worry about the potential for this bug to crop up sometime in the future; no matter how unlikely it was, the peace of mind is worth the extra .
The Rules of Non-YAGNI
As we said at the very beginning, the first question in the YAGNI checklist is: are you gonna need it? In the case of a “maybe” answer, there are a bunch of criteria to go through to determine whether it’s worth putting YAGNI to the side and writing it anyway.
In deciding if writing some extra code is YAGNI, think:
Is it best practice?
Best practices became best practices for some very good reasons. Strictly necessary or otherwise, they help keep your code clean and readable to others, and often make it easier to go back and make changes if and when you decide that, yes, you really are gonna need that extra implementation. You may not need it, whatever it is, but if it made it into best practices, go ahead and do it anyway.
Does it take very little time to write, test and integrate?
If an implementation of a new function is going to take hours to do properly, then fair enough. You’ve got work on your checklist that you’re actually getting paid to do; maybe it’s an interesting extra implementation that might be useful down the line, but it’s not the priority. Maybe write a note to yourself and pitch it to the client some other time.
If it’s going to take ten minutes? Well, that’s a different story. If you can knock out a safe and reliable implementation in the time it would take for a coffee break or toilet run, then go for it. Which leads to the next item:
Will it really go unused, or will it maybe save time down the line?
Maybe this function isn’t particularly useful right this minute. Maybe it’s an extra step in a chain that doesn’t really need it at the moment. But let’s look back at Situation 1. In the entirely possible event that the client changes their mind down the line, it made sense to implement an abstract base class, preventing the need to go back and make grand sweeping changes throughout the application.
It is entirely possible, likely even, that the client will be happy with their initial decisions, meaning that such preventative code won’t be any better than a more specific bit of code would’ve been. But so what? It still works. And if the client does change their mind? Well, then you’re laughing.
Let’s TL;DR this list down a bit:
- Is it best practice?
- Is it fast to write?
- Will it be useful later?
From here, it’s down to your gut. Keep in mind that you’re trying to save time here; if you can’t give quick, fast yes-or-no answers to these questions, then you’re just wasting time and you should just assume YAGNI. If going through the list gives you all yes responses, then go for it. Otherwise, use your own judgement.
Say no to YAGNI, say yes to YMNNIBINEETDITRWNASLOTIAHTF
Don’t over-engineer solutions. But remember that in the right situations, doing a little bit of extra work now (or in some cases, no extra work), can save you a lot of trouble in the future.
At Tera Shift we believe in YAGNI, and don’t like to waste time working on solutions to problems that never existed. But we also believe in You Might Not Need It But It’s No Extra Effort To Do It The Right Way Now And Save Lots Of Time And Hassle In The Future.
Admittedly it’s not as catchy, but it’s a good philosophy to live by.