– where by ‘macro-reuse’ I mean reusing objects or libraries (which is good), and by ‘micro-reuse’ I mean seeing a class that has some overlap with your needs and saying ‘hey, let’s derive from that and just override these two interfaces’, or seeing a function that looks similar to the one you were about to code and saying ‘hey, let’s call that and add an internal branch for my new case’ (which kills kittens).
Code reuse seems to hold the promise for software heaven. Forcing it into a paragraph, the mantra says: identify common functionality, code it at a single location, then use it all over the place. When a fix is needed, just apply it at this single location – and have your entire app enjoy it at no additional cost.
The world of software design seems to consider this an unquestionable truism[*]. Alas, it does not hold in (at least my subset of) reality.
I have virtually never seen code whose lifecycle followed this sterile path: code and maintain at a single place , enjoy everywhere. When some functionality is indeed made to service multiple clients, the real life probable scenario is that no one will ever, ever, dare to maintain it.
The cases where a product bug is identified that is truly internal to some well defined common functionality, regardless of its users, are rare at best. Much more often, the process is –
- The needs of a specific code-client change, or a behavioral bug is detected in a specific context.
- The poor soul who is tasked with the fix cannot possibly know of all other code clients and thus dares not touch the common functionality.
- The specific bug or requirements change are addressed with a patch at the client code.
- The common functionality quickly turns into a code fossil, never to be modified again.
Thus blind code reuse, intended to increase maintainability, ends up creating tight coupling – thereby vastly reducing maintainability.
I see this all the time, both in twisted flow control and overload of flow-control flags, and in ridiculous class hierarchies. I recently had to deal with ~6 layers of ~30 classes (!) aimed solely at reusing existing code. This was occasionally as few as 10 lines to reuse from a parent, but apparently someone was very literal (and very thorough) at interpreting the code-reuse mantra. The result is, of course, a huge spaghetti bowl that no one dares touch – one would always prefer to override over risking regression. Such code is also unmaintainable at another level: the functionality of a single concrete object is spread across as many as 6 classes! Just following the action paths or the content of various containers (modified throughout the hierarchy layers) was a formidable task.
______________________
[*] Only after writing this up did I find a software heavyweight that holds similar opinions. Others have also articulately phrased similar objections specifically to reuse via inheritance
Couldn’t disagree more. I think every time you duplicate code, god kills ten kittens.
Much of this article relates to misuse of inheritance, and I agree with you there, but no one said reusing code (even micro-reusing it) has to be achieved this way.
As to your statement, “hey, let’s call that and add an internal branch for my new case’”, yeah.. that’s exactly what you should do. Imagine 100 lines copy/pasted just because you were too scared to put in an internal branch. Now you have 200 lines of code to maintain, and when they start to diverge, that’s when the real fun begins.
I think it’s subjective to the case in front of you.
You don’t need to write a new walker in order to iterate over a list if you got a good one, but don’t rape a “file explorer” class just in order to turn into a list walker.