At a meta level, I often find that when I grow a codebase organically, parts of the system that I eventually find need to know about each other (at least through some interface) have been mercilessly decoupled. This often occurs in research prototype code, where one tends to frequently think of tweaks and small improvements that weren't planned for in the original codebase.
Usually, some setting or piece of data needs to be plumbed through about 15 zillion layers of function calls in ways that I never anticipated when I designed the code. This leads to a very ugly dilemma:
Use an ugly hack. This can be yet another global variable, bolting the data onto a class that it clearly doesn't belong in, putting a bunch of print statements in the middle of code that previously had nothing to do with I/O logic, doing a seemingly straightforward task in a very roundabout way to make it fit in my original codebase, etc.
Shotgun surgery refactoring. This almost never gets done in practice because the code will likely have to be rewritten anyhow if it is to become production quality, was hard to get working the first time, is hard to test, or I just plain have higher priorities than writing clean, maintainable code when either I have a deadline or I know it's just a prototype for now. Furthermore, the next tweak may break whatever nice abstractions I come up with.
Are there any good meta-tips or design principles that will help me avoid situations like this in the first place? I am NOT looking for answers that simply recommend refactoring after the fact, as a nightmarish shotgun surgery refactoring session is exactly the kind of thing I'm trying to avoid.