Over the past several weeks, I've had the obligatory parental experience of teaching a teenager to drive. This can be a nerve-wracking process as the new driver learns the mechanics of controlling the car: steering, using the pedals, shifting gears, checking rear-view mirrors, etc. But all these are relatively simple tasks that young drivers mostly master without serious problems.
The real harrowing experiences start only after new drivers have been driving for a while, when they're out on the road with other drivers. And that's where "learning to drive" truly comes into play, because it's not control of the vehicle that makes a good driver although that's certainly a prerequisite. Instead, it's the ability to anticipate the unexpected that makes a good driver--and unfortunately, that's a skill you can't teach.
You can talk about potential problems. You can describe them, and have the learner tell you what they would do in such situations. You can even practice avoidance maneuvers. But each new driver must personally experience many of the common driving hazards (and survive them) to be able to anticipate similar situations and take steps to avoid problems.
Sadly, the same rules apply to becoming a good programmer. Consider an application that stores some data in a file, retrieving it each time a user launches the application. Beginning developers, having learned the syntax to open and write data to files, simply write the few lines of code that read and store the data. If they've been programming a while, they might even write a test to make sure the code is reading and writing the data correctly. And because the code works, beginners think they're done; they've accomplished the task, met the specifications, and tested their work.
An expert programmer, faced with the same situation, knows it's not that simple. Sure, it's easy to write code to read or store some data in a file--when everything goes right. But it's not so easy to code the application to handle all the things that can go wrong, even with this simple operation. The file might not exist. The disk might be full. The file may be corrupted. The user may not have permission to access the file. The file might be in use. If the file's not local, it might not be reachable.
Granted, not all these problems are likely to happen at any given time, but developers who have delivered applications to large numbers of people know that, given enough time, all of them will happen, sooner or later.
An expert can tell a non-expert to check for such conditions, and with those specifications, the non-expert can probably code them, but only the expert anticipates them. Just like driving, what makes a good programmer is not only the ability to solve the problems that occur, but the ability to foresee (and avoid) problems that haven't occurred yet. Unfortunately, experts learn how to do that by making mistakes. It's a sad commentary on the human condition. Each generation acquires expertise primarily by repeating the mistakes of the past generations. To paraphrase Neils Bohr, "An expert is someone who has made all the possible mistakes in a very narrow field."
But when you're riding in a car with a novice driver, you'll probably appreciate P. J. Plauger's version more, "My definition of an expert in any field is a person who knows enough about what's really going on to be scared."