Moving from cowboy coding to agile development

Wednesday, November 09, 2005

Note to self: Learn to make "big" decisions faster

I had a nasty problem at work. I was asked to add some functionality to old code that took the contents of a set of data and transferred them to another data set, changing the data to different form according to given criteria. What I was supposed to do was to include some previously excluded data in the transfer. It doesn't sound that difficult, but when the function that does the actual transfer has 7-10 different branches doing different things (according to the given transformation criteria) and nobody seems to have any idea how it does what it eventually does, the task becomes rather challenging.

My big mistake was to try to include my own additional code to the method (actually, the language only has functions, but it's easier to understand this using Java terms), so that the source data set could be read only once. I ended up banging my head against a wall for 5-6 days trying to understand how the old code works. This Frankenstein of a code was about 250-350 lines of code, with loads of if-clauses, loops and boolean flags. I tried debugging and running the transfer with a couple of outputs to track down the general idea of the existing code. I guess I made some progress, but just couldn't make the bloody thing work as a whole.

Finally I decided it's better to leave the monster in its cage and work around the problem in another way. So I added the new parts of the transfer only after the existing part had already been executed. The fix was ready to be tested in about 15 minutes.

You might be able to imagine how stupid I felt. This solution forces the program to go through the source data set twice, but since it's always pretty small and there are no database queries involved, the decrease in efficiency is probably too small for a human eye to even notice. The only thing with which I can defend my stubbornness to include the new functionality inside the existing code, is the logic that these things are closely related/connected and should be done together. I still think such logic has its place in designing such additions to existing functionality. However, if the options are to spend more than a week fighting a losing battle or doing the desired addition in less than an hour, it's probably justifiable to choose the latter option.


Timo Rantalaiho said...

It's always difficult to work with legacy code. I have found two things invaluable:

1) Thriving to maintain using TDD even with old code without tests

2) Martin Feathers' excellent Working Effectively with Legacy Code

As always with software development, you must remember that it's not just about the problem at hand. (As Ambler puts it in his Agile modeling principles, software is your primary goal and enabling the next effort is your secondary goal.) The Frankenstein function you were working with was new code at some moment, and nobody made it deliberately bad. It's just that without the proper attention, software rots with changes. This is why you must think of how make it better every time you modify it.

Mika Viljanen said...

True. The colleague I talked about this with agreed that the existing spaghetti should definitely be cleaned up. This piece of code is quite old and it has been tampered with several times, and neither of us knew who would be the person to talk to about the "essence" of the function. Lots of people have added teeny-weeny bits to the existing code and so the function has slowly turned into this monster. So, the "software rot" effect describes this situation very well.

Unfortunately this code is written using a non-OO language (at least in my opinion, although the language vendor tries to disagree) and I don't know any existing unit testing tools for it. However, I have started to think about ways to make a framework for it myself... A tough task, I'm sure, but things really have to change.

Timo Rantalaiho said...

Folklore says that Kent Beck used to recommend teams to build their own version of xunit before making e.g. JUnit. This is also illustrated in Test Driven Development: By Example where he makes an xunit implementation in Python.

There are ready-made xunit implementations or similar available at least for Java, C++ (CppUnit and CppUnitLite), C#, Python, Smalltalk, Ruby, PL/SQL, JavaScript and even a very minimalist implementation for C.

Mika Viljanen said...

We use C#, and I've checked that xunit is available for that. My superior was quite interested in hearing about unit testing, when I suggested we might dicuss it, but I didn't mention TDD then.

Unfortunately most of our code is written in Centura, and I frequently have to mess with that code. I have to see if I could figure out a way to unit test that, too...