On this page:

Introduction

IntelliJ IDEA offers a lot of automatic refactoring capabilities, but as developers it's not enough to know how to perform them, we need to understand what these refactorings are, when we would want to apply them, and any possible downsides or things to consider before using them.

Refactoring, as defined by Martin Fowler, "...is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior". Hence it's important before performing any refactoring in production code to have comprehensive test coverage to prove that you haven't inadvertently changed the behaviour.

The goal of this tutorial is to introduce those who may be new to the idea of refactoring, particularly automatic refactoring, to IntelliJ IDEA's capabilities and show when you might want to apply three of the basic types of refactoring: Renaming, Extracting and Deleting.

Renaming

Renaming may seem like a trivial refactoring, but renaming via a simple find-and-replace often means unrelated items with the same name are unintentionally changed. Using IntelliJ IDEA's rename refactorings minimises these errors.

Why rename?
  1. The name is not descriptive enough
  2. The class/method/variable name doesn't match the what it really is
  3. Something new has been introduced, requiring existing code to have more specific name
Renaming as you code
  1. To rename the endpoint field, place your cursor on the word endpoint and press Shift+F6. IntelliJ IDEA will pop up a list of suggestions, based on the class name and other aspects. In this case, the name of the parameter this field is used in is also suggested.

    Rename the field.

    Select one of these options or type your own. If the field has a getter, IntelliJ IDEA will ask if you want to rename this as well.

    Do you want to rename the getter?.

    You'll notice that all uses of this field are changed to the new name, and if you've chosen to rename the getter, other classes in your project will be updated to use the new name. See the next step for more information on method renaming.

  2. To rename the method, the process is the same: place your cursor on init and press Shift+F6. Here you'll have fewer suggestions, so type the new name:

    Rename the method

    As well as renaming the method, this renames all calls of the method and all overridden/implemented methods in subclasses. IntelliJ IDEA can also rename non-code uses of the name too, which is useful if you have XML configuration or other non-Java files which refer to classes or methods. You can configure what gets renamed if you press Shift+F6 a second time to bring up the rename dialog

    More rename settings

    If the rename will apply to more than just code, IntelliJ IDEA will preview the refactoring for you, so you can select which changes you want to make. Often in these cases you may choose not to rename occurrences in comments, especially if the original method name was a common word like name.

    Preview the rename

    If you don't want to make some of these changes, press Backspace on the usages you do not want to change.

  3. Renaming a class is similar, but can also be performed via the Project Tool Window. In this case, because we've discovered we want to rename the class where we use it, we're going to use Shift+F6 on the class name in the code.

    Rename the class

    Of course, any code that uses this class will also be renamed, but you also have the option of renaming variables, inheritors and other parts of the code so they're aligned with the new name. Again, these options can be set by pressing Shift+F6 a second time.

Extracting

IntelliJ IDEA's extract refactorings give developers the power to reshape their code when it becomes clear the current design, whether on a small or large scale, is no longer fit for purpose.

Extract variable
  1. First, let's reduce the duplication and introduce a variable that describes what this operation is doing. Place your cursor anywhere in the expression message.indexOf("\"screen_name\":\"") + 15) and pressCtrl+Alt+V. IntelliJ IDEA will suggest a context for this refactoring, and you want to choose the one that encapsulates this expression:

    Select the expression to extract

    Next, if IntelliJ IDEA has detected this expression occurs more than once, you have the option to replace all the occurrences or just the one you selected.

    Select replace all occurrences

    Once the variable is extracted, IntelliJ IDEA suggests possible names based on things like the parameter the expression was used in.

    Choose or type name

    We're going to use our own name, indexOfFieldValue, to describe what this really represents. Note that you can decide whether or not you wish this variable to be final.

  2. Next we're going to introduce a variable for the String value. There's two reasons for this: firstly, to document what the String value represents, and secondly because it will help us to remove the magic number.

    Place your cursor somewhere on screen_name and pressCtrl+Alt+V.

    Select String value

    We're going to give this a more meaningful name, fieldName.

    Give the variable a name
  3. Now, we're going to create a variable for the other expression used as a parameter to substring(), using the same process, and we'll call this indexOfEndOfFieldValue.

    Extract variable
  4. Finally, we can remove the magic number, as this is just the length of the field name. The final code looks like:
    static String getUsernameFromMessage(String message) {
        final String fieldName = "\"screen_name\":\"";
        final int indexOfFieldValue = message.indexOf(fieldName) + fieldName.length();
        final int indexOfEndOfFieldValue = message.indexOf("\"", indexOfFieldValue);
        return message.substring(indexOfFieldValue, indexOfEndOfFieldValue);
    }
    It's longer than the original, but it's much more descriptive, which is particularly important in code like this where it's not clear what each expression represents. The choice of applying final or not is up to you, and depends upon your coding standards.
