In 2008 or so, I was working primarily on a mid-sized enterprise application written using Visual Basic on the ASP.Net Web Forms platform. The application had been written by a team of people with recent experience primarily in the soupy morasses of classic ASP and desktop based VB, and to put it gently, that was very obvious when you dug into the code.
I say this not to criticize that team. I was one of the original members, and while I was away from the project for a couple of years after it launched, I was close to enough to the primary developer to know the kinds of restraints he was under as he worked to add a ton of functionality in in the impossible time frames you’d generally expect at a large company with a small IT department.
Long story short, he eventually left the team and maintenance fell to me. My immediate (and mid-term) reaction was to feel completely overwhelmed. There were a lot of 2AM calls saying that our users were reporting outages. There was a lot of code to wade through. There was a lot of duplicate code. There was a lot of commented out code. There were a lot of things that made me twist my head like a dog trying to figure out exactly what that high pitched whistle means. And there were a lot of things that made me update my resume and spend a lot of my employer’s time on Monster.com. However, I had a young child and the company provided a solid paycheck, stability and a very relaxed attitude towards work/life balance so I wasn’t thrilled about the prospect of leaving.
A few years prior to this (as the garbage can full of spaghetti code was being created), Michael Feathers penned the classic Working Effectively With Legacy Code in which he defined legacy code as being code that lacked unit tests. Because of this lack of tests, he reasoned, it is difficult or impossible to make code changes without knowing what the side effects might be.
My primary take-away from this book was the concept of “seams”. While legacy code doesn’t have tests and generally isn’t written in a testable manner, “seams” are those places in the code where you can pull things apart just enough to make a tiny change that allows for testing. For example, you can move the creation of a service object out from a method and require it as a constructor argument, which will allow you to pass in a mock/fake/stub and start adding tests. As you start to develop a knack for what these “seams” look and feel like, you can add tests and gradually get even the most unwieldy pieces of code under control.
I stuck with that job and applied the lessons learned from this book and beat a lot of the more complicated code into submission. I went from zero tests to around 2500 and was able to refactor, reorganize and rewrite things in ways that both made sense and were maintainable, all the while having confidence that my changes, backed by tests, were not going to cause any issues. I put an end to calls in the middle of the night, and the late evenings and weekend work tapered off to nothing. (It wasn’t all rainbows and unicorns – there were still areas of that code base that terrified me and I’m not ashamed to admit that I added plenty of extra nonsense in the name of keeping my skills current).
I am reminded of this now because I have found myself in a similar situation: I have ended up as the “lead” on a suite of legacy Web Forms applications with seven to eight years’ history of hacks, enhancements, changes of ownership and acquisitions, pure laziness and attempts to alleviate the boredom by factoring in several technologies du jour over the lifetime of the applications.
It appears the previous developers never met a situation where they couldn’t use a convoluted stored procedure instead of a few lines of code. They seem to have had no problem with databases being shared between multiple applications. 4500 line classes? So what? Code duplicated dozens of times in the same file? What of it?
And this is no big deal. Theses developers were subject to the same temporal, fiduciary (and often, mental) restraints that we all are. That these applications are still around after nearly a decade is testament to the fact that these developers played to their strengths to meet deadlines and provide business value. They moved on to bigger and better things, and the bigger and better things that I moved on to include their legacy code.
Feathers’ book is back on my desk (along with Fowler’s Refactoring, Kent Beck’s Implementation Patterns and a Costco-sized jar of Excedrin) and I’m relearning how to walk blindly into those dark hallways, feeling for those seams where tests can be introduced and refactoring can take place.
And so it begins!