Please Reinvent The Wheel
Don’t reinvent the wheel.
I submit the aforementioned for admittance into the “Terrible Folk Wisdom Hall of Fame.” We bandy the phrase about as if it is holy scripture, but think about it…
How many times in the history of the world have we literally reinvented the wheel?
If it hadn’t been for a plethora of inventors and engineers throughout history looking for ways to improve and adapt the wheel for various uses, we’d all be driving around like Fred Flintstone.
The same is true in programming. There are many who will say, often quite vehemently, that you shouldn’t waste your time writing original implementations of existing patterns or algorithms; indeed, some would say you shouldn’t even bother developing new patterns and algorithms. “We have all these great libraries, so why waste your time? You could never write something as good!”
Aren’t we glad none of the authors of those patterns, algorithms, and libraries listened to their contemporary detractors?
Now, before a whole bunch of y’all take this as license to go reimplement bubble sort for the ten-thousandth time, let me assert that we don’t reinvent the wheel for the sheer sake of reinvention. You have to know the appropriate situations.
(Re)Invention Reason 0: Learning
I’m going to get the first reason out of the way, because it isn’t technically an motive for reinvention so much as reimplementation. One of the most effective ways of learning an algorithm or pattern is to implement it yourself.
You’ll notice that I listed this as Reason
0, and that’s intentional. Implementations written for this purpose rarely see the light of day, unless they wind up growing into one of the other categories.
Implementations written for Reason 0 usually…
- Implement the exact algorithm, without any improvements,
Are slower than their mainstream counterparts, and
Are less stable and maintainable than their contemporary counterparts.
Thus, even if you write a fresh implementation for learning, you should think twice, and twice again, before you use it in production (or distribute it to others).
(Re)Invention Reason 1: Better
One reason to reinvent the wheel is if you can improve upon its design. Wheels have seen the addition of spokes, tires, tubes, rims, treads, hubcaps, and all manner of improvements. These enable wheels to perform better in specific circumstances.
In the same way, reinvention may be justified if you can improve on an existing design. For example, I developed IOChannel as part of PawLIB. My library added a lot functionality on top of
fprint, allowing for easier printing and formatting of many types of information, including memory dumps.
However, a word of caution: be wary of piling on bells, whistles, and gongs! Don’t add functionality just for the sake of functionality. “YAGNI” (You Aren’t Gonna Need It”) is an excellent acronym to remember here: only add features for which you or your target user has a specific use case (or user story).
In other words, your invention shouldn’t make toast unless it’s a toaster.
(Re)Invention Reason 2: Faster
Another common reason to reinvent wheels is the need for speed. A racecar cannot race with wagon wheels; it needs a type of wheel specifically designed for driving on the raceway at high speeds.
Many libraries are designed to be all-purpose tools, sacrificing many specific optimizations to remain useful to the largest possible set of users. However, once you know what your specific use case is, you will often achieve superior performance by creating a custom implementation of the algorithm, pattern, or data structure.
Writing high-performance code isn’t something you can approach casually. It is a serious investment, requiring lots of time, careful attention, extensive testing, and a lot of study and source reading!
Aside from this, sometimes a coder can develop novel optimizations to improve the general implementation. At times, one may contribute these improvements to a mainstream library, while other times call for a fresh new library. This decision is not something to take lightly.
Personally, I have been discouraged by some naysayers to try and beat the performance of mainstream implementations, but the fact is, I have done it. Time marches on, revealing new innovations and improvements every day. Our craft needs people who are willing to challenge the Tried and True.
Failure here is not something to be ashamed of. Performance optimization is an art. You learn something new with every failed attempt at a faster implementation. If you have a serious need for speed, keep at it!
I’ll conclude this reason with another word of warning…
“Premature optimization is the root of all evil.” -Donald Knuth
Before you start fine-tuning your racecar to be the fastest around, you need to build a functional vehicle first. Get it working, then working well, before you embark on a full-blown optimization. Again, this is not an endeavor to take lightly. Optimization is a feature all its own.
(Re)Invention Reason 3: Cleaner
Sometimes a new implementation doesn’t need new features or faster speed to be worthwhile; sometimes it merely needs to be cleaned, refactored, and made maintainable!
Abstractions are tricky things. For every language, library, and tool we use, we are adding another level of abstraction, and as Joel Spolsky pointed out…
“Abstractions fail. Sometimes a little, sometimes a lot. There’s leakage. Things go wrong. It happens all over the place when you have abstractions.”
The truth is, we have to maintain this stuff, and sometimes that maintenance falls on the user, rather than the author.
With that in mind, which code base you rather have to jump into?
A) A popular library with a 10-year-old code base half-conforming to a 1998 standard; completely uncommented, sparsely documented, with hundreds of open issues, many dating back to the early 2000s.
B) A new library with the exact same features, written to strictly confirm with a recent standard; well documented, intent-commented, with relatively few open issues.
Now let’s make the decision even simpler:
A) Use feature X from Library A (above).
B) Write a fresh implementation of feature X, and skip using Library A altogether.
Quite often, it’s easier to write a new implementation, rather than untangle a ten-year-old plate of spaghetti.
Once again, the naysayers will probably start shouting at you: “What makes you think you can do it better than Library A, you cocky little whippersnapper?”
Your response should be “What makes you think I can’t?”
As with Reasons 1 and 2, this is not something to embark on flippantly. You have to be prepared to write code that achieves the exact same goal as the old implementation in question, but cleaner and with less bugs. What you don’t want is to create Yet One More Terrible Implementation that other coders will have to put up with.
By the way, for a real life example of this, compare and contrast the GCC compiler and standard libraries with those of LLVM/Clang. My company uses the latter primarily on grounds of the code base!
Other Reasons To (Re)Invent
There’s a few other reasons that might necessitate reinventing the wheel:
- Licensing reasons. Maybe the existing implementations are all proprietary, or by contract, all GPL-licensed. Sometimes the only reason you need for reinventing the wheel is so you can use it, and that’s okay.
The existing implementations are abandoned. Sure, you could try forking the library and developing it further, but it will depend on whether that’s more practical. (See Reason 3).
Because dependencies = pain. Sometimes it just isn’t practical to bring yet one more dependency into your project. If, after careful consideration of your options, you find that writing your own implementation is faster or better than becoming dependent on a library, or otherwise injecting third-party code into your source, go for it.
Reasons Not To (Re)Invent
Now that we’ve covered the major reasons to “reinvent the wheel,” let’s talk about a few reasons you absolutely shouldn’t:
- Because I can (unless Reason 0). Unless you have a specific reason to reinvent, please don’t. We already have too many poor Yet Another implementations out there, clogging up GitHub and Sourceforge.
Because NIH [Not Invented Here]. Believe it or not, there are talented coders that don’t work for your organization. Thoroughly explore all your options before you decide to reinvent the wheel.
Just because I don’t personally like the developer. I’m sorry, get over yourself. If your only reason for writing a fresh implementation is to show up another developer, you’re just feeding your ego. Nobody wins in that scenario.
Because I can do better with one hand tied behind my back. Yeah, you probably can’t. Always assume that reinventing the wheel is hard.
Because it will only take a few minutes. Ha ha ha ha, nope. Any time I catch myself thinking “I’ll take a few minutes and…”, I know I’m setting myself up for failure.
Because I didn’t do my research. There is never an excuse to not do your homework on existing implementations!
Don’t Be A Naysayer!
If you only remember one thing from this article, let it be this:
For the love of all things digital, please IMMEDIATELY strike “don’t reinvent the wheel” from your vernacular!
This phrase encompasses all the muse-dousing pessimism that stalls progress. We have not run out of ideas, and we never will. Every time you tell someone not to invent the wheel, you may be shutting down the next Guido van Rossum, Alan Kay, Tim Peters, or Donald Knuth. You may literally be the voice that prevents someone from creating the next breakthrough in language processing, the next fastest sorting algorithm, or the next watershed programming language.
Instead, encourage people to approach reinvention with clear, careful intention. Encourage is the key word here! You may be igniting the next spark that changes the world for the better.