Extract parameter
  1. Place your cursor on fieldName and press Ctrl+Alt+P

    Extract parameter

    As with the other refactorings, you can type a new name for the parameter if you wish. IntelliJ IDEA also previews the updated method signature. Press Enter to approve the changes.

    Any problems with the new method signature

    This particular issue tells us the method is being used as a method reference, and this change will result in the method reference being converted into a lambda expression. This message could be a sign that this is not the refactoring you wish to perform. If this is the case, the next example shows an approach we could take using Extract Method. However, for this example we'll assume we're happy with the consequences of introducing a new parameter, so we'll just select Continue.

    Next, IntelliJ IDEA will detect any code which can now be replaced with a call to the new method signature.

    Now this code is a duplicate of the updated method

    If you select Replace in this case, all the duplicate code will be replaced, and IntelliJ IDEA will select the appropriate value to pass in for the new parameter.

  2. At this point, the original method getUsernameFromMessage is more general than it was, so we should rename it. We put our cursor on the name and useShift+F6, as shown in the previous section.

    Rename the updated method
  3. We can simplify the code further. Inline is the inverse of extract, and in the code we have here it may be appropriate to inline our temporary variable, as the variable name gives us little more than having the value passed directly into the method. Or, given that getTextFromMessage really is a simple delegation to getValueForField, we can use inline to remove this method completely.

    To inline, place your cursor on the getTextFromMessage variable and press Ctrl+Alt+N

    Inline unnecessary method
  4. Now our final code looks like:

    static String getValueForField(String message, String fieldName) {
        final int indexOfFieldValue = message.indexOf(fieldName) + fieldName.length();
        final int indexOfEndOfFieldValue = message.indexOf("\"", indexOfFieldValue);
        return message.substring(indexOfFieldValue, indexOfEndOfFieldValue);
    }

    Our code that was calling the original getUsernameFromMessage method was:

    Parser::getUsernameFromMessage

    and is now

    (message) -> Parser.getValueForField(message, "\"screen_name\":\"")

    Our code that was calling the original getTextFromMessage method was:

    String[] wordsInMessage = Parser.getTextFromMessage(message).split("\\s");

    and is now

    String[] wordsInMessage = Parser.getValueForField(message, "\"text\":\"").split("\\s");
