Most software engineers would agree that simple is better. Simple software is easier to understand, easier to maintain, and more fun to work with. On the other hand, most of us don’t work with simple software. Or, if we do, it’s not simple for long.
If we all like simple software so much, why isn’t the software we work with simple? We don’t sit down one day and decide to make our software complicated. Instead, it happens one tiny step at a time. One decision where we choose complexity over simplicity, and then another, and another.
So why does this happen? If simplicity is so great, why do we keep choosing complexity? We do it because it feels right at the time. We add an abstract class, or new message bus because we’re building well designed, robust software. We work hard to develop this instinct. Unfortunately, it can often lead us astray.
The situation usually happens something like this: You’re adding a new feature to download a profile picture from S3. You’ve written a simple function that takes a user ID an image size, and then returns the downloaded image. You create a PR for your change, and your teammate says “What if we need more than one image size? Wouldn’t making multiple requests be slow?”.
This question seems innocent enough, but it conceals a hidden danger. It asks you to predict the future. Predicting the future is a necessary part of software engineering, but being asked to predict the future should make you feel uncomfortable!
Why feel uncomfortable? Because you’ll frequently guess wrong. Guessing wrong is the default outcome when predicting the future. Only with many years of prior experience can software engineers begin to do it with any sort of accuracy. Even then, our success rate is not high. Future you will always have more information than current you.
It gets even worse. There are unconscious biases at work. You want to be a good engineer. More complicated code can be more fun to write. You worry that you’ll regret the decision later. Just asking the question is enough to bias your decision making. The odds against making an accurate prediction are stacked against you.
Why worry? We make decisions like this all the time. So what if our code is a little more complicated? Perhaps it’s better to have flexible, powerful code. The problem with that line of thinking is that you’ll make the same choice next time. Eating a cupcake one afternoon is fine. Eating a cupcake every afternoon quickly becomes a problem.
That’s not even the most worrisome outcome. The real danger is not writing extra code, it’s writing the wrong code. Abstractions are one of the most powerful tools in software design. Good abstractions can make the most complex problem domain simple to grasp and a pleasure to code. The opposite true for bad abstractions. They are hard to understand, and they warp all the code that interacts with them. Something as simple as logging can be a nightmare with a bad abstraction. When working with incomplete information the chances of creating a bad abstraction are high. Good abstractions come from hard won experience with a problem domain, not from an anticipation of future needs.
So how do we avoid this trap? Let’s go back to the question. “What if we need more than one image size? Wouldn’t making multiple requests be slow?” In this hypothetical situation, you have working code that fits your current needs. You teammate is asking you decide whether you should add new functionality in order to handle an anticipated future need.
When dealing with this sort of question, it’s important to keep two things in mind. How confident am I that I’ll really need the additional functionality, and what will happen if I do nothing?
In our hypothetical example, we are asked if we need to pull multiple images. Be skeptical! Do you really need to do that? Do you have any evidence that pulling multiple images would be required? Perhaps you have a UI spec that clearly shows more than one image size. Or perhaps you’ve written similar apps before and know you are likely to need them. Or (hint: this is the most likely), you think it would be convenient to pull more than one image at a time. Unless the likelihood of needing the functionality is very high, skip it for now.
If you do decide you might need the new functionality in the future, do you need to add it now? Can you put the decision off until you have more information? How difficult will it be to un-do my decision later? If you can go back and change your mind, then there’s no need to worry. Only if it’s hard to change later do you need to consider the new functionality now. For example, if the profile image fetching function is only called in a few places, then it’s simple to go back and update any code that calls it. If, on the other hand the function is part of a library that will be used by many teams, making changes to the function signature would be much harder. Be careful though! It’s easy to assume that changing your mind will be harder than it really is. Consider the above example. If you are writing a library, you might keep the function signature as it is, and add a new function instead. That would let you add the new functionality when you need it without breaking existing users.
What about the second part of the question? “Wouldn’t making multiple requests be slow?” We could go through the the same process again, but the important point is that the second question is only relevant if the first question is relevant. If you don’t need to pull multiple image sizes, then you don’t need to worry if pulling multiple images might be slow. It’s important to watch out for this situation. It would be very easy to let a discussion on the performance implications of pulling multiple images distract you from the question of whether you need to pull multiple images in the first place. This sort of situation is very common when considering architectural decisions. You have many seemingly connected complications and trade offs. In practice, there is usually only one ore two critical decisions, and the rest are just details. The trick is to untangle the important decisions from the rest.
It might seem like I’m advocating for simplicity at all costs. This isn’t the case. There are worlds in which you really do need to pull multiple images. It’s your obligation to determine if that world is the world you live in. So, develop a habit of feeling uncomfortable when these types of questions are asked. Then take a step back and think. Ask yourself if you really do need this feature? Is this decision hard to un-do? Unless the answer to both questions is a resounding yes, resist the temptation, and keep your code simple.