How I do it
I like to think I'm a good programmer, but I expect it's the same as driving: More than half of all drivers think they are better than average, which is mathematically impossible. So maybe I'm not really all that good. Anyway ..... This is my Modus Operandi, for what it's worth.
Obviously, the first step is to break the problem down into a series of simpler ones. So I begin by writing comments of what I want each of my main sections to be. That's right: the first thing I save is just a bunch of comments! Then I fill in the actual code to do what the comments said, right between the comments, moving bits that deserve to be made into functions out of the way.
This way, it's pretty much self-documenting. I never have to go back and try to understand how a particular chunk of code works; because I already wrote that down in the first place, as soon as I figured out how I wanted it to happen. It has also given me ideas for Meaningful Variable and Function Names (which, in and of themselves, limit the need for additional comments). It might well suggest creating a new object type. The basic point is as soon as the idea is fresh in my mind, it gets turned into natural language comments, and then into the idiom of the programming language. It also means I'm coding not straight from abstract thoughts, but from words.
In Perl, which is my language of choice, methods may be "asking" -- returning a value relating to the object -- or "telling" -- setting a property, or injecting something into the object, and not naturally having anything obvious to return. I make my "telling" methods return the object itself; then I can daisy-chain them, avoiding repetition:
$cake ->ingredient("flour", 200, "g") ->ingredient("sugar", 200, "g") ->ingredient("butter", 200, "g") ->ingredient("bicarbonate of soda", 20, "ml") ->ingredient("eggs", 4) -> ingredient("water", 50, "ml") ->bake(5, 15 * 60);
Notice how that neat "15 * 60" (which, since it is being used as a literal, will be evaluated once, at compile-time) makes it obvious to the reader that the second argument to ->bake() is a time in seconds, in a way that "900" might not, and does it without an explicit comment.
All this, of course, is Laziness -- the fist great virtue of a programmer. Although it seems an effort to write down what you are thinking as you are thinking it, it is less effort than thinking of it all again later just so you can write it down.