I saw a TV commercial recently that reminded me of what it's like for developers learning advanced object-oriented development techniques. The commercial showed a golfer standing in front of a golf ball, club in hand. Before he swings, he thinks about all of the tips his friends have given him about golf:
1. Pretend you're sitting sideways on a horse
2. Pretend your arms are limp spaghetti noodles
3. Pretend you're standing in two feet of water
4. Pretend you're wearing a tractor tire around your waist
He's thinking about so many things that instead of hitting the ball, he releases the club in his backswing and nails his caddy - after all, he forgot to grip the club. I couldn't find the clip on YouTube, but I found something very similar to give you an idea:
http://www.youtube.com/watch?v=svDFoBHzM1AI had a similar experience when learning to water-ski on one ski. I didn't get on top of the water until I realized the only thing I needed to remember was to push hard with my back foot. That was it - when the boat engine roared, I pushed with my back foot. Viola!
When I began working for
Jeffrey Palermo, I struggled to find a common theme to guide my development decision making. The list of rules to follow when writing maintainable, object-oriented software is immense:
1. Don't repeat yourself (DRY)
2. Liskov Substitution Principle (LSP)
3. Single Responsibility Principle (SRP)
4. Scoping
5. Design patterns
6. Loose coupling
7. Strong cohesion
8. Small classes
9. Limited dependence on infrastructure
10. And on, and on
For developers dedicated to excellence (but unfamiliar with advanced OOP), the list above can basically prevent them from ever writing any code. In my case, what will Jeffrey think of the code I commit? How do I
*know* I'm not writing the legacy code that will torture my team in the future? Is there a simple rule I could follow (like pushing with my back leg) that I can focus on to be successful?
Fortunately, I found just such a rule to address all of the concerns listed above. It is... drum roll... 100% Unit Test Coverage. Specifically:
1. Unit test every meaningful input combination
2. In
Onion Architecture terms, test the behavior of only one domain object or service at a time
3. Test all delegation from one class to another
4. Keep your tests small and readable
Viola!
By testing all delegation logic, you're almost forced to define an interface for each of your services so you can mock them when unit testing the classes that depend on them.
By testing every meaning input combination, you're forced to break up code into small, simple classes. Methods with high cyclomatic complexity will create too many input combintations. A class with 16 input combinations can often be broken up into two classes with four input combinations each. You just cut the number of test cases in half (4 + 4 = 8, 4 * 4 = 16).
By unit testing your classes, you prevent any dependencies on infrastructure. For example, you can't include code like
DateTime.Now directly in your classes. Otherwise, you'll need to reset your system clock to produce a predictable unit test result - yuck.
You'll quickly find yourself learning design patterns because design patterns allow you to break large logical structures into small, individually testable classes.
The mistakes you do make will be small and easy to correct. After all, your unit test suite will catch any refactoring mistakes you make.
The next time you're feeling overwhelmed keeping up with the Palermos, Millers, and the like, challenge yourself to achieve 100% Unit Test Coverage. If you're like me, you'll find it rarely leads you astray.