Great post. I'd say preventive and perfective maintenance have overlapping goals. I'd bucket refactoring under both. You refactor to make future changes easier. That's adaptive but also perfective since it improves the software in some way. And it is preventive because it makes it easier to perform future changes without introducing new bugs.
How you categorize your changes is less important than being conscious of where you are spending your time. As you say, when you spend a lot of time firefighting you need to step back and figure out if there's a better course of action.
It's easy to get stuck doing a lot of maintenance without seeing the bigger picture. The fate of most succesfull software is that eventually the accumulated engineering effort is mostly spent on maintenance. Some studies I read in the early 2000s suggested that most big IT projects see upwards of 60% of their budgets eaten up by maintenance. This is a sobering thought if you are doing a greenfield project (which is fun).
My view is that maintenance problems pile up and become an obstacle to necessary adaptive maintenance. When certain changes become hard or impossible, this can result in software being decommissioned. That too is a common problem with many software projects. Many projects do not survive their first birthday.
This also means that if you know you are working on something that only has short term relevance, maybe over-engineering it in an attempt to perfect it is not the right way. Quick and dirty is not long term sustainable but it can get your results quickly.
We’re a place where coders share, stay up-to-date and grow their careers.
We strive for transparency and don't collect excess data.