YAGNI vs Uncertainty

You're sitting there staring at the (pretty good if you say so yourself) code you just wrote. It solves a pretty tricky problem in a pretty elegant way. But something is worrying you: this code might need to change. You're not sure when and you're not sure how, but you just get this feeling tingling in the back of your neck that this code is going to have to change, and probably sooner rather than later. Somehow sensing your hesitation from across the room, that guy comes over. Yo, bro, looks like you got a problem. Just remember, bro, YAGNI! You ain't gonna need all that! You've heard it all before from that guy. You've even followed his advice a couple of times. Once, yeah, he was right, but that other time... oh, that other time. So much extra work that you just know you could have avoided, somehow.

Maybe I exaggerate a bit, but we've all seen or heard of or (shudder) worked with someone who strictly follows the One True YAGNI. They never add any code for any potentiality ever, and they make sure you don't either. Then you have to change something in that code, and you feel all that time being wasted, slipping away. On the surface, it seems like good advice. Yes, we should not write code in anticipation of some future potential change that may never come. Somehow, though, it leads us down a tangled, twisted road.

YAGNI can sometimes lead us to the place of good code, but often it leads us the other way. Why is this? The problem is that YAGNI is not useful in the face of true uncertainty. Sometimes there really are going to be changes that we know nothing about. At that point, we have a decision to make: do we anticipate that change (as best as we can predict the future), or do we ignore that possibility and just write what works only today? Following YAGNI means that we make a decision now to code only what works today.

What's the problem here? Well, we've made one decision to limit what we code. To give some perspective, let's look at what would have happened if we took the other path and tried to anticipate every possible change. Once we agree on what potential changes might be coming, we can start to implement those with if-else blocks and complex code to determine what the future needs might be. Turns out, we didn't need any of this code. As a matter of fact, we actually needed something totally different but equally complex. With all the complex code we put in there, we have a few pieces we can tweak and reuse but most of it needs ripped apart and rewritten. The problem is that we made a decision earlier than we should have, and we made a bad decision.

How does this compare to the YAGNI path? As noted earlier, we are still making a decision now. We don't know enough about the future to know for sure that things won't change. Instead of writing complex, anticipatory code, we write tightly coupled code without the seams necessary to change behavior on the fly. We may even be tempted to start writing in some of that complex code on top of it to just get it done. We wrote the code we needed at the time, but we haven't written the code we always need: code flexible enough to be ready for when the future arrives.

The problem with YAGNI is not that we pick the right or wrong path, but that we pick a path at all. We need to write code that leaves that decision until the last moment before we need to make it. With a little bit of design effort, we can write code that can adapt without a rewrite. Design is the key to this. We can do things like dependency injection and subclassing/implementing interfaces to allow code to transparently work with whatever we need in the present and the future. Sometimes we may not need this little bit of extra design, but the payoff when we do is so great that it outweighs the costs, and it gives us the freedom to change when change comes.


Popular posts from this blog

The Timeline of Errors

Magic Numbers, Semantics, and Compiler Errors

The Cone of Code Uncertainty