Features

Refactoring: Inline Method in IntelliJ IDEA

The idea of Inline Method Refactoring is simple – replace a method call with its contents. Still, it is really powerful. But to appreciate its power, you need to know its use cases.

Let’s understand why, when, and how to apply the Inline Method refactoring and how IntelliJ IDEA can help you get started.

Disruption in the flow of a method

When a method call disrupts the flow in your method rather than simplifying it, you can consider applying Inline Method. In the following code, the call to the method compareRatingWithMethodParameter() is inserting unnecessary indirection in the method scheduleSession().

public class Talk {
    int rating;

    LocalTime scheduleSession() {
        return compareRatingWithMethodParameter(3) ?
                LocalTime.of(9, 0) :
                LocalTime.of(10, 0);
    }

    private boolean compareRatingWithMethodParameter(int param) {
        return rating < param;
    }
}

The preceding code could be simplified by inlining the method compareRatingWithMethodParameter():

However, you don’t have to inline every method that defines just one line of code. The decision should depend on whether it is helping you to understand the flow in your method.

Code Migration, or using the latest Java language features

When migrating your code to a later Java version, you may want to inline a couple of methods so that you can apply an operation to code from multiple methods, collectively.
Though the code in the method getSortedListOfNames() looks readable, by inlining the methods extractNamesFromSpeakerList() and sortSpeakerNames(), you can refactor it to use Java streams instead of using the for loop and Collections.sort methods separately. Here’s the original code:

    List<String> getSortedListOfNames(List<Speaker> speakers) {
        List<String> speakerNames = extractNamesFromSpeakerList(speakers);
        sortSpeakerNames(speakerNames);
        return speakerNames;
    }

    private void sortSpeakerNames(List<String> speakerNames) {
        Collections.sort(speakerNames);
    }

    private List<String> extractNamesFromSpeakerList(List<Speaker> speakers) {
        List<String> result = new ArrayList<>();
        for (Speaker speaker : speakers) {
            result.add(speaker.getName());
        }
        return result;
    }

Here’s how you can inline the methods and refactor the resulting code:

Here’s the refactored code for you to compare with the original code:

    List<String> getSortedListOfNames(List<Speaker> speakers) {
    return speakers.stream()
            .map(Speaker::getName)
            .sorted()
            .collect(Collectors.toList());
}

Group of badly refactored methods

Often programmers end up applying refactoring practices poorly. For example, in the following code, the programmer seems to have applied ‘Extract Method Refactoring’ to each individual line of code.

Though it still looks readable, inlining a few of these methods and combining the code of one or more methods into another method, followed by (sensible) refactoring, can improve the intent of the code:

    List<Speaker> getPanelists(Track track, List<Speaker> speakerList) {
        outputTrackName(track);
        outputSpeakerList(speakerList);
        List<Speaker> panelists = filterSpeakersWithoutTracks(speakerList);
        panelists = findSpeakerSpeakingOnTrack(track, panelists);
        return panelists;
    }

    private void outputTrackName(Track track) {
        System.out.println("Looking for panelists for track : " + track);
    }

    private void outputSpeakerList(List<Speaker> list) {
        list.forEach(System.out::println);
    }

    private List<Speaker> filterSpeakersWithoutTracks(List<Speaker> list) {
        return list.stream().
                filter(s -> s.speakingOn != null)
                .collect(Collectors.toList());
    }

    private List<Speaker> findSpeakerSpeakingOnTrack(Track track, List<Speaker> list) {
        return list.stream()
                .filter(s -> s.isSpeakingOn(track))
                .collect(Collectors.toList());
    }

Let’s see how we can reorganize the code. Let’s inline a couple of methods and then extract code to methods, to make the code concise and readable.

Again, here’s the final code for you to compare with the initial code:

    List<Speaker> getPanelists(Track track, List<Speaker> speakerList) {
        outputParamValues(track, speakerList);
        return speakerList.stream().
                filter(s -> s.speakingOn != null && s.isSpeakingOn(track))
                .collect(Collectors.toList());
    }

    private void outputParamValues(Track track, List<Speaker> speakerList) {
        System.out.println("Looking for panelists for track : " + track);
        speakerList.forEach(System.out::println);
    }

Inline Method improvements

(New in IntelliJ IDEA 2019.2)

