Search code examples
loggingrefactoringlegacy

Log statements prevent refactoring: how to help this?


I have some relatively large legacy method that I would like to refactor. It fits "Bulleted method" type as specified in Michael Feathers' "Working Effectively With Legacy Code" and thus it could be split in several sequential methods in rather straight-forward way . But each of its sequential steps outputs some log message and forming that message requires much more data than for the step itself. So when I try to extract method, I end up with method having, say, 6 parameters. If I had removed those log statements I would have method with only 1 parameter. So I effectively cannot refactor anything. And I'm not allowed to just drop log statements.

A part of method looks like that:

// much of code before
Device device = getDevice(deviceID);
boolean isFirstRegistration = false;

if (device == null) {
    /*logger.trace(
            "DeviceId", deviceID,
            "ADM", adminCode,
            "Phone", clientData.getPhone()
    );
    logger.info("First registration of the device. Device ID - " + deviceID);*/
    isFirstRegistration = true;
} else {
    /*logger.trace(
            "DeviceId", deviceID,
            "ADM", adminCode,
            "Phone", clientData.getPhone()
    );
    logger.info("Device ID - " + deviceID
            + " has been previously registered by adminCode: "
            + device.getAdminCode());*/
}
// much of code after

As you see, commented out logging statements. In this case I can extract method boolean isFirstRegistration(String deviceId). But when they are uncommented, signature bloats up to boolean isFirstRegistration(String deviceId, String adminCode, ClientData clientData). And that is not the most extreme case, just one from the first glimpse. Have you any ideas how should I refactor such method?


Solution

  • Sprout class. Turn logging over to a helper class and feed it all the data it needs, as it needs it.

    Update: Using the example variables presented, I would call, for example, myLogger.setDevice(device) as soon as the device was populated; similarly for adminCode, clientData,etc. Give the logger log methods like traceDeviceAdminCodeAndPhone() and logFirstRegistration(), where it uses its own instance variables. Anywhere the variables change, feed them again to the logger. Now, pass the logger in to the methods you're extracting, plus any parameters that are directly needed by the new method (but no more), and your logger can still report what it needs to from within the extracted method.

    Also, in case this is starting to look like your logger is too intimate with your method, an alternative would be to extract the method into a new class and convert some of the local variables to instance variables; then the logger could simply ask the new class for the values instead of holding onto them itself. But the logger class as a helper is probably a smaller, less impactful, refactoring. Whether that's good or bad depends on where you want to go with it.