Level 3 – Refactoring I

Code Along

A code-along is a story about coding in which you are encouraged to “code up” all the examples gray kangarooin the story. In a code-along you should not cut-and-paste.

In this code along, we will explore code refactoring which, when the context is understood, is simply referred to as refactoring.

Code can be refactored in a variety of different ways. The following list summarizes the refactorings used in this code along.

  • Converting a sequence of function calls into a nullary function.
  • Converting a nullary function declaration into a parameterized function declaration.
  • Introducing a wrapper function.
  • Factoring a constant out of the body of a function declaration.

Code to be refactored

Below is a simple program that creates the blue, yellow, red, green square (shown below the code). It contains function declarations.

open Level_3; 

build2D (32,32);

put2D (1,1) BLUE   (0,0); 
put2D (1,1) YELLOW (1,0);
put2D (1,1) RED    (1,1);
put2D (1,1) GREEN  (0,1);

show2D "4 colored square";

example_0


Step 1: Move top-level function calls to function declaration.

In this first refactoring step, the Bricklayer function calls that create the square are moved to the body of a nullary function declaration called fourColoredSquare. The square is then created by the function call fourColoredSquare().

open Level_3;

fun fourColoredSquare () =
    (
        put2D (1,1) BLUE   (0,0);
        put2D (1,1) YELLOW (1,0);
        put2D (1,1) RED    (1,1);
        put2D (1,1) GREEN  (0,1)    
    );

build2D (32,32);

fourColoredSquare ();

show2D "4 colored square";

example_0


Step 2: Introduce (x,z) coordinate parameter.

The next refactoring introduces the (x,z) coordinate parameter to the function fourColoredSquare. This change requires: (1) the creation of the formal parameter (x,z), (2)  conversion  of all absolute coordinates (e.g., (1,0)) to relative coordinates (e.g., (x+1,z)) in the body of the function, and (3) removal of the actual parameter () and addition of the actual parameter (0,0) to the fourColoredSquare function call.

open Level_3;

fun fourColoredSquare (x,z) =
    (
        put2D (1,1) BLUE   (x  , z  );
        put2D (1,1) YELLOW (x+1, z  );
        put2D (1,1) RED    (x+1, z+1);
        put2D (1,1) GREEN  (x  , z+1)    
    );

build2D (32,32);

fourColoredSquare (0,0);

show2D "4 colored square";

example_0


Step 3: Introduce 4x scalable put as a wrapper function.

This next refactoring actually changes the artifact from a 2×2 four colored square to an 8×8 four colored square. This is accomplished by introducing a wrapper function, called myPut, whose purpose is to modify all calls to the function put2D. To facilitate the introduction of myPut into the code, the myPut function is declared in such a way that its signature (i.e., the arguments it takes, and order in which it takes them) is identical to Bricklayer’s put2D function. Aligning the function signatures in this way enables the conversion from put2D to myPut to be accomplished by simply replacing all occurrences of put2D with myPut – a change that be easily accomplished using the find/replace function of a text editor.

open Level_3;

fun myPut (xSide,zSide) brick (x,z) =  put2D (4*xSide,4*zSide) brick (4*x,4*z);

fun fourColoredSquare (x,z) =
    (
        myPut (1,1) BLUE   (x  , z  );
        myPut (1,1) YELLOW (x+1, z  );
        myPut (1,1) RED    (x+1, z+1);
        myPut (1,1) GREEN  (x  , z+1)    
    );

build2D (32,32);

fourColoredSquare (0,0);

show2D "4 colored square";

example_3


Step 4: Factor out the scale constant from myPut.

The myPut function declaration contains four occurences of the scale factor 4. An important goal when writing code is to express every idea in your program only once. For example, suppose we wanted to use our program to create a (large) set of four colored squares having different sizes. To create a 10×10 four colored square the scale factor 4 would need to be changed to the scale factor 5. In particular, each occurence of the scale factor would need to be changed. We would like to redesign our program so that there is only one occurrence that needs to be changed. We will accomplish this through two refactoring steps. The first refactoring is shown here and lifts the scale factor 4 (a constant) out of the body of the myPut function declaration. This is accomlished by adding the formal parameter scale (any identifier will do) and then replacing all occurrences of the the scale factor 4 in the body of the myPut function with the formal parameter scale. In addition, all calls to the function myPut must contain the scale factor 4.

open Level_3;

fun myPut scale (xSide,zSide) brick (x,z) =  
        put2D (scale*xSide,scale*zSide) brick (scale*x,scale*z);

fun fourColoredSquare (x,z) =
    (
        myPut 4 (1,1) BLUE   (x  , z  );
        myPut 4 (1,1) YELLOW (x+1, z  );
        myPut 4 (1,1) RED    (x+1, z+1);
        myPut 4 (1,1) GREEN  (x  , z+1)    
    );

build2D (32,32);

fourColoredSquare (0,0);

show2D "4 colored square";

example_3


Step 5: Factor out the scale constant from fourColoredSquare.

The previous refactoring moved the scale factor 4 out of the body of the myPut function and into the body of the fourColoredSquare function. Since the constant 4 occurs multiple times in the body of the fourColoredSquare the goal of expressing this scale value exactly once in the program has not been reached. So we apply the same refactoring described in the previous step to the funtion fourColoredSquare. We introduce a formal parameter named scale (keeping the name consistent with our previous refactoring step – since this represents the same idea) and replace all occurences of the scale factor 4 in the body of the function fourColoredSquare with the formal parameter scale. We then need to add the scale factor 4 to every call to the the function fourColoredSquare. There is only one such call so we have reached the goal of having exactly one occurence of the scale factor 4 in the program.

open Level_3;

fun myPut scale (xSide,zSide) brick (x,z) =  
        put2D (scale*xSide,scale*zSide) brick (scale*x,scale*z);

fun fourColoredSquare scale (x,z) =
    (
        myPut scale (1,1) BLUE   (x  , z  );
        myPut scale (1,1) YELLOW (x+1, z  );
        myPut scale (1,1) RED    (x+1, z+1);
        myPut scale (1,1) GREEN  (x  , z+1)    
    );

build2D (32,32);

fourColoredSquare 4 (0,0);

show2D "4 colored square";

example_3


Step 6: Add brick parameters to fourColoredSquare.

This last coding example shows an artifact that is created using four calls to the function fourColoredSquare. Care needs to be taken when scaling – especially when creating an artifact using different scaling factors. A interesting question for the code below is: “If the fourColoredSquare function call sequence is extended, will the squares always be touching each other?” What about if we change the size of our initial square from 2×2 to 3×3? Is there a theory that underlies the creation of such artifacts using the algorithm shown in this code?

open Level_3;

fun myPut scale (xSide,zSide) brick (x,z) = 
        put2D (scale*xSide,scale*zSide) brick (scale*x,scale*z);

fun fourColoredSquare scale b1 b2 b3 b4 (x,z) =
    (
        myPut scale (1,1) b1 (x  , z  );
        myPut scale (1,1) b2 (x+1, z  );
        myPut scale (1,1) b3 (x+1, z+1);
        myPut scale (1,1) b4 (x  , z+1)    
    );

build2D (32,32);

fourColoredSquare 1 BLUE YELLOW RED GREEN (0,0);
fourColoredSquare 2 BLUE YELLOW RED GREEN (1,1);
fourColoredSquare 3 BLUE YELLOW RED GREEN (2,2);
fourColoredSquare 4 BLUE YELLOW RED GREEN (3,3);

show2D "4 colored square";

example_6