Jan 142009
 
Fowler's Refactoring book

Fowler's Refactoring book

Have you ever got a program that you needed to add, change or even fix a simple function, but you just didn’t know where to start or were afraid of breaking the program’s logical consistency?

Refactoring is a modularity improving technique that is indispensable in the skill set of any professional software developer. In this post, we use Refactoring to effectively adopt an object-oriented design syntax and make a program easier and safer to work with.

The extremely influential book “Refactoring: Improving the Design of Existing Code” introduces the technique with a small didactic program for a video store, whose main function is to print a statement of a customer’s charges and his frequent renter points.

The Initial Program

The initial program was structured with classes representing the domain concepts of Customer, Rental and Video. The statement method in the Customer class is reproduced below (updated to current Java syntax).

    public String statement() {

        double      totalCharge  = 0;
        int         renterPoints = 0;
        String      result       = "Rental Record for " + getName() + "\n";

        for (Rental each : _rentals) {

            double thisCharge = 0;

            // determine charge for this rental
            switch (each.getVideo().getPriceCode()) {
                case Video.REGULAR:
                    thisCharge += 2;
                    if (each.getDaysRented() > 2) {
                        thisCharge += (each.getDaysRented() - 2) * 1.5;
                    }
                    break;
                case Video.NEW_RELEASE:
                    thisCharge += each.getDaysRented() * 3;
                    break;
                case Video.CHILDRENS:
                    thisCharge += 1.5;
                    if (each.getDaysRented() > 3) {
                        thisCharge += (each.getDaysRented() - 3) * 1.5;
                    }
                    break;
            }

            // add frequent renter points
            renterPoints++;

            // add bonus for a two day new release rental
            if ((each.getVideo().getPriceCode() == Video.NEW_RELEASE) &&
                (each.getDaysRented() > 1)) {
                    renterPoints++;
            }

            // show figures for this rental
            result += "\t" + each.getVideo().getTitle() +
                      "\t" + String.valueOf(thisCharge) + "\n";
            totalCharge += thisCharge;
        }

        // add footer lines
        result += "Amount owed is " + String.valueOf(totalCharge) + "\n";
        result += "You earned " + String.valueOf(renterPoints) +
                  " frequent renter points";

        return result;
    }

According to authors Martin Fowler and Kent Beck, a long method is a “bad smell in code” indicating “there is trouble that can be solved by a refactoring“. A quick analysis reveals the method is directly responsible for all of the following functions:

  • calculating the charge and frequent renter’s points for each customer’s rentals;
  • calculating the total charge and frequent renter’s points for the customer;
  • generating and formatting the statement.

A throughout program analysis confirms there is a modularity problem: it is not really an object oriented design. The classes are just data structures and the program’s main functionality is lumped into one single method. Take a look at the class diagram.

Video Store - Initial Class Diagram

Video Store - Initial Class Diagram

The dissociation between data and function responsibilities in a object oriented design is a modularity problem at the syntactic level. It means there is a problem in how the modules are composed and combined to form the program. This type of problem is usually indicated by the following symptoms:

  • long methods;
  • one object manipulating data defined in other objects;
  • long object/method chains intermediating access to other objects.

Modularity problems at syntactic level typically emerge as high priority issues as soon as any maintenance need touches them. Fortunately, they usually can be detected from program code analysis and solved simply by applying basic object-oriented design rules.

New Requirements Expose the Design Problem and Ignite Refactoring

Using Fowler’s own words, “there’s nothing wrong with a quick and dirty simple program”, as long as there is no need to change it. As expected, the program’s bad design became a relevant problem when the users asked to create an HTML version of the customer’s statement and told other changes would follow in the rules for video classification and rental calculations.

Although the change in the customer’s statement is confined to format and does not affect content, it is not possible to reuse the current statement generation and just add a specific method for the new HTML format.

There are two ways to immediately add the new function:

  1. duplicate the conditional logic for the intended format at each output point inside the statement method;
  2. duplicate the statement method itself, including the content generation logic for calculating charges and frequent renter points.

Duplication is unacceptable, because it adds complexity and threats the next changes. Replication breaks the program modularity by introducing coupled parts to the program’s structure. Whenever one of these parts changes, the respective couples must also change simultaneously. The resulting program would be harder to change and prone to inconsistency.

We should, instead, follow our master’s advice:

“When you find you have to add a feature to a program, and the program’s code is not structured in a convenient way to add the feature, first refactor the program to make it easy to add the feature, then add the feature.”

Martin Fowler

Before jumping into the program’s code and making any changes, necessary precautions must be followed by any professional software developer:

  1. identify precisely the scope of the intervention: what is to be changed, what is to remain invariant;
  2. prepare, review and put in place solid tests covering both changes and invariants.

Cohering Data and Function Abstractions

The most basic design rule for object-oriented design is to use the classes as the loci for cohesive abstractions integrating data and function responsibilities. The statement method is a clear target for this rule.

Fowler decomposes the statement method into elemental functions relative to the classes already defined in the program and then delegates each of these functions to the appropriate class:

  • the Rental class becomes responsible for calculating its charge and frequent renter’s points;
  • the Customer class becomes responsible for calculating its total charge and frequent renter’s points.

The class responsibilities rearrangement is evident comparing the class diagrams before and after the refactoring.

Video Store - Rearranged Responsibilities

Video Store - Rearranged Responsibilities after Refactoring

The transformation is very beneficial to the statement method, now renamed textStatement. It became easily understandable after delegating all the logic for calculating charges and frequent renter points and restricting its responsibility to content generation and formatting.

    public String textStatement() {

        String result  = "Rental Record for " + this.getName() + "\n";

        for (Rental each : _rentals) {

            // show figures for this rental
            result += "\t" + each.getVideo().getTitle() +
                      "\t" + String.valueOf(each.getCharge()) + "\n";
        }

        // add footer lines
        result += "Amount owed is " + String.valueOf(this.getCharge()) + "\n";
        result += "You earned " + String.valueOf(this.getPoints()) +
                  " frequent renter points";

        return result;
    }

Refactoring the program made it easier and safer for us to add the new feature by creating the new htmlStatement method for the HTML format. The program still exhibits some duplication, but complexity was managed to a more reasonable level.

    public String htmlStatement() {

        String result  = "<h1>Rental Record for <em>" + this.getName() + "</em></h1><p>\n";

        for (Rental each : _rentals) {

            // show figures for this rental
            result += "\t" + each.getVideo().getTitle() +
                      "\t" + String.valueOf(each.getCharge()) + "<br />\n";
        }
        result += "</p>";

        // add footer lines
        result += "<p>Amount owed is <em> " + String.valueOf(this.getCharge()) + "</em></p>\n";
        result += "<p>You earned <em> " + String.valueOf(this.getPoints()) +
                  "</em> frequent renter points</p>\n";

        return result;
    }

Conclusions

The initial Video Store didactic program instantiates the infamous Anaemic Domain Model, the archetype of a procedural design disguised under an object oriented appearance. Anaemic models gradually degenerate via duplication into coupled structures that are harder to change and prone to inconsistency.

Purely syntactic refactoring greatly improved the program modularity by simply applying the fundamental object-oriented design rule: integrate cohesive data and function abstractions into objects. The refactored program is easier and safer to change. The new requirement can be implemented by a smaller and more focused intervention, without generating any unintended side effects.

In this post the refactoring technique was merely used to change the program structure towards a proper object oriented syntax. There is plenty of room for additional improvement. The next post in the Revisiting Fowler’s Video Store series will explore the refactoring technique at a deeper level.

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)