This metaphor works well to communicate the need for refactorings if at least one person realized the need for it.
But in various cases nobody in the project realizes that there is a problem until the team face a huge debt which seems impossible to pay back.
I see two possible reasons:
Being blind: There is a different level of cleanliness people consider clean enough. In interviews I have repeatedly talked to people considering a 20 line method a good thing and don’t have problem with nested control structures 5 levels deep. Those developers wouldn’t notice anything smelly when looking at code which I would consider CMD (Code of Mass Destruction). This problem is most of the time fairly easy to fix by teaching and coaching. But there is a more insidious possible reason:
Being a frog in boiling water: There is a theory that a frog which sits in cold water doesn’t jump out of the water if you heat it really slow until it dies. Wikipedia isn’t decisive if this is actually true, but I definitely see this effect in software development. It looks like this: At some point you find something in your code that looks like it has an easy abstraction. So you build a little class encapsulating that behavior and use that class instead. It works great, so that little class gets used a lot. Sometime later the class gets a new feature to handle a variation of the original case. And it goes on like this, until one day what was a simple abstraction has turned in a complex library, possibly even a framework. And now that framework is a problem on its own. Its getting hard to understand how to use it.
And you realize you are a poor little frog sitting in boiling water with no idea how he got there. Hint: It’s a good idea to jump even when it is a little late.
Why does this happen? Just as the frog has a problem sensing the small change in temperature and realizing he is getting into trouble the developer doesn’t see he is hurting the quality of the code base until it is to late.
Again: Why? Let’s make the example a little more specific. You have blocks of 10 lines of simple repetitive code in N places in your code base. You replace it with a call to a simple class of 40 lines of code. So you save (9*N – 40) lines. On the call site your code gets significantly simpler, of course the class is a little more complex but that’s ok. Now while implementing a new feature you are about to create another of those 10 line blocks. Obviously you want to use the helper class. But it’s not fit for the job. You need to add a feature to it. That’s ok. You also have to add something to the public API to turn that feature on or of. Maybe it’s a new constructor, a new method or an additional parameter. That’s not ok. Until you changed the API of your class the changes where local to the helper class and its usage at the new site. But when you changed the API, you added complexity to all the call sites of your class. Whenever you now call that helper you have to think a little more about the correct API to use. This unfortunately isn’t easy to see. So it can easily happen that your API turns slowly so complex that using it is more painful then just writing the 10 lines it replaces down.