I've written about days in the TDD zone, and I've seen how the code written with this discipline of intentionality, compactness, and decoupledness from external dependencies tends to have almost magical powers, racing to the rescue towards the end of projects to take on unexpected tasks and save the day. But no aspect of software development leads to an endless string of good days, and to give a fuller picture of what practicing (and advocating) TDD is like, I want to write about some more challenging experiences.
On my current project we've been practicing some pairing and some TDD, which I like to call non-extreme programming. Pairing serves to build the "Borg Brain" of the team, to create a shared understanding of how the system works, without tying up a two-man development team to single thread all tasks. You do it enough so that you naturally have the instinct to reach out to your partner before taking a design decision (my personal heuristic: If you're worried the other developer will ask Why Did He Do That?, pair up so the question becomes the much more agreeable, Why Did We Do That?), and so that any member of the team naturally reaches out when stuck.
In a similar way, writing a unit test to define a problem has become a valuable touch stone, a way to clarify design thinking, while not being a practice I follow religiously at all points in the sprint. It's for day 3 to day 5, not demo day minus one. Again, the goal is to nourish the code with good design, without the commitment of resources to achieve the purity of The Three Rules of TDD. It's a flexitarian approach to agile: make healthy habits an every larger part of your development life, but have a steak when you feel you need to have a steak.
So towards the end of the last sprint, the other developer raised a concern that one of our controllers was becoming cluttered with business functionality and was getting to be hard to read and hard to work with. (Code smells: opacity and rigidity, for those keeping score at home.) So, story points delivered, and a day to demo, we decided to refactor it into shape. A quick exchange of ideas lead to a decision to build a Watchamacallit, which, we decided, would allow us to eliminate much of the code in this class. So we started writing tests, white-boarding out functionality in our TestFixture, to build a nice-clean Watchamacallit. We thought about whether we wanted to have separate methods for AddThis and AddThat, whether the implementation of one should call the other, we made our tests fail, and then pass, and we kept things tidy and readable. I showed off some resharper tricks, and was feeling pretty good about life when my team mate compared coding with me to a Pluralsight course. All that was left was integrating this new code, and...
Good feeling gone.
Because there really wasn't a need for the Watchamacallit. This functionality was already cleanly handled. We had built something we didn't need, a cardinal sin in agile. We had killed a day.
Never wanting EVER to have that feeling again, I asked myself how this happened. And it was obvious. We refactored as if we were building new functionality. You write tests to do things the system doesn't yet do, that your business owner needs. You write tests to confirm edge cases. You don't write tests to build new functionality that is really old functionality, and you don't refactor code without looking at, and working with, the code you are trying to refactor. You do it in small chunks, DRY-ing up this, abstracting out that, and breaking up monster methods in to readable chunks. You refactor, you don't start from scratch. You work from direct feedback from the system:
The third value in XP is Feedback. More coaching phrases are "Don't ask me, ask the system" and "Have you written a test case for that yet?" Concrete feedback about the current state of the system is absolutely priceless. Optimism is an occupational hazard of programming. Feedback is the treatment. - Kent Beck, Extreme Programming Explained, p. 31I discussed with my teammate the next day, and shared one of the key responsibilities of the non-driving developer is to cast a critical eye on the code flowing on to the screen:
While one partner is busy typing, the other partner is thinking at a more strategic level. Where is this line of development going? Will it run into a dead end? Is there a better overall strategy? - Kent Beck, Extreme Programming Explained, p. 101So a heuristic: if the non-coding pair is saying things like "Wow that's cool," the necessary critical component is absent. Stop, hand the keyboard over, and ask if what you're doing makes sense.
Next: A TDD crisis of confidence
No comments:
Post a Comment