IntelliJ IDEA 2023.1 Help

Introduction to refactoring

IntelliJ IDEA offers a lot of automatic refactoring capabilities, but as developers it is 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 is 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?

Renaming is one of the simplest things you can do to improve the readability of the code. When a class, method or variable name doesn't match what it appears to do, it can cause a lot of confusion. Some reasons why you might want to rename something:

  • The name is not descriptive enough.

  • The class/method/variable name doesn't match what it really is.

  • Something new has been introduced, requiring existing code to have more specific name.

Renaming as you code

Imagine you come across the following code as you're implementing some feature or fixing some bug.

server = new Server(path, port, endpoint); server.init(); server.run();

Let's assume that we want to:

  • Rename endpoint, a field, to describe what sort of endpoint it is.

  • Rename init(), a method on Server, to something that more accurately describes that method.

  • Rename Server, a class, to something more specific.

  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.

Impact of renaming

Renaming local variables or private methods can be done fairly safely on-the-fly. For example, while you're working on a piece of functionality that touches this area of code, you can perform this refactoring knowing the impact is limited in scope.

Renaming classes or public methods could potentially impact a lot of files. If this is the case, this sort of refactoring should, at the very least, be in its own separate commit so the changes are clearly separated from any changed or additional functionality that you may have been working on at the 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

Extract variable is a low impact change to make your code code self-documenting. It can also be used to reduce code duplication.

Imagine you come across the following code

static String getUsernameFromMessage(String message) { return message.substring(message.indexOf("\"screen_name\":\"") + 15, message.indexOf("\"", message.indexOf("\"screen_name\":\"") + 15)); }
We can use extract variable to improve the readability of this code by:
  • Removing the duplication of message.indexOf("\"screen_name\":\"") + 15)

  • Introducing variables to describe what each of the indexOf calls represent

  • Removing the magic number 15

  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 press Control+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 press Control+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

Extracting or adding a parameter allows a developer to change a method so that it's easier to use. You may want to change the parameters, for example by passing in some values from an Object rather than the object itself, or you may want to introduce a value from the method body as a parameter to allow the method to be used in more places. We're going to look at an example of the latter.

For this example, we're going to use the same code as the last example, after it has been refactored, and extend it slightly to show another method in the same class:

static String getTextFromMessage(String message) { final String fieldName = "\"text\":\""; final int indexOfFieldValue = message.indexOf(fieldName) + fieldName.length(); final int indexOfEndOfFieldValue = message.indexOf("\"", indexOfFieldValue); return message.substring(indexOfFieldValue, indexOfEndOfFieldValue); } 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); }

Our goal is to remove the duplication of code that we see in these two methods. To do that, we're going to:

  • Change fieldName into a parameter so that we can make the getUsernameFromMessage method apply to any field.

  • Rename getUsernameFromMessage to something which represents its more general nature

  • Remove the duplication of code in getTextFromMessage.

  1. Place your cursor on fieldName and press Control+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 use Shift+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 Control+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");

Note that the way we applied this refactoring forces all callers to pass in the field name and a) spreads the use of a String value around your code and b) may introduce duplication of one or more of these String values. This may be appropriate for your code, especially if the String duplication is dealt with, or the method is not frequently used. However, if this is not a trade off you wish to make for reducing the duplication of code, see the next chapter for an alternative approach.

Extract parameter can be very powerful, so it's worth reading the more detailed help page.

Extract method

One way to aid code readability is to have it in small, understandable sections. Extract method allows a developer to do just that, moving segments of code into their own, descriptively-named, method when appropriate.

Some developers may find themselves writing long methods that perform the operation they have in mind, and when they've completed (and tested) the functionality, look at the code to see where it can be refactored and simplified and break up these longer methods. Or, as a developer comes across code when they're implementing a new feature, they realise extracting some code into its own method lets them reuse existing functionality.

We're going to look at the same example as in the previous section, but take a slightly different approach to before.

static String getTextFromMessage(String message) { final String fieldName = "\"text\":\""; final int indexOfFieldValue = message.indexOf(fieldName) + fieldName.length(); final int indexOfEndOfFieldValue = message.indexOf("\"", indexOfFieldValue); return message.substring(indexOfFieldValue, indexOfEndOfFieldValue); } 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); }

As we saw earlier, the previous refactoring had some trade-offs: a method reference needed to be converted to a lambda expression, and all calling code needed to know the field name that was required. We may choose to remove the code duplication between the two methods in a different way:

  • Extracting the common code into its own method.

  • Inlining variables to simplify the remaining code.

  1. First, highlight the code that's common between the two methods:

    Highlight an instance of the duplicate code

    Pressing Control+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 Control+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 Control+Shift+ArrowDown. 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.

Note that the Extract Parameter and Extract Method examples start with the same code, but end with code that looks very different. This is not just because we used a different refactoring, but because we made different decisions - in the first example, we chose to remove duplication completely, and move some of the decision-making into the caller of the method. In the second example, we chose to provide an API which hid the details of the field name behind small helper methods but still provide the more general method as well. We also could have mixed and matched the approaches, the refactoring we chose to begin with may have lead us in a particular direction but we can dictate our final destination. We should remember the goal of our refactoring (in this case, reduce duplication) and understand the trade offs we're making when we choose one direction over another, for example deciding whether we want calling code to know which field name they're asking for.

Impact of extracting

The good news is that you can undo an extract fairly easily. Not only by selecting Control+Z, of course, but by inlining the created method so that the code is back where it used to be.

The extract refactorings we've mentioned here are used regularly by experienced developers to shape the code as it evolves, and it would not be uncommon to use them to some greater or lesser extent every time the code is touched. Some of the ones that were not covered, like extract interface and extract superclass, may have a wider impact on the overall design, and more care should be taken with them.

    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

    IntelliJ IDEA lets you safe delete unused code fragments or whole files, informing you if it's safe to delete the code, and giving you the option to preview the changes before you make them. The fastest way to identify and deal with unused code is to make sure the relevant inspections are enabled, which they usually are by default:

    Inspections for finding unused code

    Let's continue the example of our previous refactoring. Assuming we ended up with this code:

    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); }

    It's possible that some time later, when we come back to this code, the getUsernameFromMessage method is no longer used - maybe it's no longer required, or maybe people are comfortable calling getValueForField with the relevant parameter. So assuming we're comfortable with these reasons, we can go ahead and remove this method.

    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.

    Impact of deleting

    IntelliJ's inspections can show you code that is unused, but if your code is going to be packaged as a library for others to use, or exposes a public API in some other way, it's possible that some public symbols may be marked as unused when in actual fact they are used by code that you do not control. If public symbols appear to be unused, you should check if these are used by other systems in some way.

    Unused parameters, local variables, and private fields are good candidates for deletion as it should be easy to see that deleting them does not impact any functionality.

    Using safe delete to remove symbols, whether they were unused or not, lets you check before you perform the refactoring that the areas impacted are the ones you expect, and gives you control over the changes you wish to apply. However, still be aware of the caveat that public symbols may be used by systems out of your control, so always exercise caution when deleting these.

      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.

      Last modified: 27 February 2023