In my past five years of experience developing software, I've come to the conclusion that a primary component of writing great software is managing risk effectively.
Suppose I want to bake a cake to take to a dinner party tomorrow night. I could pick a recipe based on its deliciousness, but I review the ingredient list and instructions and equipment beforehand so that I won't have to stop in the middle and rush out and buy something I forgot, potentially ruining the cake. I could base it on an ingredient I love, but Dave can't eat nuts. I could choose a recipe beyond my skill level, but I might fail and have to choose between arriving empty handed or with a prepackaged dessert from the supermarket. Yick.
Everyone makes similar judgments when writing software, even if we do so unconsciously. Consider the risk of failure. How do you know your program works?
Me, I test software that matters. If it's a short program intended to explore an idea or a program I expect never to grow more than 30 lines, I might add debugging output or run a few manual tests. Otherwise, I blatantly use testing to drive development, in design, implementation, and maintenance.
Testing (and TDD) not only allow me to build a suite of automated tests corresponding to expected behavior such that I can run those tests and verify that any change did or did not change expected behavior, the discipline of using testing to drive my development and design in the small changes the way I think. Instead of thinking merely "This function has to return a particular type of value when it succeeds," I think "What else can it return?" Because I write a test first, I think about how other people might call the function. I think about how other people might abuse the function, on purpose or by accident.
Where possible, I make the API difficult to misuse.
When that's not possible, I evaluate the risk of misuse -- its likelihood and consequences -- and consider the cost of preventing that misuse.
Perhaps TDD isn't the only way to do this. It's the most effective way I've found, but it may not be the only way. Even so, I believe that identifying and understanding the risk of failure will help novice programmers improve their skills.
The primacy of using an API over writing an API changes my state of mind. It's easier for me to consider these risks when I start from the question "How will people use this API?" instead of "How do I implement this code?"