Thursday was an interesting day - I found myself challenged in a completely unexpected manner. We are in the final stages of formal QA before they enter into regression testing, and I have been doing a lot of work in the legacy application.
Throughout this whole project, I have been able to develop in a TDD fashion far more than I ever have before. It has gotten to the point where even to look at a problem is to solve it in a testable fashion. Where the legacy application was concerned, we generally took an approach of creating "targeted" presenters using a customized version of the MVP pattern.
When I say "targeted" presenters, I mean that we layered the new functionality directly into the existing pages, but decoupled it with a presenter that was used solely for the new functionality. This approach has created several problems, and I'm not sure I would recommend it for future use: it turns out that where the two styles of development interact, the code is very brittle.
One place where the interaction is most sensitive is in the save processes. The legacy code looks back to .NET Framework 1.1, and performs validation and persistence of the existing objects before the MVP code gets persisted. Because we don't want the 2.0 code referencing the 1.1 code, and the 1.1 code cannot recognize the 2.0 code, we cannot wrap the persistence between the two sets of components into a single transaction. Needless to say, there is some really ugly code involved in these processes.
The defect I was working on on Thursday was even worse. It involved code in a class that was termed a presenter, but a crucial step in the processing had been missed. So I spent about 8 hours layering in a fix that referenced a specification and other existing code, and did some ugly hoop-jumping to maintain the state of the view until all the persistence processes were complete in both code-bases.
I was testing and getting ready to check in when... oops. The presenter was used by two different views, both of which were parts of very different implementations of the legacy code! Since this was a legacy app, and the "presenter" was not fully tested, I was not aware of this through failed unit tests. Further, the presenter and views are defined in a separate solution, so I wasn't easily to see where the view was used via Resharper. Suddenly my state preservation model didn't look so good, and with much agonizing and advice on the part of a co-worker, I threw away my solution and stopped treating the "presenter" code as a presenter. Instead, 3 hours later I had the code-behinds on the aspx pages calling into business methods on the "presenter" class and all worked appropriately - at least as far as I could tell.
What bugs me is that it seems that I've become so committed to coding in a testable fashion that I couldn't even see this solution in the first place. And when it was suggested to me, I agonized for a few minutes before I could accept that it was the right way to go. I don't think that this is necessarily right and proper. As developers, we want to do the best job for our employers that we can, and when producing new code, that means TDD. However, when working with legacy code, sometimes TDD is probably more like putting lipstick on a pig - it looks good but does it really lead to the best solution?
Chad Myers recommended that evening that I read Michael Feather's book on Legacy code. Ironically, I've started the book already, just haven't gotten too far into it. I think that I will make a point of finishing it this month.