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.
- The name is not descriptive enough
- The class/method/variable name doesn't match the what it really is
- Something new has been introduced, requiring existing code to have more specific name
-
To rename the
endpointfield, 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.
.
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.
.
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.
-
To rename the method, the process is the same: place your cursor on
initand press Shift+F6. Here you'll have fewer suggestions, so type the new name:
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
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.
If you don't want to make some of these changes, press Backspace on the usages you do not want to change.
-
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.
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.
-
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:
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.
Once the variable is extracted, IntelliJ IDEA suggests possible names based on things like the parameter the expression was used in.
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. -
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_nameand pressCtrl+Alt+V.
We're going to give this a more meaningful name,
fieldName.
-
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 thisindexOfEndOfFieldValue.
- Finally, we can remove the magic number, as this is just the length of the field name. The
final code looks like:
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
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); }
finalor not is up to you, and depends upon your coding standards.
-
Place your cursor on
fieldNameand press Ctrl+Alt+P
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.
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.
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.
-
At this point, the original method
getUsernameFromMessageis 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.
-
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
getTextFromMessagevariable and press Ctrl+Alt+N
-
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::getUsernameFromMessageand 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");
-
First, highlight the code that's common between the two methods:
Pressing Ctrl+Alt+M will bring up the 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 thefieldNameparameter 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.
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.
-
At this point, our
getTextFromMessageandgetUsernameFromMessagemethods 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
fieldNameand select Refactor.
-
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
getValueForFieldmethod 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.
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.
- If the unused declaration inspection is turned on, the method name will be in grey to
represent that it is unused.
-
Place the cursor in
getUsernameFromMessageand press Alt+Enter. This will give you the option to delete this method.
-
Selecting safe delete will pop up the safe delete dialog, allowing you to search for usages of this method.
Press OK to go ahead and do the search. In our case, it was completely safe to delete, so the method is removed.
-
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
Press View Usages to check what these are
-
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.
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.
Now we'll see in our Safe Delete Conflicts window that this code is no longer valid.
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
getUsernameFromMessagemethod 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.