Duplication is far cheaper than the wrong abstraction — Sandi Metz
Over the last few years, I’ve found myself leaning on what I think of as a useful new code smell I’ve stumbled across: “Polluted by the Precipitant.” Code with this smell has names, APIs, or organization (whether structure or even just location in the codebase) tied to the place that code is invoked, even when the implementation itself is more general or flexible than that API implies. It’s been polluted by whatever need caused it to be introduced in the first place — its precipitant — instead of having been designed as a cohesive independent unit.
I realize this is perhaps a little more vague than the code smells identified by Martin Fowler in Refactoring book. But, this has worked well for me as a "sniffable" guide to a potential issue. It’s usually a sign of code that was grown over time and "refactored" by simply extracting functions/methods out of a growing implementation and naming those new units with summaries of their purpose within the existing structure, but not always. I think this can also be fairly applied to code in modules designed for a general purpose but that doesn't handle the breadth of cases you'd expect in a general-purpose utility. Here are some examples:
- A function is named for its purpose in an overall workflow but its implementation is effectively an implementation of a general operation applied to other specific concepts/types/modules.
- A function is named for a general need, but only operates on a subset of the input type. Or, it includes logic only relevant to some narrow use cases, which blocks it from being truly general.
- A useful, reusable unit of code about another type/concept lives as a "helper" within the implementation of an unrelated module that happens to need that code in its implementation. This may lead to duplication or for this unit to be imported into other modules that shouldn't logically be dependent on the one in which it lives.
- A representation of a certain kind of data or state reuses an externally-imposed structure or format, in lieu of a more appropriate direct representation of that state. For example, externally-sourced JSON is propagated throughout the system instead of making illegal states unrepresentable within a domain type and providing a mapping to/from the external representation.
Why Code Smells
What unifies these cases (at least in my mind) is the ontological content in code. Names, module ownership, concept association, etc. are spoiled by mixing in aspects of another concern. This is usually due to a failure to design a unit of code independent of the specific motivation for its introduction. Reading someone else’s code involves trying to reconstruct a rationale for how and why that code was originally structured. You often run into challenges when trying to reconcile code with an explanatory framework. When you find yourself thinking, “Oh, I see, this thing is just there to be called by this other thing,” you’ve got a sign that the structure is not well thought out. In that case, it may be worth attempting to formulate an alternative basis of abstraction.
For me, this smell is closely related to the Sandi Metz quote above. It often arises when developers attempt to be DRY too aggressively without being attentive to the meaning, coupling, and cohesion of their code. By failing to consider their immediate task in the abstract, they made things worse. They may have been better off with the duplication they may have been trying to eliminate.
A Signal That Something Isn’t Right
Upon analysis, this smell may be tied back to more specific flaws, especially a few of the classic code smells identified by Fowler et al. It’s sort of the “smelly garbage” of code smells. You don’t have to dig into the heap to figure out the exact proportion of old takeout versus rotten banana you’re picking up to identify that something stinks. Ultimately it’s a signal something isn’t right. It’s up to you whether or not you think it’s worth it to dive into the bin and sort out the root cause.