IntelliJ IDEA 2019.2 includes significant improvements to the Inline Method refactoring.
A method might include multiple exit points by defining multiple return statements. When you inline such a method in IntelliJ IDEA 2019.2, it can be modified to define just one exit point in the form of a single return statement, to exit the method. IntelliJ IDEA refers to this as the ‘transforms to single exit point’ feature.

In the following code, when you inline the method isBreakRequiredBetweenSession(), the process introduces a variable result. The multiple return statements are replaced with an assignment to the variable result and just one return statement at the end:

    void outputBreak() {
        boolean breakReq = isBreakRequiredBetweenSession();
        System.out.println("Break required = " + breakReq);
    }

    private boolean isBreakRequiredBetweenSession() {
        if (isWeekend())
            if (isDurationGreaterThan50min())
                return true;
            else
                return false;
        else
            return false;
    }

Let’s inline the method isBreakRequiredBetweenSession():

As you can see, the modified code is simpler to follow – since it doesn’t have multiple exit points. Here’s the final code for you to compare with the initial code:

    void outputBreak() {
        boolean breakReq;
        if (isWeekend())
            if (durationGreaterThan50min())
                breakReq = true;
            else
                breakReq = false;
        else
            breakReq = false;
        System.out.println("Break required = " + breakReq);
    }

It is usual for developers to define multiple return statements in a method that return values from various control statements like if, for, and others. Usually such a method also includes a return statement at the end. Here’s an example, which defines multiple exit points in the method acceptSpeaker() highlighted using end-of-line comments:

    boolean acceptSession(Speaker speaker, Talk talk) {
        List<DayOfWeek> days = List.of(DayOfWeek.SUNDAY, DayOfWeek.SATURDAY);
        if (speaker.getName().equals("JavaGuru"))
            return true;                                // Exit Point 1
        if (talk.getTrack() == Track.SCALA) {
            return false;				// Exit Point 2
        }
        for (DayOfWeek day : days) {
            if(talk.date.getDayOfWeek().equals(day)) {
                return true;				// Exit Point 3
            }
        }
        return false;
    }

    boolean submitTalk(Speaker speaker, Talk talk) {
        System.out.println("Received another submission");
        boolean isAccepted = acceptSession(speaker, talk);
        if (isAccepted) {
            // Update website
        }
        return isAccepted;
    }

When you inline the method acceptSession() in submitTalk(), IntelliJ IDEA will detect its multiple exit statements and modify the code accordingly:

Here’s the final code for you to compare (end-of-line comments added for your convenience):

    boolean submitTalk(Speaker speaker, Talk talk) {
        System.out.println("Received another submission");
        boolean isAccepted = false;
        List days = List.of(DayOfWeek.SUNDAY, DayOfWeek.SATURDAY);
        if (speaker.getName().equals("JavaGuru")) {
            isAccepted = true;                       // Assignment 
        } else {
            if (talk.getTrack() != Track.SCALA) {
                for (DayOfWeek day : days) {
                    if (talk.date.getDayOfWeek().equals(day)) {
                        isAccepted = true;           // Assignment
                        break;                       // break statement added
                    }
                }
            }
        }
        if (isAccepted) {
            // Update website
        }
        return isAccepted;                           // single return statement
    }

Inline method with negation

(New in IntelliJ IDEA 2019.2)

With the enhancements in IntelliJ IDEA 2019.2, the Inline Method refactoring also supports negation at call site. In the following code, when you inline the method check() with negation in the method isValidName(), it won’t be modified to a ‘single exit point’:

    boolean check(Speaker speaker) {
        if (speaker == null) return false;
        String name = speaker.name.trim();
        if (name.isEmpty()) return false;
        return name.length() % 2 == 0;
    }

    boolean isValidName(Speaker speaker) {
        return !check(speaker);
    }

Here’s how the inlined code is negated – notice the reversal of return values and condition (name.length() % 2 == 0) to (name.length() % 2 != 0):

Here’s the modified code for you to compare with the original code:

    boolean isValidName(Speaker speaker) {
        if (speaker == null) return true;
        String name = speaker.name.trim();
        if (name.isEmpty()) return true;
        return name.length() % 2 != 0;
    }

Summary

As developers, we refactor our code often. To refactor our code efficiently, we must know the use cases – why, when, and how to refactor it. IntelliJ IDEA plays a crucial role by automating the various refactoring options for you.

Happy coding and sensible refactoring!

image description