One of the great things about pair programming is getting to see how somebody else approaches a problem. One can learn a lot by observing another programmer's thought process. For example, the first day of working with Mat, I learned that some programmers use something called a 'debugger' to troubleshoot problems in their code.
Evidently, not everybody is quite as hard on their delete key as I am.
At one point during the train ride, Daniel looked at me, and said, "Wow — we're deleting more code than we're keeping!" To which I responded, "Yeah, of course we are. Don't you always?"
This illustrates one of my strategies for writing great code. Trying to get it right the first time is futile. So, I stopped trying.
I'd much rather get it out of my brain, and in to my editor, no matter how crude; see my tests go green, and refactor it immediately. Mostly, that means deleting the entire implementation, and starting from scratch. Let me tell you why.
Trying to perfect an implementation in one's mind is a form of speculation. It is extremely difficult to judge the readability of something you cannot read, or the performance of something you cannot run. Such a judgement may be possible in some cases, but it will never be a replacement for actually seeing, reading, or running the code.
A good portion of the time, the solution that seems crude in my mind turns out to be perfectly adequate once I see it in my editor. Sometimes, it's even fairly elegant. When my first try sucks, refactoring leads me to the right solution far more quickly than speculating ever could. This is because identifying problems in my initial implementation is so much easier when I can actually see, and manipulate it.
A great programmer certainly has a better sense for which solutions are going to be more readable. That programmer will usually get closer on the first try. But, in my experience, the very best programmers are the fastest at trying out several different solutions and choosing between them, or quickly iterating on one, until it's perfect.
The elegant implementation you see when you read a great programmer's code is often the third or fourth try. The great programmer is often more effective because they can implement several solutions in the same amount of time it takes the average programmer to implement one.
All the while, they are improving their base of experience by having real interactions with many real solutions to the same problem. That allows them to arrive at a better solution to the problem next time. That, I believe, is one of the reasons programmer productivity is exponential.
Since I started working with highly productive languages like Python and Ruby, I have come to believe that code should be disposable. The easier it is to refactor, or even rewrite (a form of refactoring), the better. That's one of the reasons I am a proponent of dense languages. Anything that can't be easily refactored due to its size is a major liability.
It seems pretty clear to me that programming is best learned through doing. That's why so many philosophy graduates, and jazz musicians are better programmers than unmotivated kids with software engineering degrees. Writing code is a lot more valuable than thinking about code.
The faster you can write, and evaluate code, the faster you can get better, which accelerates your ability to write and evaluate code. That's exponential growth.