Tuesday, October 2, 2007

The Legacy Code Dilemma and compiler warnings

I hit the Legacy Code Dilemma today while trying to reduce compiler warnings in our solution.  For those that don't know it, the Legacy Code Dilemma is:

I need to fix some legacy code, but the fix isn't ideal.

After working on green-field development for a while, dealing with legacy code can be frustrating at first, unless you go through Michael Feathers' excellent Legacy Code book.  Whenever I feel the need to spend two days refactoring a small area to get it under test, I have to remind myself that it's more important to deliver business value AND pay down technical debt than paying down technical debt alone.  But then you hit dilemmas, where you KNOW the solution you're putting in is ugly, but just don't have the resources to "do it right".

To fix or not to fix

I hit that today, where I was trying to reduce compiler warnings.  I could fix the compiler warnings by making them go away, or fix the design problems that made the compiler warnings show up in the first place.

For example, I encountered a lot of "variable declared but not used":

try
{
    return PricingDatabase.GetPrice(itemId);
}
catch (Exception ex)
{
    return 0.0M;
}

In this case, the specific warning is "The variable 'ex' is declared but never used".  The problem is the variable "ex" is declared but never referenced in the "catch" block.  The design flaw, at least one of them, is that exception handling is used for flow control.  Any time you see try-catch block swallow the exception and do something else, it's a serious code smell.

The problem about fixing the design issue is that I'm dealing with legacy code, which has no tests.  Such design changes are foolish and even dangerous without proper automated tests in place.

Live to fight another day

If I wanted to fix the design flaw, I was in for a real battle.  I would need to get this module under test, and get any client modules under test.  For a codebase with little or no unit tests in place, that's just not feasible.

Instead, I'll fix the warning:

try
{
    return PricingDatabase.GetPrice(itemId);
}
catch (Exception)
{
    return 0.0M;
}

I kept the exception swallowing and all its nastiness, but gained a small victory by removing a compiler warning.  Since this solution has several hundred compiler warnings, all these little victories will pay off.  Instead of charging off on a path of self-righteous refactoring, I've fixed the pain and lived to tell the tale.

3 comments:

TexicanJoe said...

You are right that you claimed a small victory but you still have a "broken window" it is just hidden now.

What is going to prompt you to go back later and get this under test or refactor this?

Jimmy Bogard said...

The same thing that prompts me to add tests any other place in legacy code: I need to change it. If I don't need to change a piece of code, I don't touch it. The reasons why I need to change a piece of code varies widely, from system stability, larger refactorings, or business requirements.

Honestly, broken windows aren't an issue when a system is built with broken windows. We just have a very different strategy when dealing with changing legacy code versus writing new code.

Anonymous said...

You've "boarded up the window" kind of. It's still broken, but at least you've removed the compiler error.

Nice post.