Thursday, 2 July 2009

Where are you going to put that code?

Often we want to modify existing software by inserting an additional step. Before we do operation X, we want to do operation Y. Consider a simple example of a LabelPrinter class, with a Print method. Suppose a new requirement has come in that before it prints a label for a customer in Sweden, it needs to do some kind of postal code transformation.

Approach 1 – Last Possible Moment

Most developers would immediately gravitate towards going to the Print method, and putting their new code in there. This seems sensible – we run the new code at the last possible moment before performing the original task.

public void Print(LabelDetails labelDetails)
{
    if (labelDetails.Country == "Sweden")
    {
        FixPostalCodeForSweden(labelDetails);
    }
    // do the actual printing....
}

Despite the fact that it works, this approach has several problems. We have broken the Single Responsibility Principle. Our LabelPrinter class now has an extra responsibility. If we allow ourselves to keep coding this way, before long, the Print method will become a magnet for special case features:

public void Print(LabelDetails labelDetails)
{
    if (labelDetails.Country == "Sweden")
    {
        FixPostalCodeForSweden(labelDetails);
    }
    if (printerType == "Serial Port Printer")
    {
        if (!CanConnectToSerialPrinter())
        {
            MessageBox.Show("Please attach the printer to COM1");
            return;
        }
    }
    // do the actual printing....
}

And before we know it, we have a class where the original functionality is swamped with miscellaneous concerns. Worse still, it tends to become untestable, as bits of GUI code or hard dependencies on the file system etc creep in.

Approach 2 – Remember to Call This First

Having seen that the LabelPrinter class was not really the best place for our new code to be added, the fallback approach is typically to put the new code in the calling class before it calls into the original method:

private void DoPrint()
{
    LabelDetails details = GetLabelDetails();
    // remember to call this first before calling Print
    DoImportantStuffBeforePrinting(details);
    // now we are ready to print
    labelPrinter.Print(details);
}

This approach keeps our LabelPrinter class free from picking up any extra responsibilities, but it comes at a cost. Now we have to remember to always call our DoImportantStuffBeforePrinting method before anyone calls the LabelPrinter.Print method. We have lost the guarantee we had with approach 1 that no one call call Print without the pre-requisite tasks getting called.

Approach 3 – Open – Closed Principle

So where should our new code go? The answer is found in what is known as the “Open Closed Principle”, which states that classes should be closed for modification, but open for extension. In other words, we want to make LabelPrinter extensible, but not for it to change every time we come up with some new task that needs to be done before printing.

There are several ways this can be done including inheritance or the use of the facade pattern. I will just describe one of the simplest – using an event. In the LabelPrinter class, we create a new BeforePrint event. This will fire as soon as the Print function is called. As part of its event arguments, it will have a CancelPrint boolean flag to allow event handlers to request that the print is cancelled:

public void Print(LabelDetails labelDetails)
{
    if (BeforePrint != null)
    {
        var args = new BeforePrintEventArgs();
        BeforePrint(this, args);
        if (args.CancelPrint)
        {
            return;
        }
    }
    // do the actual printing....
}

This approach means that our LabelPrinter class keeps to its single responsibility of printing labels (and thus remains maintainable and testable). It is now open for any future enhancements that require an action to take place before printing.

There are a couple of things to watch out for with this approach. First, you would want to make sure that whenever a LabelPrinter is created, all the appropriate events were hooked up (otherwise you run into the same problems as with approach 2). One solution would be to put a LabelPrinter into your IoC container ready set up.

Another potential problem is the ordering of the event handlers. For example, checking if you have permission to print would make sense as the first operation. The simplest approach is to add the handlers in the right order.

Conclusion

Always think about where you are putting the new code you write. Does it really belong there? Can you make a small modification to the class you want to change so that it is extensible, and then implement your new feature as an extension to that class? If you do, you will not only keep the original code maintainable, but your new code is protected from accidental breakage, as it is isolated off in a class of its own.

Post a Comment