Lab: Refactoring Code

Steven J Zeil

Last modified: Jan 7, 2022
Contents:

This lab will give you practice in refactoring existing Java code: modifying it in ways that have nothing to do with writing it in the first place or fixing bugs.

1 Set Up Your Project

  1. By now, you should have set up your development environment.

    You may reuse your project directory from that earlier lab or start a new project with the original set of files.

  2. If you are starting with a fresh copy, set up a Java project with that directory using your chosen IDE.

    • Make sure that the code compiles as it did before.
    • Set up a run configuration using one of the provided test files and make sure that the program runs correctly.

2 Refactoring 1: Project Layout

As projects grow, we are seldom eager to keep everything in one flat directory structure. Instead, we try to separate the source code, any test data, and any files produced by compiling the source code into separate directories.

  1. If you are using Eclipse, delete the project from the list in the Project or Package Explorer column. (Do not delete the files – just the project.)

  2. Exit your IDE and do the remaining steps via operating system commands.

  3. Delete, if present, the files/directories: .project, .classpath, and .vscode/.

    Note that all of these start with a ‘.’. which means that in Linux and MacOS these are considered “hidden” files that do not show up in normal directory listings. You can use “ls -a” at the command line to show these hidden files.

  4. Let’s isolate the source code. Create the nested directories src/main/java and move the .java files inside there.

    src/main/java is now our source code directory. Java projects can have multiple source code directories.

    If we were to compile this code from the command line, we would start by cd’ing to the source code directory and would issue the compilation command from there.

    1. Try it: From a command line, cd to src/main/java and give the command

      javac -g Highway.java
      

      It should compile without errors. Then examine the directory. You should see the .class files produced by compiling Java code.

    2. To run the program, we either need to be in the source directory or supply a CLASSPATH to that source directory. There are two ways to modify the CLASSPATH. One is to set it as an environment variable or to supply the path info to a -cp command line parameter.

      cd to the top of the project (the directory containing src/) and give the command:

      java -cp src/main/java Highway testData/test000.dat
      

      Then, to prove that the -cp was necessary, give the command

      java Highway testData/test000.dat
      

      You can see that, without the -cp, the java engine does not know where to look to find the code.

    3. Delete the .class files from within src/main/java

  5. Let’s isolate the test data. Create the nested directories src/test/data and move the .dat files inside there. Remove the testData/ directory afterwards.

    Check your directory structure. Your project’s top-level should now have the README.md file, the src/ directory, and nothing else. If you have anything else, delete it.

  6. Open up your IDE, creating a Java project at your top directory (the one containing src/). Both Eclipse and VSCode are smart enough to recognize that your project’s source code directory is src/main/java.

  7. In your operating system, search the project directories for .class files.

    Note that both IDEs will, by default, store the .class files in a separate directory from the source code.

    • Among other advantages, this makes cleaning up a project fairly easy, because you can safely delete the bin/ directory at any time, knowing that the IDE can rebuild everything within it.
  8. If you are running Eclipse, right-click on the project name in the Package Explorer, and select Build path, then Configure Build Path.

    The Build path in Eclipse controls a variety of crucial project settings, including how many source code directories you have and where they are, and where the compiled output code should be stored.

    Go to the “Source” tab and examine your project setup. It should be obvious where Eclipse believes that your source code directory is. Can you also see where the output directory is set? Try changing the location of the output directory and observe the effects, both within Eclipse and at the Operating System level.

3 Refactoring 2: Repackaging the Code

  1. Edit Interval.java and add the following line at the top of the file:

    package edu.odu.cs;
    

    Save the file. Your IDE should now flag the code as erroneous, and if you examine the error message you will see that it is complaining about the location of the file being inconsistent with the package.

    Remember that, in Java, packages mirror directories and vice versa.

  2. Your IDE will offer to fix the problem for you. In Eclipse, click on the small red marker on the left edge of the source code. In VSCode, click on the offering line and then click Ctrl-. to request a “quick fix”.

    1. Each IDE will offer to either change the package declaration to match the current directory location, or to move the file to a new directory to match its package declaration.

      Select the option to move the file.

    2. Examine the effects of this both in the IDE and at the operating system level. You should see a change in the directory structure under src/main/java/.

  3. In the IDE, move the other .java files to the new package directory. Again you will see error messages showing up.

    This time, allow your IDE to change the package declaration to match the new location of the file.

    The directories src, src/main, and src/main/java are part of the project structure.

    The directories edu, edu/odu, and edu/odu/cs are part of the actual Java source code.

  4. Verify that you can still run the program. Don’t forget that your test data is now inside src/test/data/.

  5. Exit your IDE for the moment and return to a command line.

    1. cd to src/main/java (your source code directory). Look at the directory contents. You should have only a directory named CS350.

    2. Compile the code:

      javac -g edu/odu/cs/Highway.java
      

      We still compile the code from the source code directory, but now we indicate which code to compile by giving the path through the package directory structure.

    3. cd back to the top of the project and run the code:

      java -cp src/main/java edu.odu.cs.Highway src/test/data/test000.dat
      

      Note the use of ‘.’ in the class name rather than ‘/’. We aren’t giving a path to a file here - we are giving the full name, including packages, of the compiled class that we want to run.

    4. Delete the .class files in src/main/java/edu/odu/cs/.

4 Refactoring 3: Improving the Source Code

  1. In your IDE, open Highway.java. Find the line that calls the subtract function. Highlight the name of that function by right-clicking on it or clicking and dragging your mouse across it. Now, let’s find its declaration.

    • In Eclipse, use the F3 key or right-click and select “Open Declaration”.
    • In VSCode, use the F12 key or right-click and select “Go to Declaration”.
  2. You should be looking at the declaration of the subtract function in Ranges.java. We are going to rename this function.

    • Double-click or click-and drag your mouse to highlight the word “subtract” in the declaration.

    • If you are in Eclipse, right-click on the highlighted word and select Refactor, then Rename.

    • If you are in VSCode, right-click on the highlighted word and select Rename Symbol. Change the name to “remove”.

    Now look at Highway.java. Notice that it is marked in the IDE editor as changed but not saved. Look inside the while loop in the function doIt. You can see that the call to subtract has been replaced by the new name, even though we made the change in a completely different file. This was clearly smarter than a simple text search-and-replace.

    Save the changes to both files.

  3. Again, look at Ranges.java, inside the newly renamed remove function. According to the principles of Clean Coding, there’s too much going on inside this function body.

    Click-and-drag the mouse to highlight the 2nd if statement (and its body).

    • If you are in Eclipse, right-click on the highlighted text and select Refactor, then Extract Method...
    • If you are in VSCode, right-click on the highlighted text and select Refactor..., then Extract to method

    Name the new function “replaceIfOverlapping”.

    You should see the if statement pulled out of the remove function and replaced with a call to a replaceIfOverlapping function.

  4. In that new function, highlight the if statement body inside the { } and extract that as a new method named “replaceOverlappingInterval”.

  5. Look in the newly created function. Do you see two small if statements with nearly identical structure? Highlight the first of them, and extract as a new function named “addIfNonEmpty”. Notice that both blocks of nearly identical code are replaced by a call to the newly extracted function.

    You may want to rename the Interval parameter to this common function to something more general, like “interval”.

  6. Check to be sure that the program compiles and runs as before.

    It should. The point of refactoring is to introduce useful changes without affecting the behavior of the code.