Monday July 23, 2018
The Myth of Code Reuse, a quick video by Iain Lowe, became quite popular on Reddit in /r/programming a few weeks ago. I think the reason for it’s popularity is obvious. Lowe talks about the amount of time developers invest in making code “reusable” with interfaces and abstractions, only to watch that code be replaced a few years down the road. Many professional developers have worked with legacy code bases that frequently use these types of abstractions (designed for “future-proof” code), but found the code to be more complicated than necessary and difficult to work with.
Developers have been taught that code reuse is an important part of good code, so they tend to try to put it into every system they design. Unfortunately, this also tends to add to the complexity of the system. Often, the “reusable” code is never actually reused. In the end, the abstraction adds complexity to the system without delivering any additional value.
Similarly, a blog post about the cost of using The Wrong Abstraction was recently on the front page of Hacker News. Like the video, this blog post illustrates the difficulty of working with abstractions that are a poor match for the codebase. Whether by poor design or by a lack of refactoring as the code base evolves, bad abstraction patterns can turn into a big code maintenance cost.
How can developers safe-guard against anti-patterns emerging in their code base to create code that is acutally maintainable? I think there are 2 important rules to follow:
It’s easy for software developers to get carried away with the design of their code. They want to apply all the algorithms and design patterns they’ve learned. They want to use the latest technologies and newest features. They take pride in their work, and they don’t just want a solution that’s good enough. They want the best solution.
This often leads to bloated, complex code that’s difficult to work with. Interfaces and other abstractions pop up all over the code base, even when they’re not needed at all. Sometimes, developers will go as far as developing classes that support things like custom plugin or callback architectures because they envision a future where it might be needed. This usually results in bad code because it’s more complex than it needs to be. It’s easy for developers to be conned into the trap of building cool stuff that wasn’t asked for. If a plugin system is required (as part of the story, within scope) because there’s specific planned work that will make use of it, then it obviously needs to be written. Otherwise, don’t write it. If code is going to be reused (or even maintained for several years), it needs to be easy for other developers to read and understand. Every bit of added complexity makes code harder to maintain. Complexity is bad. In almost every case, the best code is the simplest code.
The counter-argument to the case I’m making for simplicity is, of course, the need to prepare for the future. Some might say that the argument against complexity is short-sighted, and that it’s a good trade-off to introduce some complexity into the system to make life easier down the road. But I don’t think that’s usually the case. The future is unpredictable, and you’ll inevitably build something that is different than what you end up needing.
So, how do you protect your code base against an uncertain future? A high-quality unit test suite is the best way to ensure a code base continues to be maintainable. If developers can’t quickly introduce changes without the fear of breaking existing functionality, the code base is no longer maintainable. The best way to provide the security future developers need (whether it’s yourself or others) is with a good unit test suite. Good unit tests are a kind of living documentation of what the code is expected to do, and they evolve with the code, providing a safety net for making changes. This safety net allows the development team to make changes to the code base quickly, providing a hedge against the uncertainty of the future.
Complexity is bad. Keep your code as simple as possible. Write high-quality unit tests and keep them up-to-date to ensure the code base can evolve rapidly. Unit tests are the best way to future-proof your code base.