The Dangerous Allure of Code Reuse
Tuesday 20 May 2014 20:42
Back when I was learning to code, one of the ideas that kept cropping up was the idea of code reuse. If one of the main benefits of functions is the ability to reuse a bit of code, doesn't it follow that we should have future code reuse in mind when writing functions? My experience says: nope. It's this thinking that leads me to create functions with six Boolean arguments, or to create functions with complex, deeply nested switching logic. By following principles such as "Don't repeat yourself" (DRY) and "You ain't gonna need it" (YAGNI), and creating short functions that operate at a single level of abstraction, I find that useful functions pop out without consciously thinking about code reuse.
If I try to write a function with code reuse in mind, it becomes tempting to write features that might be useful. In other words, it encourages the opposite of YAGNI (you ain't gonna need it). Far better to have a tightly focused function that does exactly what I need, no more nor less. Sometimes that function will already exist exactly as we need it. Great! Use it.
But what if there's an existing function that's similar, but would require some changes for our required functionality? Should I create a new function or should I modify the existing function? It's tempting to tweak the existing function in the name of code reuse, but I'd suggest that we should err on the side of creating new functions.
One of my guiding principles is also one of the main benefits of functions: abstraction. Imagine that there's already a function that does exactly what you need. What would it be called? What concept does it represent? Now take a look at the existing function. Are they the same concept? If the concepts are related but distinct, that suggests they belong in different functions. If it happens that they have similar implementations, then you'll find the same consideration happening recursively: is this sub-concept in my new function actually the same sub-concept in the existing function? And so on. Although you might have created a new function, it might not actually lead to much new code if it shares many of the same underlying concepts.
I try to write short functions that operate at a single level of abstraction. The result is that I write plenty of short functions that only get used in one place, at least initially. Such functions might end up getting used in other places, but they weren't really designed with reuse in mind: instead, the foremost consideration was whether that function represented a coherent abstraction of a concept.
This isn't to say that I completely ignore code reuse when writing functions. Sometimes, when weighing up different design choices that are similarly elegant for my current use-case, I'll pick a design based on which one is likely to be best suited to usages that I anticipate in the future. However, I won't add anything specifically for code reuse: I've still chosen a design which does only what I need, but is also amenable to anticipated future change. If that future change never happens, then the function still makes sense and doesn't have extra unused functionality.
To summarise: I try to avoid crowbarring in functionality to existing functions just because it's similar. Separate concepts should go in separate functions, even if they share underlying concepts. Better to have two coherent, small, simple concepts than a larger but ill-defined concept. Code reuse is a good thing, but it's not something that I actively design for.
Footnote: for brevity, I've only talked about functions in this post, but it applies equally to classes and other such constructs for the same reasons.
Topics: Software design