– 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.