TDD alone is not enough
Per yesterday’s post, I spent my day writing a text UI in Ruby to access my Java code. Instead of letting my existing Java API drive the design of my Ruby code, I mocked out the Java side completely and simply wrote the code to express the interface I wanted. I only concerned myself with the behavior that my UI needed such as sending player names for internal record keeping, getting the board for display purposes, sending the move to make, checking for a win or tie, and getting the results to display. That’s all any client of my Java code would ever need to play a game. Each one of those items is usually no more than a single method, and as a result my programmatic interface is straight forward and clean.
Now the big task lays ahead. It’s time to refactor my Java code to work well with the interface that the client has specified. This type of outside-in development is not something that I just discovered, but for some reason in my Rails app I was having problems expressing this correctly. I think it had a lot to do with my unfamiliarity with Rails and tackling two problems at once is usually not a good idea.
It also helped to reinforce that doing TDD alone is not enough. TDD can help you create a cleaner design, but it is no guarantee. You still need design-sense to know how to structure your application beyond the basic decoupling benefits of TDD.
One of the major problems my code had was that any UI that used it was not in control of the game flow. I got it in my head that this is a game and it needs a game loop. But why does Tic-Tac-Toe need a game loop? It’s just move until the game is over. I believe when Jim first told me to add Swing as a UI option, he had identified this dependency and was trying to get me to break it. What did I do? I created some poorly implemented method of having my game poll the UI in it’s loop. What I should have done is what I’m doing now. Let the UI dictate the game flow by sending commands to the game. My game should only care about keeping itself in a valid state. As long as it performs that task, the client is free to implement the flow however it wants. In my text UI, it is using a loop. In my Swing code, it should use events. In Rails, it’s going to receive a request over the web and then manipulate it’s model. My game code shouldn’t care about these details. All it needs to know is that when I tell it to place a marker in the top left corner, it should make sure it’s valid and then manipulate it’s state.
This day has been a pretty big eye opener for me. Will I make this mistake again? I surely will. But next time I hope that as soon as I start feeling this resistance to change, I can identify it and rip it out.
I came to the same conclusion with one of my programs too! I tested it in every possible spot, but that alone couldn’t intelligently design my project. Every once in awhile I had to take a step back and really think about what everything was and if it was really doing what it was supposed to be doing. It is, at least, my biggest challenge when writing any decently sized program.
LupusDei
July 8, 2009 at 5:26 PM
If there is one thing I have learned during this apprenticeship, it is that design is HARD. It has really given me a respect for the skill and effort put into good design. Hope to see you around the office again soon!
Caleb
July 8, 2009 at 8:42 PM