Programming is a complex activity. It must be the most complex activity ever undertaken by humans, because it's the only one I know of where its practitioners habitually make so many mistakes without (usually) being accused of incompetence.
The bad news is, as programmers, we're doomed. The computer is quite capable of exceeding any threshold of complexity that we might try to put up--certainly, any threshold that corresponds to what ordinary, or even extraordinary, humans might be able to deal with. Our machines are getting more and more powerful, but our brains are not.
On the other hand, just because there is a cliff, doesn't mean we have to go jump over it right now. You may not be able to avoid the complexity trap in the end, but there are ways to postpone it, just a little.
If it isn't clear already: there are no panaceas or magic bullets. There are a number of techniques that have been found to be effective, in that programs written with them turned out to be just a bit more manageable than those without. Most of these techniques are fairly simple, which shouldn't come as a surprise. After all, the programming techniques you use should be helping you tame the complexity of the problem, not adding to it.
What do I mean by "taming the complexity"? Here are a couple of points that any experienced programmer would identify with:
Here are some specific techniques for dealing with these issues:
I like to think of this as adding creative redundancy, and I think that one important way in which programming languages can advance in future is to come up with even more applications of this idea. Imagine being able to declare a variable such that it can only be used in an associated control construct that guarantees at compile time that the variable will always be initialized before it is referenced, without the need for expensive run-time checks...
I won't say that code reuse is easy. There is no magic foresight you can exercise to ensure that a piece of code is reusable from the start--though there are some habits you can develop with practice, that make it easier. The only thing that makes a piece of code reusable is the fact that you have succeeded in reusing it!
Usually, the moment when I realize that I can reuse a sequence of code is after I have written a second, very similar sequence in another place. Then I go back and look at how I can rewrite the two into a single, more general piece that can be used in both places.
In short, you mustn't be afraid to rewrite. Trying to reuse a piece of code for the second time is probably the hardest part: once you've got some code into a shape that you have successfully been able to use twice, the third time will be easier, and the fourth time easier still.
Here are some comments on Frederick Brooks' The Mythical Man-Month.