A mindset for long-living software systems
Not far away from the Rhine river in Cologne, tourists find one of the most famous sights in Germany: the Cologne Cathedral. It was built in a period of 632 (!) years and completed in 1880. And from then on, the renovation work started. As of today, the cathedral has not seen a day without scaffolding for over 100 years. The Dombauhütte Köln, the institution responsible for the renovation, even has its own article in the German Wikipedia.
Let's change the scene to software development: Some might argue that old mainframe programs exist, which, being live since the completion of the Cologne Cathedral, are still being enhanced and maintained up to today. But the same also holds for the majority of newer software systems: They are never fully completed. If you don't constantly take care of a software system, it ages. Libraries and systems have to be updated, existing behavior changed, bugs fixed, new features added.
The appropriate mindset for continuous improvement
To enable continuous improvement here, developers need to operate with the appropriate mindset.
This starts with being aware that you are working on a system that will never be perfect, and will never be completed. Not only in terms of new features, also existing areas will have to be rewritten over and over again to facilitate maintainability.
This also means that the statement "you write code neither for the computer, nor for you, but for whoever reads it later" cannot be underestimated. So, when it comes to the readability of the code and the structure of the module you are working on, you should always put yourself in the position of a newcomer who, in a few years, will initially come across this place in the source code for the first time: Do you understand the naming of the variables? Does the class actually do what the concept says it should do (and only that)? Does this new control flow match the basic architecture, or have e.g. cycles been created?
Avoiding the Broken Window Effect
Especially during the further development of grown systems, you inevitably come across places that need to be refactored, but can't be changed completely right now. Developers often make life easy for themselves here by introducing new functionality directly without any further quality assurance measures, since in this area of the system "it doesn't matter anymore anyway".
However, in most cases this is not a good idea. If the code is not to be removed very soon anyway (usually not the case), every change that is being made without appropriate quality assurance such as test coverage, review or the like, only makes further changes more difficult. Hence, it is important to ensure that changes do not make the system even less maintainable.
Otherwise, we can observe an effect here similar to the broken windows theory in urban areas: Dilapidated urban areas preferentially attract crime and thus even more decay. In software areas with bad quality, developers tend to take less care in further development as well.
The usual warning about big bang rebuilds
The fact that major rebuilds of a system should no longer be done with a big bang, but step by step, is widely accepted today. However, when tedious improvements are made to the architecture and system, chances are that such refactorings will not be continued consistently halfway through. In this case, it is important to keep such issues explicitly on the radar during planning and to ensure that the refactorings are eventually completed.
Often it is not a question of prioritization at all, but sometimes simply a question of attention: if long-lasting refactorings stop being in the spotlight, they will not be pushed forward anymore. With such refactorings, you have to make sure that they stay on your radar for a longer period of time and that you can continue to work on them. Taking an hour out of your day-to-day business to look for code passages that need further work for refactoring is already enough to make progress on the refactoring work. As with many software development topics, the key to success also lies in good (self-)organization.
On the other hand, some refactorings should not be started hasty and carelessly: Particularly when changes can affect large parts of a system, it is best to carry out the refactorings in coordination with the entire team: There should be a common understanding of how the changes can be made and what the target system should look like. In most cases, better solution alternatives can be determined through coordination. Such coordination also makes it possible to tackle a rebuild together as a team rather than as a lone warrior.
Last but not least, a half-heartedly started modification bears the risk that instead of having a legacy approach in your system needing improvements, you may end up with two inferior approaches. Sometimes it can make sense to stay with the old solution for the time being, even for new code areas, in order to increase the consistency in the system (which also has a positive effect on maintainability). A developer in one of our projects used to describe this with the words "if it's madness, it should at least be methodical".
Commitment, humility, empathy
We developers like to be convinced of our code, our concepts and our way of writing code. There's nothing wrong with healthy self-confidence, but sometimes a little humility would do us good. For one thing, the code we write today is almost certainly the new legacy code of tomorrow. And so many systems that we now derogatorily refer to as legacy have been developed with the best of our knowledge and conscience back then.
And even today, it is by no means certain what kind of programming style will stand the test of time and will actually be considered maintainable in the future, even according to objective criteria. The same applies to refactorings on a larger scale. A first important step is to put yourself in the shoes of the other developers: Instead of "I'll clean up your mess", the attitude should be "Let's think together how we can improve the system here". Instead of "this is crap", it should be "there are good reasons why it is the way it is". And instead of "I know how the whole thing should ultimately look", I personally prefer to work with people who embody the attitude that "the team is more than the sum of its parts".
Many developers like to perpetuate themselves with the code they write. Delivering good work should always be our aspiration, but the goal must be the overall result: a software system that can be continuously improved despite its complexity. Renovations are part of this. Just like with the Cologne Cathedral.