OK, maybe it’s not always inherently bad, but it is definitely not what it’s cracked up to be. And it’s definitely not the end-all-be-all that most of us were taught.
In my early years, I was dogmatically taught never to write the same code twice. Anytime you see duplicated code, or even similar code, refactor it into a function, right? What if the code was slightly different? Create some parameters. What if you need one extra field in some cases? Add a little logic around that to decide what to return. What if you run into a case where you can’t fix one caller without breaking another caller? Add another layer of abstraction and logical branching to your function. At this point, you started out trying to improve your code-hygiene a little bit, and all you’ve done is spend the whole day creating a monstrosity that is almost impossible to understand. Well sure, you don’t think so, but hey, nobody likes to admit their kids are ugly.
Always, always, always, stop and ask yourself, “Am I creating more problems them I am solving?”
Keep It Simple, [Obscenity]!
The fact of the matter is, when you try to create reusable code, you are inevitably making things a lot more complicated. It’s rarely a case of just taking what you have already written and exposing it to another component or team.
If you are using the code for yourself, you just have to make sure it works for you based on how you are going to use it, and you should be able to clearly visualize all of the possible scenarios. However, in order to make the code reusable, you are creating a black box to be called by another developer; as such you need to make it work no matter what, with any sort of crazy input, and you will need to make sure that it is clearly reporting back to the caller when something goes wrong. You need to make sure that the interface makes sense to everyone, not just you. You may even need to write up some documentation (ugh). And when you are all done and have covered every base, someone will call your component in a way you never expected, and of course it won’t work because all of the code we write is rife with bugs, and everyone will argue about how this component “should” be used, and someone will demand to see the unit test coverage stats, and someone else will schedule a meeting to define the scope, and someone else ramble on about how we need a framework, and the manager rolls his eyes and swears never to let these damn developers try to build something fancy again, and nobody is actually getting any real work done.
Worser Is More Gooder
Of course, some will argue that you should always try to make your components as reusable as possible, because it requires you to work through all of those quality issues no matter what, and will produce better software. This would be great if your sole goal was to create the best software in the world, but nobody is paying you to do that. No, you are being paid to write software of sufficient quality within the time and budget allotted. If you spend unnecessarily time gold-plating your code, it may make you feel cool, but it is outright wasteful. I’m not saying to cut corners when you should not, but I what I AM saying is that there definitely are times when it actually does make sense to cut comers. You need to draw a line in the sand of exactly how good this product really needs to be and stick to that, otherwise you’ll never finish. You can software that is really cool, or you can build software that ships. Only one of those options is actually useful to society.
But, But, But….
OK, so don’t make something reusable if you done need to. However, what if you think you are going to need it to be reusable? I have a secret to tell you: YAGNI.
I can’t tell you how many times I’ve been working on a very specific implementation to solve a very specific problem, and somebody (who usually has initials in his job title) chimes in saying “well, make this and that and the other thing configurable, so when we sell some other product to some other company, we can include it just by flipping a few switches.” This person is figuring that “it’s just an ‘if’ statement, what’s the big deal?”, but that person is not taking into account the increased code complexity and the exponential increase in testing requirements. Often times, this is even a former developer, who actually knows how code works, but has been out of the trenches for long enough that it has impaired their judgment about code complexity and its day-to-day impacts.
But then again, math solves everything. Say your QA department originally had 143 test scripts to run through before release, and now you’ve added some new configurable switch into the system. If you really want to be thorough, you should now have 286 test scripts, because you now have to retest everything with the switch turned on, and then again with the switch turned off. Say you had actually added to different switches? 572 test scripts. That’s a lot of work for a feature that will never get used. And come on, between you and me and the desk, we all know that Mr. Executive SVP COO is never going to close those magical deals with XYZ Bank and ABC Manufacturing, partly because he has no idea what he’s selling, but also partly because you guys can’t ship anything because you now have 1144 test scripts to run through to get a small release out the door.
So How Do I Know What To Reuse?
If you aren’t going stand around and guess what to reuse, how we will know? Easy, stop trying to prematurely reuse anything. Instead, refactor your existing code to reuse that which is already duplicated, where you can reasonably say will actually benefit from reuse and reduce overall maintenance and complexity.
How do you know when that is? Use that fancy experience and judgment of yours, it’s the only thing that separates you from the robots that will be doing your job some day.
Well what if I don’t have the necessary experience and judgment? Easy, pay me to write your code for you. email@example.com. </plug>
Anyhow, my rule of thumb is: Never build something to be reused until you actually have two systems that actually need to reuse it right now. If you only have one thing that will use it right now, and that other mythical feature will also use it some day, deal with it when that feature comes along (hint: never). Even if that feature comes along, the changes that you actually predicted and designed for it correctly is approximately zero, so you’re still going to have to make design changes anyway. However, if you kept it lean and simple, it’s simply a matter of adding the new code you need to a small lightweight base. But if you tried to build something big and fancy and configurable, you’ll now have to spend twice as much time disassembling all the unnecessarily complicated junk you build around it to support the thing that you were unsuccessfully trying to support in the first place. The easiest code to refactor is the code that doesn’t yet exist.
So yeah, YAGNI.