Good Design, Part III: Refactoring

Remember this?

 

public static ProductBean installProduct(String p) {

  ProductBean pi = new ProductBean(p);
  Connection c = new Connection("$Updates");
  for (Record x : c.getAllForKey("PLUG_INS", "P_NAME", p)) {

    // validate serial number
    if ("SNUM".equals(x.getString("RECORD_TYPE"))) 
    { 
      SecurityHelper.validateProduct(x.getString("SERIAL_NUMBER"),
        getEncryptionCertificate()); 
      pi.addSerialNumber(x.getString("SERIAL_NUMBER"));
    }

    // install license
    if ("LIC".equals(x.getString("RECORD_TYPE"))) 
    { 
      SecurityHelper.validateLicense(x.getString("USER_LICENSE"),
        getEncryptionCertificate());
      SecurityHelper.registerLicense(x.getString("USER_LICENSE"));
      pi.addLicense(x.getString("USER_LICENSE"));
    }

    // install driver
    if ("PDRV".equals(x.getString("RECORD_TYPE"))) 
    {
      Drivers.hotInstall(x.getBlob("BIN_DRIVER"));
    }
  }
  return pi;
}

There are at least three ways this code could be made better. The choice depends on any known history for this code, the goal of the refactoring efforts, as well as the confidence to try a refactoring or two and get a sense for which will get us most quickly to our goals.

Let’s listen in as Bob and Alice refactor the snot out of the method above.

Alice: “Phew! Long Method!”

Bob: “Yeah. And the loop is doing three separate things…”

Alice: “Okay, let’s try Split Loop.”

Alice opens a browser tab to https://www.refactoring.com/catalog/splitLoop.html just to be sure they don’t miss a tiny but critical step.

Bob: “Hey, cool: We made it worse.”

Bob and Alice now run all the tests. Did they really make it worse, or did they just make some of the duplication even more obvious?

Alice: “This code is going to give me a headache. Let’s do some general housekeeping on the Duplicated Code and all the abbreviated variable names.”

Code smells often hide other code smells, so it’s a good idea to trust your own judgment (or that of your Pair-Programming partner) regarding the most obvious or most potent stink, clean that up, then take another sniff. Also check in frequently with your intended goal. Are you refactoring to better understand the code, or are you about to find and fix a defect, or add new functionality? It’d be great if we could simply rewrite all our old code so it was “perfect,” but without a purposeful destination, even refactoring can become a waste of time.

Bob proceeds to use his knowledge of the IDE’s refactoring hotkeys (mostly Rename Variable and Extract Local Variable), and runs the tests after each significant set of refactorings. (Alternatively, there are awesome IDE plug-ins, like NCrunch, that will run tests in parallel with your editing activities.)

Alice: “Wow. Headache cured. Now let’s clean up that Long Method by doing an Extract Method on each loop.”

Bob: “Okay…Validate Serial Numbers, Install Licenses, Install Drivers… ”

Alice: “And let’s delete those comments now that they’re extraneous.”

Alice: “Much better, yes?”

Bob: “Yeah, but we’ve increased the amount of structural duplication with that Split Loop. I’m tempted to create some lambdas to remove the loops.”

Alice: “I see what you mean. The trouble is, we have teams who use this code but aren’t yet lambda-savvy. It’s a lot more readable than it was, and we’ve made our next enhancements easier to achieve.”

Refactoring Is Design

Refactoring is software design. It’s reshaping the design of the code, not rewriting the code. Typically, refactoring reduces duplication, resulting in less code.

Software design, in turn, is often a mystery to those outside the developer community. It has something to do with how the code accomplishes its functionality, but the tests or specifications do an even better job of that, so what exactly is software design?

Ultimately, a good design is one that helps the team understand what the code is doing, and allows the team to introduce new functionality without disturbing old. So good design includes robustness (through tests, and by isolating changes), clarity (ubiquitous naming, simple structures, modularity), and maintainability.

A good design is, therefore, code that passes all the tests, and that the team agrees facilitates change.

In the finale of this mini-series about Good Design, we’ll take a closer look at the role of tests in design, and we’ll also explore how some Agile team practices have a direct impact on the quality of your software design.

Related Articles

Responses