There’s a fine balance between planning for the future and coding for it. This article discusses how to be ready for changes without over-committing now.
If you have any level of experience as a developer, it includes trying to add a feature to a brittle piece of code. The experience was not much fun: you may have put in extra hours, and the project probably still came in late. If you’re lucky it was a small project, or at least you weren’t responsible for that big ball of technical debt.
Coding for the future
The obvious solution to this is to plan, and code, for the future. Next time you wrote an important module you may have spent time considering future use cases.
How might this module be used?
After considering these cases, you designed a system to handle them all. The scope was a bit larger than when you started. You had to adjust the schedule a little bit. But, you tell yourself, it’s going to be worth it. When those inevitable changes come, I’ll be ready. So you set about building a module to handle all the cases. Unit testing discovers some nasty interactions between the extensions, but you catch them and fix them, adding only a little time.
Occasionally you get lucky. You guessed right and when the next feature comes along, it’s a breeze. This happens fairly often when you have a project that has a stable roadmap and few unknowns, or you place only small bets on the near-term future. You do work on a product with a stable roadmap and few unknowns… right?
In faced-paced environments, two other outcomes are much more likely:
You didn’t see that coming
The swiss-army-knife module you built doesn’t really fit the new use case very well. If it’s your own module, you know you couldn’t have anticipated this. If it’s someone else’ you wonder how they built such a complicated and flexible module, but overlooked this really obvious feature. It’s probably not as bad as that hair-ball you wrote just after “Programming 101”, but it’s frustrating that your planning didn’t really pay off.
The module you built just sits there for years, doing it’s thing. This seems like a happy ending, except that you spent extra time up-front with no payoff. This is fine if you’re content to be the team member known for “always doing a god job… eventually.”
I’m here to tell you that there’s a better way.
Not coding for the future
The problem above has been faced by programmers for decades, and the solution is summed up in the pithy phrase
“You Aren’t Going to Need It”
I first learned this phrase from Robert. C. Martin’s “Agile Software Development” however I believe it originated with the eXtreme Programming movement of the 1990s.
The trick is not to ignore the future entirely, but to have your cake and eat it too. That’s right, you can write code almost as quickly as if you were ignoring future changes, and still smile when those changes arrive: it’s almost as if you had been psychic all along.
You do this by writing code that is easy to extend in the future, without trying to actually solve future problems now.
Actually doing this is a skill that you won’t develop overnight, but I can get you started on the right path with the following two approaches:
- Planning (only) – For the future you can imagine
- SOLID Design – For the future you can’t
It turns out that the first half of the common response to avoiding future problems was correct: think about the future. Even a small amount of time actively thinking about the future is a great way to identify some likely future changes.
Creating a priority system?
Perhaps users will want to rename the priority levels.
Serializing in-memory data to JSON?
You may want to use msgpack or BSON eventually.
The trick is not to actually build-in support for any of those things.Instead, what you want to do is use them as test cases for your design: if I needed this feature in the future, would my current design make it more difficult?
This is a bit subtle, so let me use the case above as an example:
Planning (Only) Example
Imagine you’re writing a module to serialize a few C++ simple structures to JSON. I picked C++ here because it has notoriously limited reflection abilities.
An obvious approach might be to write toJSON(typeX x) methods for each structure that directly convert the data to JSON strings, either manually or with a JSON SAX generator. This is minimum-effort, but it’s not very reusable if you want to support another format, an example of not planning ahead.
A more sophisticated approach would be to develop an intermediate representation for the data. You could convert each structure to that representation, then write a reusable JSON serializer for it. This isn’t a bad approach, but it’s definitely more work. This is an example of coding for the future that may not pay off.
The best of both worlds, in this case, might be to use a JSON library with a DOM model. Write methods to map each structure to the JSON DOM, and return that object. Then simply call the “toString” method on the result to get JSON. This is about the same level of effort as the first approach. It may be marginally less efficient, but you’re using JSON, so it’s unlikely that was your top concern. How does this help us for the future though?
The trick is the DOM. Someone has already created an intermediate representation for anything you can store in a JSON document. If you ever need to write a different format, you can write a single, generic “toSomeOtherFormat” method that accepts the JSON DOM and writes the target format, reusing the code specific to each structure.
This third approach takes approximately zero extra effort compared to the first approach, and is almost as flexible as the second.
The key here is that we didn’t add significant extra work or extra complexity up front. We simply decomposed the work to expose interfaces that allow future use cases to be implemented more easily. In this case, we separated the two implicit steps:
- map C++ structures to a self-describing format, and
- Generate valid JSON
Those two steps happened in all three examples. It’s just that in the first example we combined them into a single function, and in the second example we created a lot of extra work. The third example separates these steps without extra development effort.
Just in case you’re thinking this is a special case, I want you to imagine you couldn’t use an off-the-shelf DOM. Could you still get the best of both worlds?
Once you start looking at problems this way you’ll start to see them everywhere.
SOLID Design Principles
Planning can help you write modules that will work well for future requirements that you anticipated. What about those requirements that seem completely unforeseeable?
Much like it’s hard to predict whether it will rain next weekend, but easy to predict that it will be cold in winter, there are common types of changes that occur in software. While the details can be impossible to predict, these changes follow common general forms. Even better, smart people have figured out good ways to deal with them. These are often referred to as design principles, and the SOLID principles are, in my opinion, some of the best.
According to Wikipedia, The SOLID principles were first described by Robert C Martin in his 2000 paper “Design Principles and Design Patterns”, although the acronym was coined later.
I won’t go into these principles here. Wikipedia has a great introduction, and of course Robert Martin’s own Blog and Books expand on them.
I will tell you that if you really learn these principles, and you apply them, your code will almost accommodate whatever the future may hold.
- You’ll find it much easier to extend your code. You’ll do less rewriting and less refactoring.
- You’ll find that your team-lead or customer understands: effort will correlate much better to the size of the change as observed by the customer. You’
The Pragmatic Programmer, by David Thomas and Andrew Hunt is another great resource. I’m particularly fond of the catchy maxim “Don’t Repeat Yourself” or DRY. However, I suggest this book for pros looking for a different perspective, not for beginners. It contains a lot of wisdom, but it’s also a bit old-school and includes some guidance that I feel is counterproductive. Read it once you have a solid foundation and are comfortable deciding for yourself.
Put it into action
Starting tomorrow, you can
- Start thinking about future use cases
- Start testing your design against those cases.
A good design can be reused without being rewritten.
- Stop coding for future use cases
A good design for today doesn’t try to solve the future use cases.
- Review the wikipedia article on SOLID
Over the next month, you can review each of the SOLID design principles in detail. Use Wikipedia, Robert C. Martin’s Blog, or buy a book. I would suggest giving yourself 1-3 weeks to absorb each principle and try to put it to use in your work or in a pet project. You might also consider doing a case study – consider a change that was harder than it should have been. Can you spot a violation of one of these principles in the design you had to work with?
If you’re interested in the books I mentioned above, the amazon links here generate a small commission for this blog. They won’t cost you anything extra.
I personally find books to be worth the money as the clear structure that goes into a good book helps me absorb the content. However all of these ideas absolutely are available for free online as well.
It turns out that the new edition of “Agile Software Development, Principles, Patterns, and Practices” has become rather expensive. If you decide to check out Bob Martin’s newer books, please let me know what you think!
Future – Throughout this article, when I refer to “future” I mean “uncertain future”. In general this would be anything that isn’t both well-specified and committed with a near-term date.