In my Third Law of TDD Liang asked me in a comment:
“Could you let me know if you have a basic application architecture design in mind before you write the first line of code? Or you just let the test/code lead you to a design. For example, if you find a common method in related classes, you create a Interface, just as you mentioned in validation. If that is correct, I think TDD is pretty hard for a beginner, since the developers should have very good knowledge in OO, design patterns, refactoring before beginning implementing TDD. otherwise how she/he know how and when to refactoring?…”
To the first question, do I have a basic application architecture in mind before I write the first line of code? That’s a long conversation, but the short answer is that I generally have a basic idea for the application/system/service as a whole. The longer answer that I’ve learned over the last couple years is to not commit to a design or a structure upfront. As far as the unit tests leading the design and knowing when to refactor, you should constantly be queuing up design ideas and evolving your thinking about the design but the unit tests drive the actual direction. You should only introduce an abstraction, either upfront or by a refactoring, when you know enough to say that the abstraction in question is valuable. Creating an abstraction too early often leads to painful, sclerotic code. Waiting too long is a wasted opportunity or a harder refactoring. The key is to make design decisions of all kinds at the Last Responsible Moment. Determining or recognizing that last moment is something that comes with experience.
To the second question, how much should you know before you start using TDD? I’d restate that question to a more generalized “how much do you need to know to start programming?” While TDD does have some of its own lore (xUnit, mocks, Dependency Injection, IoC, etc.), for the most part the knowledge and skills that enable effective TDD are the same skills you would want to do more traditional upfront design work. You’ve got to look for a coding and design style that fits with your own thinking style to balance upfront thinking and reflective thinking.
I would, and do, recommend TDD for new developers. You have to start at some point and it certainly helps to get some coding experience under your belt before you tackle subjects like Design Patterns and Code Smells. I think the value of TDD for a new developer is the concentration on verifying code in small chunks for the rapid feedback. Another advantage of using TDD as a beginner is the emphasis on simplicity as you layer on functionality little by little and introduce abstractions slowly.
You simply can’t ever code blindly without thinking for very long, so you might as well get used to working reflectively. Many of the worst application architectures I’ve seen (and one that I did) were a result of determining abstractions upfront that didn’t really work, and then compounding the problem by bludgeoning blindly ahead. Regardless of your style, you absolutely have to continuously reflect on your design as you work, both to spot design flaws and opportunities for better designs.
To be honest, I think TDD is more difficult for someone with a lot of experience in upfront design techniques. I had to “unlearn” quite a bit before I became effective with TDD. The developers I’ve worked with that started in Agile shops seem to have an easier time within an Agile process.
As far as what you need to know to be effective with TDD and continuous design in general, the more you know the better you’ll be. I’d start with basic OOP best practices with something like the Craig Larman book. Learn about code smells and Anti-Patterns to recognize when your code is moving in a bad direction. Studying design patterns will help you inside of any design technique or development process.
Here’s two absolute truths about an individual’s coding:
- Everybody works differently
- Results will improve with more experience and more knowledge