A Java "extract method" refactoring example

Summary: A Java “extract method” refactoring example is demonstrated.

If you don't own a copy of Martin Fowler's Refactoring book, I highly recommend it. The basic idea of refactoring source code is that the code "smells" in one way or another, and there are a variety of ways to improve smelly code. More specifically, Mr. Fowler describes refactoring as this:

Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code, yet improves its internal structure.

The Extract Method refactoring pattern

Probably the most common refactoring pattern I use is one named Extract Method. With Extract Method, when you look at a piece of source code, you can see that there is too much going on in one place in your source code, and you further see that there are one or more "chunks" of the code that can (should) be pulled out into their own methods.

An Extract Method Java example

Looking at a specific Java Extract Method refactoring example, the following source code demonstrates the smell of one method doing too many different things in one place. I've put numbers in the comments to highlight the three major chunks of code:

// (1) make sure the code only runs on mac os x
boolean mrjVersionExists = System.getProperty("mrj.version") != null;
boolean osNameExists = System.getProperty("os.name").startsWith("Mac OS");

if ( !mrjVersionExists || !osNameExists)
{
  System.err.println("Not running on a Mac OS X system.");
  System.exit(1);
}

// (2) do all the logfile setup stuff
int currentLoggingLevel = DEFAULT_LOG_LEVEL;

File errorFile = new File(ERROR_LOG_FILENAME);
File warningFile = new File(WARNING_LOG_FILENAME);
File debugFile = new File(DEBUG_LOG_FILENAME);

// order of checks is important; want to go with more granular if multiple files exist
if (errorFile.exists()) currentLoggingLevel = DDLoggerInterface.LOG_ERROR;
if (warningFile.exists()) currentLoggingLevel = DDLoggerInterface.LOG_WARNING;
if (debugFile.exists()) currentLoggingLevel = DDLoggerInterface.LOG_DEBUG;

logger = new DDSimpleLogger(CANON_DEBUG_FILENAME, currentLoggingLevel, true, true);

// (3, 4) do all the preferences stuff, and get the default color
preferences = Preferences.userNodeForPackage(this.getClass());
int r = preferences.getInt(CURTAIN_R, 0);
int g = preferences.getInt(CURTAIN_G, 0);
int b = preferences.getInt(CURTAIN_B, 0);
int a = preferences.getInt(CURTAIN_A, 255);
currentColor = new Color(r,g,b,a);

A simple way to improve this Java code is to perform the Extract Method refactoring on it, and in this case, applying it several times. In this Java Extract Method example, I recommend breaking this source code out four smaller Java methods, as shown here:

dieIfNotRunningOnMacOsX();
connectToLogfile();
connectToPreferences();
getDefaultColor();

I think you'll agree that this series of Java method names is much easier to read than the big block of code shown earlier. The new code is broken out into small methods, and each method does "one thing", and their Java method names match what they do.

Now that you've seen those four method names, here is the source code for the methods I created with this Extract Method refactoring:

// (1)
private void dieIfNotRunningOnMacOsX()
{
  boolean mrjVersionExists = System.getProperty("mrj.version") != null;
  boolean osNameExists = System.getProperty("os.name").startsWith("Mac OS");
  
  if ( !mrjVersionExists || !osNameExists)
  {
    System.err.println("Not running on a Mac OS X system.");
    System.exit(1);
  }
}

// (2)
private void connectToLogfile()
{
  int currentLoggingLevel = DEFAULT_LOG_LEVEL;
  
  File errorFile = new File(ERROR_LOG_FILENAME);
  File warningFile = new File(WARNING_LOG_FILENAME);
  File debugFile = new File(DEBUG_LOG_FILENAME);
  
  // order of checks is important; want to go with more granular if multiple files exist
  if (errorFile.exists()) currentLoggingLevel = DDLoggerInterface.LOG_ERROR;
  if (warningFile.exists()) currentLoggingLevel = DDLoggerInterface.LOG_WARNING;
  if (debugFile.exists()) currentLoggingLevel = DDLoggerInterface.LOG_DEBUG;
  
  logger = new DDSimpleLogger(CANON_DEBUG_FILENAME, currentLoggingLevel, true, true);
}

// (3)
private void connectToPreferences()
{
  preferences = Preferences.userNodeForPackage(this.getClass());
}

// (4)
private void getDefaultColor()
{
  int r = preferences.getInt(CURTAIN_R, 0);
  int g = preferences.getInt(CURTAIN_G, 0);
  int b = preferences.getInt(CURTAIN_B, 0);
  int a = preferences.getInt(CURTAIN_A, 255);
  currentColor = new Color(r,g,b,a);
}

In this example, it may not be obvious why the connectToPreferences() method has been pulled out by itself, but if I could show you all the Java source code, this would be much more obvious. Besides the getDefaultColor() method, there are several other "get" methods that get information from the Java Preferences store. When you think of it this way, the connectToPreferences() method makes sense as a standalone method, as it is required by each of these other "get" methods.

Java Extract Method refactoring - Summary

In summary, in Martin Fowler’s Refactoring book, he describes the Extract Method refactoring pattern like this:

You have a code fragment that can be grouped together.

Turn the fragment into a method whose name explains the purpose of the method.

Also, to be clear, he puts a strong emphasis on naming the method very clearly. Good method naming makes your code much easier to read. I'll add one more piece of motivation and advice here myself:

Good method names keep you from having to write more documentation, or, put another way, any time you're thinking about writing documentation above a Java method signature, ask yourself why you're writing this documentation. If the real reason is because the method name doesn't describe what the method is actually doing, rename your method.

I hope this Extract Method refactoring example has been helpful. If you have any questions or comments, just leave a note in the Comments section below.