Extract method
  1. First, highlight the code that's common between the two methods:

    Highlight an instance of the duplicate code

    Pressing Ctrl+Alt+M will bring up the Extract Method Dialog.

    Extract method dialog

    Type the name of the new method, getValueForField, and check the parameter names and order. In this case, we're going to swap the order of the parameters because we prefer the fieldName parameter to be closer to the name of the method. This will depend upon your code style and team preferences, you might like to read the name and parameters aloud to see if it makes sense as a statement in natural language.

    Final settings in extract method dialog

    When you press OK, IntelliJ IDEA will detect code that can be replaced with a call to this new method, and will offer to refactor that too. We're going to select Yes.

    Refactor duplicate code
  2. At this point, our getTextFromMessage and getUsernameFromMessage methods are two lines of simple code, and here it makes sense to inline the fieldName variable, as the method name is descriptive enough to remove the temporary variable. Press Ctrl+Alt+N on fieldName and select Refactor.

    Inline unnecessary variable
  3. As a last touch, you may want all similar methods grouped together. Depending upon your settings, IntelliJ IDEA may have placed the new method directly under the method you were in when you chose to extract the method, as in our case here. To put the helper methods next to each other, put your cursor on the getValueForField method name and press Ctrl+Shift+Down. This will place your new method, getValueForField, under the existing getUsernameFromMessage method.

    Our final code looks like:

    static String getTextFromMessage(String message) {
        return getValueForField("\"text\":\"", message);
    }
    
    static String getUsernameFromMessage(String message) {
        return getValueForField("\"screen_name\":\"", message);
    }
    
    static String getValueForField(String fieldName, String message) {
        final int indexOfFieldValue = message.indexOf(fieldName) + fieldName.length();
        final int indexOfEndOfFieldValue = message.indexOf("\"", indexOfFieldValue);
        return message.substring(indexOfFieldValue, indexOfEndOfFieldValue);
    }

    Now we have two very specific helper methods which get the message body and the username, and a more general method that can be used to get the value of any field from the message. Additional helper methods can be added when there are other fields which are frequently needed.

When IntelliJ IDEA detects duplicate code this is a very good candidate for creating a new method that can be called by all of the places that has the duplicate code.

Deleting

Sometimes when you've refactored code in several steps, you can end up with code that is no longer used, or ideally should not be used. As the goal of refactoring is simplification, you should always aim to remove unused code where you can - regardless of any impact (or not) unused code has on the performance of your application, unused code definitely takes a toll on developers working with and trying to understand the application.

Safe delete
  1. If the unused declaration inspection is turned on, the method name will be in grey to represent that it is unused. Unused method is safe to delete
  2. Place the cursor in getUsernameFromMessage and press Alt+Enter. This will give you the option to delete this method. Delete unused method
  3. Selecting safe delete will pop up the safe delete dialog, allowing you to search for usages of this method.

    Safe delete dialog

    Press OK to go ahead and do the search. In our case, it was completely safe to delete, so the method is removed.

  4. It's possible that our "unused" method is not flagged as unused, as it may be covered by a test. But if we still know that it's unused, or have checked it via Alt+F7, we can still safely delete it.

    Place the cursor on the method name and press Alt+Delete. This will pop up the safe delete dialog as before, and this time when you press OK IntelliJ IDEA will warn you that this method has usages

    Usages found

    Press View Usages to check what these are

    See all code usages
  5. Use the results panel to navigate to the usages by double-clicking on each usage. In our case, we see there's a test that calls the method we want to delete.

    Method is used in a test

    Since this test is there to ensure the correctness of a method we no longer want, we can delete this test too. In the editor window, press Alt+Delete on the test method name and say OK in the Safe Delete dialog. The test method will be removed.

  6. Now we'll see in our Safe Delete Conflicts window that this code is no longer valid.

    No more usages of the method

    Since this was our only usage of the method we originally wanted to delete, we can select the Rerun Safe Delete button. This time when you press OK on the Safe Delete dialog, the getUsernameFromMessage method will have been removed.

Conclusion

IntelliJ IDEA has a number of automatic refactorings available, all of which aim to let you, the developer, reshape your code in as low-impact way as possible. The aim is to make small, incremental changes, all the time keeping the code in a state that compiles. The power of the refactoring capabilities lies in chaining smaller changes together to move the code in the direction of some goal that you have in mind: reducing duplication, removing unnecessary code, striving for simplicity, improving readability, or some larger re-shaping of the design.

Small, simple changes are possible, even desirable, while working on new features or bug fixing, but remember that larger changes may need to applied separately to differentiate between refactoring that should not impact existing functionality and functional changes.