Guideline: Refactoring
Relationships
Related Elements
Main Description

Topics

What is refactoring?

Refactoring is the act of improving the structure of a program without changing its behavior. Refactoring is done in tiny little steps, each barely worth doing. In between each step, we run the relevant unit tests to make sure that the changes we made have not broken anything. The edit/compile/test cycle is usually between 30 seconds and five minutes.

Why should I refactor?

The purpose of refactoring is to improve the design and readability of the code. There are several specific goals:

  • The code should pass all its tests.
  • It should be as expressive as it is possible for you to make it.
  • It should be as simple as it is possible for you to make it.
  • It should have no redundancy.

When should I refactor?

Refactoring is not something that we schedule. There is no line item in the schedule for it. There is no particular time when we do it. Refactoring is done all the time. As you and your pair partner work on a task, such as writing tests and code, you will notice that the code and tests are not as clean and simple as they could be. That is the time to stop and refactor that code.

The rule is: Don't let the sun set on bad code.

An Example of Refactoring

Consider the two unit tests and the Formatter class shown below. The Formatter class works but is not as expressive as I'd like it to be. So I'll refactor it in stages.

  public void testCenterLine()
     {    
   Formatter f = new Formatter();                    
  f.setLineWidth(10);     
    assertEquals("   word   ", f.center("word"));                
 }
  public void testOddCenterLine() throws Exception
   {     
  Formatter f = new Formatter();     
 f.setLineWidth(10);     
    assertEquals("  hello   ", f.center("hello"));
  }


  import java.util.Arrays;
  public class Formatter
 {   
    private int width;   
   private char spaces[];    
  public void setLineWidth(int width)
     {        
       this.width = width;        
     spaces = new char[width];        
       Arrays.fill(spaces, ' ');    
       }       
    public String center(String line)
       {        
       int remainder = 0;        
      StringBuffer b = new StringBuffer();        
        int padding = (width - line.length()) / 2;        
      remainder = line.length() % 2;        
      b.append(spaces, 0, padding);        
       b.append(line);                       
      b.append(spaces, 0, padding + remainder);        
       return b.toString();     
       }
   }

The setLineWidth function is a little mysterious. What is this spaces array and why is it being filled with blanks? By looking ahead into the center function, we see that the spaces array is just a convenience to allow us to move a number of blanks into a StringBuffer. I wonder if we really need this convenience array.

For the moment, I'm going to pull the initialization of the array out into its own function named buildArrayOfSpaces. That way, it's all in one place, and I can think about it a little more clearly.

public void setLineWidth(int width)
{    
   this.width = width;        

buildArrayOfSpaces(width);  
}  
private void 
buildArrayOfSpaces(int width)
{        
   spaces = new char[width];        
   Arrays.fill(spaces, ' ');  
} 



Run the tests: the tests pass

I don't like the way center function is constructed. There is math scattered all through it. I think we can rearrange the math to make things clearer.

public String center(String line)
{        

int remainder = line.length() % 2;        

int numberOfBlanksInFront = (width - line.length()) / 2;        
   
int numberOfBlanksAtEnd = (width - line.length()) / 2 + remainder;    
   StringBuffer b = new StringBuffer();        
   b.append(spaces, 0, 
numberOfBlanksInFront);        
   b.append(line);        
   b.append(spaces, 0, 
numberOfBlanksAtEnd);        
   return b.toString();  
}



Run the tests: the tests pass

This looks good, but we can reduce the clutter by changing some of the variables into functions.

public String center(String line)
{        
   StringBuffer b = new StringBuffer();        
   b.append(spaces, 0, 
numberOfBlanksInFront(line));        
   b.append(line);        
   b.append(spaces, 0, 
numberOfBlanksBehind(line));        
   return b.toString();   
}    

private int numberOfBlanksBehind(String line)

{        
   
int extraBlankIfOdd = line.length() % 2;        
   
return (width - line.length()) / 2 + extraBlankIfOdd;  

}    

private int numberOfBlanksInFront(String line)

{        
 
return (width - line.length()) / 2;  

}  



Run the tests: the tests pass

This makes the center function read a little better. However, the use of the StringBuffer.append function is a little confusing. We might be able to improve it a little by creating a more explicit function.

public String center(String line)
{    
   StringBuffer b = new StringBuffer();                    

appendBlanks(b, numberOfBlanksInFront(line));     
   b.append(line);     

appendBlanks(b, numberOfBlanksBehind(line));     
   return b.toString(); 
}   

private void appendBlanks(StringBuffer b, int numberOfBlanks)

{     
   
b.append(spaces, 0, numberOfBlanks); 

} 



Run the tests: the tests pass

Now we can rewrite appendBlanks to avoid using the spaces array.

import java.util.Arrays;  
public class Formatter
{     
   private int width;                       
   public void setLineWidth(int width)
   { 
      this.width = width;     
   }       
   public String center(String line)
   { 
      StringBuffer b = new StringBuffer();              
      appendBlanks(b, numberOfBlanksInFront(line)); 
      b.append(line); 
      appendBlanks(b, numberOfBlanksBehind(line)); 
      return b.toString();     
   }       
   private void appendBlanks(StringBuffer b, int numberOfBlanks)
   {

 while(numberOfBlanks-- > 0)     

    b.append(' ');     
   }      
   private int numberOfBlanksBehind(String line)
   { 
      int extraBlankIfOdd = line.length() % 2; 
      return (width - line.length()) / 2 + extraBlankIfOdd;                  
   }      
   private int numberOfBlanksInFront(String line)
   { 
      return (width - line.length()) / 2; }    
   } 
}


Run the tests: the tests pass

The names of functions like numberOfBlanksBehind imply that the reader knows that these will be called from the center function. We should eliminate this implication by renaming those functions.

import java.util.Arrays;
public class Formatter    
{     
   private int width;       
   public void setLineWidth(int width)
   { 
      this.width = width;     
   }       
   public String center(String line)
   { 
      StringBuffer b = new StringBuffer(); 
      appendBlanks(b, 
numberOfBlanksToLeftOfCenter(line));                
      b.append(line); 
      appendBlanks(b, 
numberOfBlanksToRightOfCenter(line));                
      return b.toString();     
   }       
   private void appendBlanks(StringBuffer b, int numberOfBlanks)
   { 
      while(numberOfBlanks-- > 0)     
         b.append(' ');     
   }      
   private int 
numberOfBlanksToRightOfCenter(String line)
   { 
      int extraBlankIfOdd = line.length() % 2; 
      return (width - line.length()) / 2 + extraBlankIfOdd;     
   }       

   private int 
numberOfBlanksToLeftOfCenter(String line)
   { 
      return (width - line.length()) / 2;     
   }
} 



Run the tests: the tests pass

And I think we are done. You might find other refactorings to do, or you might not agree with all of the refactorings I've done. That's to be expected. The point is, however, that I have put a lot of effort into the readability and simplicity of this class. That effort will help others understand this class and make it easier for them to change the class when the time comes.