In my entry last week, I wrote about my experiences using TDD for the little personal project I've been working on called ZPlanner. At that point I was just beginning to refactor. I think that's one of the biggest things forgotten when people try to do some variant of TDD, or any programming really. The thought of starting to code without having spent hours and hours designing your solution to the last method signature is a really scary prospect to some.
If you're anythiung like me, much of your formal education tried to ensure you had all sorts of documentation, that you'd thought out everything throughly and it tends to be difficult to break out of this frame of mind. The thing is, TDD *does* work. The thing is TDD assumes that after you've written your test and you've gotten your code to pass it (by hook or crook), you go back and refactor. The problem is many people skip the refactoring step and it's absolutely essential.
That's where you fix all the crappy stuff you did to pass your tests, where you try to tighten up your design. Otherwise, you end up with just want TDD detractors say you will, crappily designed and implemented code.
In any event, TDD isn't really the subject of this blog. It's more what my experiences with refactoring ZPlanner and something I noticed about my own coding (at least for ZPlanner) in the process.
If I were to sum up the result of my this refactoring, in a single sentence it would be this:
"Make the code as stupid as possible".
For a long time, I've always claimed I liked to keep things simple. That complexity is the enemy of maintainability and that it's important to "do the simplest thing possible" per TDD. When I started looking at my ZPlanner code, though, there was all sorts of complexity. My primary class, EstimatedItem, was an abstract base class which represented both stories and tasks (neither had a more concrete representation), I'd subclassed it to create my Iteration object, used a recursive Hibernate relationship (which took some time to figure out), and because the object was recursive, I had a bunch of recursive functions and complex logic to figure out how to sum estimates and where a given EstimateItem might be in a node-like structure. Okay, great! But the thing is all of that likely made it pretty damn difficult for anyone who was first coming to my code.
I'd also created an abstract base class in an attempt to represent all form actions. Of course, that wasn't good enough for me, so I tossed in Java Generics, with the notion that every item that was modifiable via the interface had a parent and child type, each passed in as template arguments. The fact that in some cases this abstraction really had to be hammered together to fit--for example, in the case of a Project there was no parent entity so I passed in Object as the Parent type of Project (which really makes absolutely no sense).
Of course, I was quite happy with myself for all of this at the time! Oh, look at me, I have a recursive Hibernate relationship! Oh, look at my cool use of Generics! Oh, look at all this recursive logic I know!
I think that's often the case with us programmers. we want to prove to others that we know our shit. All that complexity was almost like a badge of honor to show that I, too, was worth my meddle. Of course, I didn't justify it this way. No, it all makes sense, I told myself. It's the simplest thing possible after all, because I'm using the least code. But then using the least code, is often not the same thing as doing the simplest thing possible. Just look at any gob of hacked up Perl where the goal seems to be to put as much logic on one line as possible and obfuscate it to the greatest degree possible. "If it was hard to write, it should be hard to debug!", goes the old saying.
When I stepped back, though, I realized that all my cleverness really hadn't gotten me anything that meaningful. Maybe I had a few less classes, but the ones I did have could only be understood via my huge swaths of comments. Nothing was clear.
So, I started to try and make my code as un-clever as possible. Rather than using a recursive object relationship with an abstract base class, i copied and pasted my EstimatedItem into separate classes for each of Project, Iteration, Story, and Task. I eliminated the recursive nature. After all, did I really need infinitely nestable tasks? When the hell would anyone use that, clever as it may be? Sure, I had a bit more code, but it was obvious. Suddenly, I *had* a Story class. And guess one, it has a private member variable called "Iteration", which go figure, was the parent iteration to which the story belong. That's not very clever is it?
It also meant that rather than having a single table for all my objects (because in Hibernate, that's the preferred strategy when using inheritance) with join tables for the data of subclasses, I had an 'iterations' table with just the iterations, a 'project' class with just hte projects, and so on. I also got rid of my BaseAction class that was poorly representing the abstract notion of a http request. Sure my action classes ended up having a couple extra private members, and a few extra getter and setters, but it also meant you could just look at the class and pretty much know what it was doing.
Thankfully, all of this was relatively easy, because I had a lot of test code. It made the refactoring relatively easy and gave me some assurance that things actually worked when I made changes. I probably "rewrote" about 50-75% of the code and it only took 20-25 hours. I still have around 85-90% test coverage and everything pretty much works.
All of this means, that when I look at the code now, there's nothing really to pat myself on the back about. There's nothing particularly clever. There is a bit more code now, but it's not that much more (maybe 25%), but what's there is all quite mundane and obvious.
And that's the whole point.
Error'd: Tangled Up In Blue
2 days ago
2 comments:
A friend let me konw he'd written a similar blog to this which is here:
http://quay.wordpress.com/2007/10/03/design-smart-code-stupid/
My only comment to him was that, yeah, I've always *felt* this way. The interesting thing to me about my refactoring was that I realized sometimes my "ideal" and reality didn't quite coincide.
Nice article, simplicity rocks.
Post a Comment