Today I finished reading Working Effectively with Legacy Code by Michael Feathers. If you are not familiar with this book, you might be wondering if I’ve somehow been roped into maintaining some sort of 10 year old VB6 application -fortunately this is not the case. But I am indeed working on a legacy application – at least, it is legacy by Michael’s rather unique definition. Michael defines legacy code as code that is not under test, specifically unit tests. By Michael’s definition, virtually all of the code I have written during my career was legacy from the day it was born, and when I reflect on it, this seems like a fair statement. Here is a short excerpt from the preface:
Code without tests is bad code. It doesn’t matter how well written it is; it doesn’t matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behaviour of our code quickly and verifiably. Without them, we really don’t know if our code is getting better or worse.
It doesn’t matter how clean and refactored my code was – developers that had to maintain it were forced to do so with uncertainty. It is specifically that element of uncertainty that I associate with the legacy code experience.
The book uses a FAQ format, with chapter titles such as:
- I need to make a change. What methods should I test?
- I can’t get this class into a test harness.
- This class is too big and I don’t want it to get any bigger.
- I need to make a change but I don’t know what tests to write.
It does a great job of illustrating refactorings that let you break dependencies so you can get the code under test. These refactorings are designed to be performed safely without tests so you can get the tests written and then move on to more significant refactorings. The book also does a great job of tackling the mechanics of writing the tests themselves.
I think the book could have been better named. Ayende says that it should be mandatory reading for people that write code for a living. I agree with him, but the title does not communicate this. In general I found it very readable and relatively quick to get through, and the lessons the book offers are profound.
So the obvious question is whether it has changed the way I’ve been writing code. Well unfortunately, I wrote a bunch of legacy code just last week! By night I revel in the freedom that my unit tests give me as I work on Fluent NHibernate, but by day I continue to work in a manner that Uncle Bob would consider unprofessional. Why?
Unfortunately the book doesn’t have a chapter called “I’m all alone, nobody else is convinced that writing unit tests is worthwhile. Is it worth starting if its just me doing it?”. Nor does it have a “This is just a quick fix for someone else’s broken code, I’ll write tests when the –real- work begins” chapter (its a weak argument but I’ve found it an easy trap to fall into). The closest the book comes is the very last FAQ chapter: “We feel overwhelmed. It isn’t going to get any better”. But at less than 2 pages, it didn’t quite deliver what I needed to hear.
I’m finding it difficult to take that first step and add that 55th project to the solution (yeah…) so I can write some tests. Part of it is procrastination, but part of it is fear. I believe these ideas have value and I don’t want to screw it up. But simply “waiting until the time is right” is the refrain of the procrastinator.
This makes me feel that Working with Legacy Code is a great book, but not an entirely complete package. It gives you the tools and techniques but assumes that the reader can simply read it and run with it. Michael developed these techniques as he worked with teams that heeded his advice and were prepared to follow his lead. A piece of the puzzle is missing because Michael has been the consultant for so long.
Maybe this criticism is unfair. People can write books full of advice, but its up to the reader to start putting that advice into practice. A new work week begins tomorrow, perhaps I’ll add that 55th project before I do anything else tomorrow morning.
Great book, it's full of practical advice that's useful on almost any app.
ReplyDeleteI've got that similar feeling of isolation with regards to unit testing..it IS a real challenge to do it when others don't see the value in it. Educating the masses comes slowly, but we have to try..
It's very difficult when I feel like I'm alone and I know that other people will break my tests and not care about it. I have to remind myself that it's not about other people, it's about doing my job to the best of my ability and taking that first step is always the most difficult.
ReplyDeleteMaybe you'll try it and catch a regression bug. Maybe someone else on the team will start asking what you're doing. Maybe it will snowball. Then again, maybe it won't, but you'll still be able to look yourself in the mirror and know you did everything you could to write quality code.
If you do try, please keep us posted. We all could use some inspiration at times. :)
Thanks for the comments Rowan and Brian. I do intend to follow up on this in a week or two.
ReplyDelete1. Have you tried TypeMock? I'm not in an environment where I need it, but I have always seen it described as "mock anything" (including code that was not written with interfaces, DI, etc) - allowing you to get started with unit testing without a big refactor first.
ReplyDelete2. Whether tests are a separate project or not is personal preference. While it may be your preference that they are, I reckon you should try to not let that be a big drag. Tests within an existing project are certainly not "bad".
3. Part of it is just attitude. I dont want to fire up my web browser and click on shit over and over, just to see if I changed some domain logic properly. I value my time - clicking about within a web interface is not a good way to spend it.
I haven't tried TypeMock. Honestly the code base I'm working on isn't that bad - implementing the tests themselves is not hugely difficult, especially now that I have better ideas of how to approach it thanks to Michael's book. I totally agree with your point that firing up the UI to test some functionality can often be less efficient than simply writing the test.
ReplyDeleteI am happy to say that I managed to get past the first hurdle and I've been writing some tests, perhaps I'll write a follow up post this weekend to discuss it further.
Thanks for the comment!