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.

The example of "print statements" in the middle of I/O logic is pretty interesting. But in that specific case you would want your logger to be a global variable or something similar, because every class has the right to log. Doing straightforward tasks in a roundabout way to fit the architecture of the application is warranted in some cases. In others, you need to refactor ;)
–
YarOct 19 '10 at 21:38

@Yar: Yea, the print statements mostly occur when I want to learn what some algorithm is really doing under the hood and didn't anticipate wanting to do that and don't have an easy way to plumb this information up to higher levels. It can also occur when the amount of data I need to dump is huge and wouldn't easily fit in memory.
–
dsimchaOct 19 '10 at 21:44

3 Answers
3

Consider early prototype code - if you are doing something very different than what you have done in the past the first go of it is likely to teach you that you should have done it differently early on - work out your ideas then start fresh

Minimize early decoupling - to me it is far easier to refactor something out of a coupled scenario once I have good reason than to juggle the bits across an arbitrary early decoupling border.

Consider having fewer tiers or allowing functions to skip tiers. In some projects the latter can approach a necessity at times.

Using a dependency injection framework can help with adding new dependencies without using global variables or passing values through a bunch of method calls. An AOP framework may help implement new cross-cutting concerns.

For example, logging can be achieved through either AOP and/or a logging object that can be injected at any layer.

An example of a framework that provides both of these concepts is Spring.

EDIT: I know the comment below is against the idea of more dependencies, but I did find this for Python: Spring Python

This is somewhat useful, but I program primarily in D and Python, neither of which have a very framework-y culture or many frameworks available. Furthermore, I prefer avoiding adding dependencies, especially ones that require non-trivial configuration.
–
dsimchaOct 19 '10 at 23:51

I think that, like a lot of other things in programming, you need balance.

Find a solution that is both good enough and doesn't require too much work (as it is a prototype anyway and you will probably change lots of other things). Next when you got the things mostly finished, you can try to see where it can be made truly good.

Don't skimp too much on the good enough part though, if your code is too bad (usually I think that adding a variable is not a problem, the big problem is lack of structure) you won't see the good change that needs to be done later.

I think as long as you keep a somewhat clear structure in your design, the rest is not that much important as it will be easily refactored out later.

Again, in the end, you are the programmer and you are the one in charge of taking the right decision regarding code clarity, time to implementation, etc. Don't try to fit a one-size-fit-all rule if you think it won't work.