Some of this is just the evolution of software, basically Gall's Law. The initial code is small, simple, and functional. But the complexities of business needs end up making the code more complex over time too.
I have found that test-driven development can guide the code towards better maintenance, and structure. It forces the developer to break things down into small, easily testable sections of code, that are also easier to understand as a side-benefit.